FileObserver失效,接收不到event

如何使用FileObserver

1. 申请权限 别忘了在代码中申请动态权限

	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

2.实现FileObserver 实现FileObserver时可以指定一个或多个文件及目录,还可以指定关心的events。

   private val observer = object : FileObserver(dir.absolutePath, ALL_EVENTS) {
            override fun onEvent(event: Int, path: String?) {
                if (debug) {
                    Log.d("FileObserverManager", "onEvent event $event path $path")
                }
            }
        }

3.启动监听和停止监听


        init {
            observer.startWatching()
            if (debug)
                Log.d("FileObserverManager", "init startWatching")
        }

        fun tryRelease() {
            observer.stopWatching()
            if (debug)
                Log.d("FileObserverManager", "tryRelease stopWatching")
        }

遇到的问题

整个使用过程还是比较简单的,没有什么复杂的过程,但是在使用过程中发现有的时候接收不到事件。在使用的过程中有多个画面都需要对路径文件变化进行监听,在画面退出的时候取消当前画面的监听。在网上搜索关于FileObserver接收不到event的问题,大家都是通过全局持有FileObserver的方式解决了问题,可是我的问题始终没有解决。 为了解决问题我们只能看FileObserver源代码来分析问题的原因了。通过FileObserver的源代码确实发现了有蹊跷的地方。

   private static ObserverThread s_observerThread;

    static {
        s_observerThread = new ObserverThread();
        s_observerThread.start();
    }

这个静态的ObserverThread应该就是问题的关键,由于他是静态的,所以所有的FileObserver都使用同一个ObserverThread来监听文件变化的。

 public void startWatching() {
        if (mDescriptors == null) {
            mDescriptors = s_observerThread.startWatching(mFiles, mMask, this);
        }
    }

FileObsever的startWatching方法用于开始监听,开始监听后返回数据给mDescriptors,可以想象我们可以通过mDescriptors找到文件监听的对应关系。下面看下停止监听的代码:

    public void stopWatching() {
        if (mDescriptors != null) {
            s_observerThread.stopWatching(mDescriptors);
            mDescriptors = null;
        }
    }

我们看到停止文件监听时把mDescriptors传给ObserverThread了,所以认为mDescriptors是保持着文件监听的映射关系应该是没问题了。

我在使用过程中多个界面都会通过FileObserver启动监听和停止监听,那么会不会是后面的画面退出后停止了前面画面对相同的路径的监听。经过反复测试发现确实就是相同路径的不同FileObserver的启动与关闭会相互影响。

如何解决问题

既然每个目录的不同的FileObserver会相互影响,那么我们为每个目录只维护一个FileObserver不就可以了吗。

object FileObserverManager {
    private const val debug = false
    private val fileObserverWrappers = hashMapOf<String, FileObserverWrapper>()

    fun register(listener: FileEventListener) {
        var fileObserverWrapper = fileObserverWrappers[listener.dir.absolutePath]
        if (fileObserverWrapper == null) {
            fileObserverWrapper = FileObserverWrapper(listener.dir).apply {
                fileObserverWrappers[listener.dir.absolutePath] = this
            }
        }
        fileObserverWrapper.addFileEventListener(listener)
    }

    fun unregister(listener: FileEventListener) {
        val fileObserverWrapper = fileObserverWrappers[listener.dir.absolutePath]
        fileObserverWrapper?.removeFileEventListener(listener)
        if (fileObserverWrapper?.tryRelease() == true) {
            fileObserverWrappers.remove(listener.dir.absolutePath)
        }
    }

    private class FileObserverWrapper(dir: File) {
        private val fileEventListeners = mutableListOf<FileEventListener>()

        private val observer = object : FileObserver(dir.absolutePath, ALL_EVENTS) {
            override fun onEvent(event: Int, path: String?) {
                if (debug) {
                    Log.d("FileObserverManager", "onEvent event $event path $path")
                }
                fileEventListeners.forEach { fileEventListener ->
                    if (event.and(fileEventListener.eventMask) != 0) {
                        fileEventListener.onEvent(event, path)
                    }
                }
            }
        }

        init {
            observer.startWatching()
            if (debug)
                Log.d("FileObserverManager", "init startWatching")
        }

        fun addFileEventListener(listener: FileEventListener) {
            fileEventListeners.add(listener)
            if (debug)
                Log.d("FileObserverManager", "addFileEventListener ${fileEventListeners.size}")
        }

        fun removeFileEventListener(listener: FileEventListener) {
            fileEventListeners.remove(listener)
            if (debug)
                Log.d("FileObserverManager", "removeFileEventListener ${fileEventListeners.size}")
        }

        fun tryRelease(): Boolean {
            if (fileEventListeners.isEmpty()) {
                observer.stopWatching()
                if (debug)
                    Log.d("FileObserverManager", "tryRelease stopWatching")
                return true
            }
            return false
        }
    }

    interface FileEventListener {
        val dir: File
        val eventMask: Int
        fun onEvent(event: Int, path: String?)
    }
}

fileObserverWrappers字段维护了目录与FileObserver的映射关系,FileObserverWrapper中除了管理FileObserver,它还管理着对相同目录的不同监听。

fun register(listener: FileEventListener) {
        var fileObserverWrapper = fileObserverWrappers[listener.dir.absolutePath]
        if (fileObserverWrapper == null) {
            fileObserverWrapper = FileObserverWrapper(listener.dir).apply {
                fileObserverWrappers[listener.dir.absolutePath] = this
            }
        }
        fileObserverWrapper.addFileEventListener(listener)
    }

    fun unregister(listener: FileEventListener) {
        val fileObserverWrapper = fileObserverWrappers[listener.dir.absolutePath]
        fileObserverWrapper?.removeFileEventListener(listener)
        if (fileObserverWrapper?.tryRelease() == true) {
            fileObserverWrappers.remove(listener.dir.absolutePath)
        }
    }

这两个方法完成了注册路径监听和删除路径监听,同时FileObserverManager是单例的,他维护了应用的所有监听。

    interface FileEventListener {
        val dir: File
        val eventMask: Int
        fun onEvent(event: Int, path: String?)
    }

FileEventListener接口定义了监听的路径和关心的事件。下面的代码展示了如何定义listener进行监听。

val fileEventListener = object : FileObserverManager.FileEventListener {
            override val dir: File
                get() = File("mnt/sdcard")
            override val eventMask: Int
                get() = FileObserver.CREATE.or(FileObserver.DELETE)

            override fun onEvent(event: Int, path: String?) {
                path ?: return
                Log.d("FileObserverManager", "onEvent $path")
            }
        }

fileEventListener监听的目录是"mnt/sdcard",监听的事件是创建和删除。

总结

  1. 文件权限必须申请。
  2. ObserverThread是静态的变量保存的,所以在相同进程中对相同路径的不同FileObserver的启动与取消会相互影响。
  3. 使用FileObserverManager单例对象维护相同路径的FileObserver,保证相同路径FileObserver的唯一性,这样也解决了冲突问题。

我的公众号已经开通,公众号会同步发布。
欢迎关注我的公众号

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mjlong123123

你的鼓励时我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值