Android - finalize的替代方案
  mkIZDEUN7jdf 2023年12月05日 19 0

前言

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对应的是同一个原始对象。

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月05日 0

暂无评论

推荐阅读
mkIZDEUN7jdf