首页 > 其他分享 >Android - finalize的替代方案

Android - finalize的替代方案

时间:2023-12-04 18:32:41浏览次数:26  
标签:FileResource val finalize demo close Android 替代 closeable sd

前言

java中可以重写finalize()方法来监听对象即将被回收,在里面做一些释放资源的操作,但是它被废弃了,有兴趣的同学可以查一下资料,我们探索一下有没有方案替代它。

分析

一般来说访问硬件或者文件资源的实例,在使用完毕之后需要关闭,如果忘记关闭了,finalize()被回调的时候也会关闭。如果不依赖finalize(),我们该怎么实现?

模拟一个文件资源接口,以及它的工厂类:

interface FileResource {
    fun write(content: String)
    fun close()
}

object FileResourceFactory {
    fun create(path: String): FileResource {
        // TODO 创建接口实例
    }
}

FileResource实例在使用完成后需要调用close()来释放资源。

假设我们是FileResource这个库的开发者,如果外部忘记调用close()了, 我们应该在没有引用指向实例的时候调用close(),那怎么判断没有引用指向实例了?

可以在外部从工厂获取实例的时候返回一个代理对象给外部使用,具体步骤如下:

  1. 创建原始对象,引用保存
  2. 创建代理对象,引用保存
  3. 弱引用映射到原始对象
  4. 返回代理对象给外部使用

说的比较抽象了,我们继续看下面的实现吧。

实现

先写FileResource的实现类,通常访问某个资源会有一个唯一标识,例如文件资源的路径,代码如下:

class FileResourceImpl(private val path: String) : FileResource {
    override fun write(content: String) {
        logMsg { "write $content $this" }
    }

    override fun close() {
        logMsg { "close $this" }
    }
}

再写代理类,就是对原始对象做一层包装,这里利用了kotlin语法特性,通过by委托给传入的原始对象,实现如下:

class FileResourceProxy(
    private val instance: FileResource
) : FileResource by instance

最后写工厂类,负责创建FileResource实例:

object FileResourceFactory {
    private val _holder = mutableMapOf<WeakReference<FileResource>, FileResource>()
    private val _refQueue = ReferenceQueue<FileResource>()

    // 创建实例
    fun create(path: String): FileResource {
        // 1.创建原始对象
        val instance = FileResourceImpl(path)

        // 2.创建代理对象
        val proxy = FileResourceProxy(instance)

        // 3.弱引用保存代理对象
        val weak = WeakReference<FileResource>(proxy, _refQueue)

        // 4.弱引用映射原始对象
        _holder[weak] = instance

        // 返回代理对象给外部使用
        return proxy
    }
}

create()方法返回到是代理对象proxyproxy被第3步中创建的weak弱引用保存,当外部没有引用指向proxy的时候后,weak就会被放入_refQueue中。

_refQueue的类型是ReferenceQueue,它的作用就是WeakReference保存的对象没有被引用的时候,垃圾回收机制会把WeakReference添加到ReferenceQueue中。

换句话说,当我们在ReferenceQueue中能拿到WeakReference的时候,WeakReference之前保存的对象已经没有被引用了,刚好符合我们的需求,就是proxy对象已经没有被引用了。

接着,我们再定义一个关闭方法来检查ReferenceQueue

fun close() {
    while (true) {
        // 1.从_refQueue中取弱引用
        val weak = _refQueue.poll() ?: break

        // 2.弱引用获取映射的原始对象
        val instance = _holder.remove(weak)

        // 3.关闭原始对象
        instance?.close()
    }
}

close()方法不断的从_refQueue中取弱引用,如果能取到,说明这个弱引用保存的代理对象已经没有被引用了,此时我们可以关闭原始对象了。

来测试一下它能不能正常工作,测试代码:

class MainActivity : AppCompatActivity() {
    // 代理对象
    private var _proxy: FileResource? = FileResourceFactory.create("/sdcard/app.log")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 触发write方法
        _proxy?.write("content")
    }

    override fun onStart() {
        super.onStart()
        logMsg { "onStart" }
        // 检查关闭
        FileResourceFactory.close()
    }

    override fun onStop() {
        super.onStop()
        logMsg { "onStop" }
        // 引用置为null
        _proxy = null
    }
}
12:35:30.735 closeable-demo          com.sd.demo.closeable           I  write content com.sd.demo.closeable.FileResourceImpl@6a57e3a
12:35:30.745 closeable-demo          com.sd.demo.closeable           I  onStart
12:35:35.018 closeable-demo          com.sd.demo.closeable           I  onStop
12:35:42.202 closeable-demo          com.sd.demo.closeable           I  onStart
12:35:42.203 closeable-demo          com.sd.demo.closeable           I  close com.sd.demo.closeable.FileResourceImpl@6a57e3a

onStop()里面将引用置为null,然后打开Profiler手动触发垃圾回收,onStart()里面调用FileResourceFactory.close()方法,根据日志可以看到对象的close()方法被触发了。

优化

流程已经走通了,但现在我们是主动调用FileResourceFactory.close()来关闭的,怎么做到自动关闭,这个放到最后来处理,先来看看有没有可以优化的地方。

优化一

每一个资源接口都要写一个代理类,这太机械化了,我们可以利用java动态代理来创建代理对象,关于java动态代理这里不赘述,直接看代码:

// 创建实例
fun create(path: String): FileResource {
    // ...

    // 2.创建代理对象
    val clazz = FileResource::class.java
    val proxy = Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { _, method, args ->
        if (args != null) {
            method.invoke(instance, *args)
        } else {
            method.invoke(instance)
        }
    } as FileResource

    //...
    return proxy
}

这样子我们就不需要FileResourceProxy这个类了。

优化二

在实际的场景中,一般有唯一标识的资源要考虑并发问题,create()的时候,如果他们传的参数path是一样的,那么返回的代理对象应该指向同一个原始对象,这样子我们只要考虑对象内部的线程同步逻辑就可以了。

资源的唯一标识可以理解为key,每个key对应一个实例。

先不考虑多个key的场景,只考虑单个实例的场景,单实例的工厂类写好后,我们把key和这个类做一个映射就可以了。

看一下单实例工厂类的代码:

class SingletonFileResourceFactory {
    private var _instance: FileResource? = null
    private val _proxyHolder = WeakHashMap<FileResource, String>()

    // 创建实例
    fun create(factory: () -> FileResource): FileResource {
        // 原始对象,如果为null,就调用factory创建
        val instance = _instance ?: factory().also {
            _instance = it
        }

        // 创建代理对象
        val clazz = FileResource::class.java
        val proxy = Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { _, method, args ->
            if (args != null) {
                method.invoke(instance, *args)
            } else {
                method.invoke(instance)
            }
        } as FileResource

        // 弱引用保存代理对象
        _proxyHolder[proxy] = ""
        return proxy
    }

    // 代理对象是否为空
    fun isEmpty(): Boolean {
        return _proxyHolder.isEmpty()
    }

    // 关闭
    fun close() {
        if (isEmpty()) {
            // _proxyHolder为空,说明外部的代理对象已经都用完了,可以关闭原始对象了
            _instance?.close()
            _instance = null
        }
    }
}

单实例工厂的逻辑比较简单,内部保存一个原始对象,如果为null就调用factory参数创建保存。

代理对象则换成了WeakHashMap保存,当_proxyHolder为空的时候说明外部的代理对象都已经用完了,没有引用了,此时可以关闭内部保存的原始对象,即close()方法的逻辑。

单实例工厂写好后,多个key对应多个实例的工厂就很好写了,看一下代码:

object FileResourceFactory {
    private val _holder = mutableMapOf<String, SingletonFileResourceFactory>()

    // 创建实例
    fun create(path: String): FileResource {
        val singletonFactory = _holder[path] ?: SingletonFileResourceFactory().also {
            _holder[path] = it
        }
        return singletonFactory.create { FileResourceImpl(path) }
    }

    // 关闭空闲的对象
    fun close() {
        _holder.iterator().run {
            while (hasNext()) {
                val item = next()
                val factory = item.value
                try {
                    factory.close()
                } finally {
                    if (factory.isEmpty()) {
                        remove()
                    }
                }
            }
        }
    }
}

代码简化了很多,_holder是一个Map,把key和单实例工厂做一个映射,create()的时候直接调用单实例工厂的create()即可。close()方法的逻辑也很简单,只要遍历所有单实例工厂调用close()就可以了。

优化三

如果每个资源接口都写这么一套逻辑,还是很繁琐的,可以写一个通用模板,通用模板不关心具体是什么资源接口了,只要它有提供关闭方法就可以了。

刚好有现成的接口java.lang.AutoCloseable,我们就用它来写通用模板:

public interface AutoCloseable {
    void close() throws Exception;
}

单实例工厂模板:

class SingletonFactory<T : AutoCloseable>(
    private val clazz: Class<T>
) {
    private var _instance: T? = null
    private val _proxyHolder = WeakHashMap<T, String>()

    // 创建实例
    fun create(factory: () -> T): T {
        // 原始对象,如果为null,就调用factory创建
        val instance = _instance ?: factory().also {
            _instance = it
        }

        // 创建代理对象
        val proxy = Proxy.newProxyInstance(clazz.classLoader, arrayOf(clazz)) { _, method, args ->
            if (args != null) {
                method.invoke(instance, *args)
            } else {
                method.invoke(instance)
            }
        } as T

        // 弱引用保存代理对象
        _proxyHolder[proxy] = ""
        return proxy
    }
}

代码和之前的SingletonFileResourceFactory差不多,只不过FileResource被抽象为模板了,Class从构造方法传进来,其他一模一样的代码就没有贴出来了。

多实例工厂模板:

class CloseableFactory<T : AutoCloseable>(
    private val clazz: Class<T>
) {
    private val _holder = mutableMapOf<String, SingletonFactory<T>>()

    // 创建key对应的实例
    fun create(key: String, factory: () -> T): T {
        val singletonFactory = _holder[key] ?: SingletonFactory(clazz).also {
            _holder[key] = it
        }
        return singletonFactory.create(factory)
    }
}

代码和之前的FileResourceFactory差不多,就不赘述了。

重构

模板写好了,我们来重构一下代码,FileResource接口要继承java.lang.AutoCloseable

interface FileResource : AutoCloseable {
    fun write(content: String)
}

重构一下FileResourceFactory,内部使用CloseableFactory

object FileResourceFactory {
    private val _factory = CloseableFactory(FileResource::class.java)

    // 创建path对应的实例
    fun create(path: String): FileResource {
        return _factory.create(path) { FileResourceImpl(path) }
    }

    // 检查关闭
    fun close() {
        _factory.close()
    }
}

重构完了,方法体里面一行代码搞定,我们来测试一下重构之后是否能正常工作

class MainActivity : AppCompatActivity() {
    // 代理对象
    private var _proxy1: FileResource? = FileResourceFactory.create("/sdcard/app.log")
    private var _proxy2: FileResource? = FileResourceFactory.create("/sdcard/app.log")
    private var _proxy3: FileResource? = FileResourceFactory.create("/sdcard/app.log.log")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 触发write方法
        _proxy1?.write("content")
        _proxy2?.write("content")
        _proxy3?.write("content")
    }

    override fun onStart() {
        super.onStart()
        logMsg { "onStart" }
        // 检查关闭
        FileResourceFactory.close()
    }

    override fun onStop() {
        super.onStop()
        logMsg { "onStop" }
        // 引用置为null
        _proxy1 = null
        _proxy2 = null
        _proxy3 = null
    }
}
15:09:44.417 closeable-demo          com.sd.demo.closeable           I  write content com.sd.demo.closeable.FileResourceImpl@bc5d31e
15:09:44.417 closeable-demo          com.sd.demo.closeable           I  write content com.sd.demo.closeable.FileResourceImpl@bc5d31e
15:09:44.417 closeable-demo          com.sd.demo.closeable           I  write content com.sd.demo.closeable.FileResourceImpl@c237ff
15:09:44.428 closeable-demo          com.sd.demo.closeable           I  onStart
15:09:50.580 closeable-demo          com.sd.demo.closeable           I  onStop
15:09:54.848 closeable-demo          com.sd.demo.closeable           I  onStart
15:09:54.848 closeable-demo          com.sd.demo.closeable           I  close com.sd.demo.closeable.FileResourceImpl@bc5d31e
15:09:54.848 closeable-demo          com.sd.demo.closeable           I  close com.sd.demo.closeable.FileResourceImpl@c237ff

一共有3个代理对象,_proxy1_proxy2_proxy3,实际上第1个和第2个他们代理的是同一个原始对象,因为他们的path一样。

从日志也可以看出一共是2个原始对象,最终FileResourceFactory.close()执行的时候也确实close()了2个原始对象。

自动关闭

这一节我们来实现自动关闭,即自动触发FileResourceFactory.close()

可以用IdleHandler实现,关于IdleHandler,有专门讲它的文章,这里就不赘述了,简单来说就是当你在主线程注册一个IdleHandler后,它会在主线程空闲的时候被执行。

看一下代码:

private class SafeIdleHandler(private val block: () -> Boolean) {
    private var _idleHandler: IdleHandler? = null

    fun register() {
        val mainLooper = Looper.getMainLooper() ?: return
        if (mainLooper === Looper.myLooper()) {
            addIdleHandler()
        } else {
            Handler(mainLooper).post { addIdleHandler() }
        }
    }

    private fun addIdleHandler() {
        Looper.myLooper() ?: return
        _idleHandler?.let { return }
        IdleHandler {
            block().also { if (!it) _idleHandler = null }
        }.also {
            _idleHandler = it
            Looper.myQueue().addIdleHandler(it)
        }
    }
}

这里说明一下构造方法中block的返回值Boolean代表什么意思:

true表示IdleHandler监听对象还要继续监听后续的线程空闲事件 false表示不再继续监听了,这个IdleHandler就会被移除

我们在多实例工厂CloseableFactory中使用一下它,看一下它的完整代码:

class CloseableFactory<T : AutoCloseable>(
    private val clazz: Class<T>
) {
    private val _holder = mutableMapOf<String, SingletonFactory<T>>()

    // 创建key对应的实例
    fun create(key: String, factory: () -> T): T {
        val singletonFactory = _holder[key] ?: SingletonFactory(clazz).also {
            _holder[key] = it
        }
        // 注册IdleHandler
        _idleHandler.register()
        return singletonFactory.create(factory)
    }

    private val _idleHandler = SafeIdleHandler {
        close()
        // 返回true,表示继续监听空闲回调;返回false,表示不继续监听了
        _holder.isNotEmpty()
    }

    private fun close() {
        _holder.iterator().run {
            while (hasNext()) {
                val item = next()
                val factory = item.value
                try {
                    factory.close()
                } finally {
                    if (factory.isEmpty()) {
                        remove()
                    }
                }
            }
        }
    }
}

最后我们再来测试一下:

class MainActivity : AppCompatActivity() {
    // 代理对象
    private var _proxy1: FileResource? = FileResourceFactory.create("/sdcard/app.log")
    private var _proxy2: FileResource? = FileResourceFactory.create("/sdcard/app.log")
    private var _proxy3: FileResource? = FileResourceFactory.create("/sdcard/app.log.log")

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 触发write方法
        _proxy1?.write("content")
        _proxy2?.write("content")
        _proxy3?.write("content")
    }

    override fun onStop() {
        super.onStop()
        logMsg { "onStop" }
        // 引用置为null
        _proxy1 = null
        _proxy2 = null
        _proxy3 = null
    }
}
17:20:51.112 closeable-demo          com.sd.demo.closeable           I  write content com.sd.demo.closeable.FileResourceImpl@bc5d31e
17:20:51.112 closeable-demo          com.sd.demo.closeable           I  write content com.sd.demo.closeable.FileResourceImpl@bc5d31e
17:20:51.112 closeable-demo          com.sd.demo.closeable           I  write content com.sd.demo.closeable.FileResourceImpl@c237ff
17:20:56.600 closeable-demo          com.sd.demo.closeable           I  onStop
17:21:01.313 closeable-demo          com.sd.demo.closeable           I  close com.sd.demo.closeable.FileResourceImpl@c237ff
17:21:01.313 closeable-demo          com.sd.demo.closeable           I  close com.sd.demo.closeable.FileResourceImpl@bc5d31e

和上面的测试代码差不多,去掉了手动close()的代码,可以看到已经可以自动close()了。

如果读者有自己的资源接口,想实现自动关闭的功能,只要在你的工厂类中使用CloseableFactory就可以了,当然了,要保证它是单例的,因为这样子才可以实现同一个key对应的是同一个原始对象。

标签:FileResource,val,finalize,demo,close,Android,替代,closeable,sd
From: https://blog.51cto.com/u_16175630/8680638

相关文章

  • 做Android开发必须要掌握哪些架构上的知识?
    前言谈到架构,在座的很多Android大佬想到的一定是MVC、MVP、MVVM这几个词,然后对比一下它们的优缺点,接下来就是站队的时间了。常常写MVC,偶然见到了MVP,真香。而写久了MVP,又听说了MVVM,又是真香~“真香”定律在架构这里真是被用得淋漓尽致,此外还要喜新厌旧一番,使用MVVM的鄙视使用MVP的,使......
  • Android 9.0 app全屏通过系统属性控制手势上滑是否显示虚拟导航栏和状态栏
    1.前言在9.0的系统rom产品定制化os开发中,在系统设置app的全屏后,默认会隐藏导航栏和状态栏,页面全屏显示的时候,然后底部上滑会显示虚拟状态栏和导航栏显示几秒钟后会自动消失,由于项目开发需要要求通过api来控制全屏时上滑是否显示虚拟导航栏和状态栏,这就要从上滑事件分析看如何显......
  • android 申请相机权限没有弹出授权对话框怎么办?
    当您在Android应用程序中申请相机权限时,如果没有弹出授权对话框,可能是由于以下几个原因导致的:权限已被授权:在某些情况下,如果用户先前已经授予了相机权限,系统将自动授予权限,而不会再次显示授权对话框。您可以在应用程序的设置中查看权限状态,或者在设备的应用程序设置中查看您的应用......
  • yocto-queue 库如何实现替代数组【玩转源码】
    前言前面提到了可以使用yocto-queue库代替Array操作数组,本篇则深入源码了解一下yocto-queue是如何实现替代数组的。yocto-queue源码分析源码中的代码量相对较少,读起来会比较轻松,看似可以琢磨的点少,其实不然。代码中包含知识点主要包括类的属性、链表与数组的对比、队列、自定义迭代......
  • Android开发显示头部Bar
    Android开发显示头部Bar需求:显示如下图:显示头部Bar,颜色也能自定义。解决方案这个修改是在如下三个文件里进行修改:按顺序修改:themes.xml(night):<resourcesxmlns:tools="http://schemas.android.com/tools"><!--Baseapplicationtheme.--><stylename="Base.Theme.Cro......
  • 07.Android开发者选项
    1.开启开发者选项点击设置点击关于手机连续点击N次MIUI版本2.常用选项开启开发者选项不锁定屏幕USB调试选择模拟位置信息应用显示触摸操作指针位置调试GPU过渡绘制显示所有“应用程序无响应” ......
  • 【Android逆向】一些零碎的笔记
    *在/sdcard/下的文件无法执行,必须将其拷贝到其它位置执行,如/data/目录,/data/目录中是system分组,可以执行程序;*每个应用都会创建一个对应的应用用户,如:cn.abcpiano.pianist包名的应用,创建了一个u0_a147用户;* getpropro.product.cpu.abi ......
  • android开发aar包或者jar包出现类重复问题Caused by: java.lang.RuntimeException: Du
    如果是仓库依赖的方式直接使用exclude语句移除相同的依赖库即可,如下:implementation("org.java-websocket:Java-WebSocket:1.5.2"){excludegroup:'org.slf4j',module:'slf4j-api'//exclude掉websocket库依赖的slf4j库}但是如果是aar包或者jar包里面的类重复呢?这个......
  • Android 启动流程 fastboot flashing unlock
     启动流程 | Android开源项目 | AndroidOpenSourceProjecthttps://source.android.google.cn/docs/security/features/verifiedboot/boot-flow?hl=zh-cn启动时验证启动时验证会尽力确保所有已执行代码均来自可信来源(通常是设备的原始设备制造商[OEM]),以防受到攻......
  • 直播网站源码,写一个android底部导航栏框架
    直播网站源码,写一个android底部导航栏框架import'package:flutter/material.dart'; voidmain(){ runApp(MyApp());} classMyAppextendsStatelessWidget{ @override Widgetbuild(BuildContextcontext){  returnMaterialApp(   title:'BottomNaviga......