FileObserver:监听文件/目录变化

Quibbler 2020-3-27 3079

FileObserver监听文件/目录变化


        Android OS提供了一个强大的监听文件目录变化的抽象类:FileObserver。FileObserver对象实例常常用来监听一个文件或者文件夹。当文件夹被监听时,任意子文件或文件夹的访问或修改都会触发事件。注意:FileObserver 不能监听到二级子文件夹或文件的变化。目录获取参考《Android中的各种File目录》

        Google官方文档:Android 开发者 > Docs > 参考 > FileObserver



1、构造FileObserver


1.1、构造方法

        要使用FileObserver,先来了解一下如何获取一个FileObserver对象,它是抽象类,其中包含一个抽象方法onEvent(int event,String path) 。构造方法有一些几种:

        ①FileObserver (String path)  deprecated

        ②FileObserver (File file)

        ③FileObserver (List<File> files)

        ④FileObserver (String path,int mask) deprecated

        ⑤FileObserver (File file,int mask)

        ⑥FileObserver (List<File> files,int mask)

参数说明:

        file:不难看出是要监听的文件或者目录对象。这个参数值不能为空!

        files:或者干脆直接传入一个File的集合List。这个参数值不能为空!

        path:至于直接传入String文件或者目录路径的构造方法已经被废弃。

        mask:是设定要监听文件或目录变化的哪些事件:一个(0)或多个事件(加在一起)。


1.2、mask常量

        有ACCESSMODIFYATTRIBCLOSE_WRITECLOSE_NOWRITEOPENMOVED_FROMMOVED_TOCREATEDELETEDELETE_SELFMOVE_SELF 。

Constant描述
ACCESS      1文件被读取
ATTRIB      4权限 所有者 时间戳被修改
CLOSE_NOWRITE      16打开并关闭了文件夹(未修改)
CLOSE_WRITE      8打开并关闭了文件夹(有修改)
CREATE      1073742080监控的文件夹下创建了子文件或文件夹
DELETE      1073742336监控的文件夹下删除了子文件或文件夹
DELETE_SELF      1024被监控的文件或文件夹被删除,监控停止
MODIFY      2文件被修改
MOVED_FROM      64被监控文件夹有子文件或文件夹移走
MOVED_TO      128被监控文件夹有子文件或文件夹被移入
MOVE_SELF      2048被监控文件或文件夹被移动
OPEN      32768文件或文件夹被打开
ALL_EVENTS      4095以上所有事件



2、FileObserver方法

        FileObserver类中主要只有三个方法:onEvent(int event, String path)startWatching()startWatching()


2.1、onEvent(int event, String path)

        这是个抽象方法,通过匿名类初始化FileObserver对象的时候实现该方法,当监听到文件变化会回调此方法。

参数:

        event:发生的事件类型

        path:相对于触发事件的文件或目录的主要监视文件或目录的路径。 对于某些事件,例如MOVE_SELF,此值可以为null


2.2、startWatching()

        监视不是从创建开始的! 必须先调用startWatching()才能接收事件。

        开始监听文件事件。被监视的文件或目录此时必须存在,否则将不报告任何事件(即使稍后出现)。如果已经开始监视,则此调用无效。


2.3、stopWatching() 

        停止观看事件。 某些事件可能正在处理中,因此即使此方法完成后,事件也可能会继续报告。 如果监视已停止,则此调用无效。



3、原理

        FileObserver内部有一个ObserverThread类型的私有静态变量s_observerThread

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


        来看看ObserverThread类的源码,它继承自线程。

    private static class ObserverThread extends Thread {
        private HashMap<Integer, WeakReference> m_observers = new HashMap<Integer, WeakReference>();
        private int m_fd;
        
        public ObserverThread() {
            super("FileObserver");
            m_fd = init();
        }
        
        public void run() {
            observe(m_fd);
        }
        
        public int[] startWatching(List<File> files,@NotifyEventType int mask, FileObserver observer) {
            final int count = files.size();
            final String[] paths = new String[count];
            for (int i = 0; i < count; ++i) {
                paths[i] = files.get(i).getAbsolutePath();
            }
            final int[] wfds = new int[count];
            Arrays.fill(wfds, -1);
            startWatching(m_fd, paths, mask, wfds);
            final WeakReference<FileObserver> fileObserverWeakReference =
                    new WeakReference<>(observer);
            synchronized (m_observers) {
                for (int wfd : wfds) {
                    if (wfd >= 0) {
                        m_observers.put(wfd, fileObserverWeakReference);
                    }
                }
            }
            return wfds;
        }
        
        public void stopWatching(int[] descriptors) {
            stopWatching(m_fd, descriptors);
        }
        
        public void onEvent(int wfd, @NotifyEventType int mask, String path) {
            // look up our observer, fixing up the map if necessary...
            FileObserver observer = null;
            synchronized (m_observers) {
                WeakReference weak = m_observers.get(wfd);
                if (weak != null) {  // can happen with lots of events from a dead wfd
                    observer = (FileObserver) weak.get();
                    if (observer == null) {
                        m_observers.remove(wfd);
                    }
                }
            }
            // ...then call out to the observer without the sync lock held
            if (observer != null) {
                try {
                    observer.onEvent(mask, path);
                } catch (Throwable throwable) {
                    Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable);
                }
            }
        }
        
        private native int init();
        private native void observe(int fd);
        private native void startWatching(int fd, String[] paths,@NotifyEventType int mask, int[] wfds);
        private native void stopWatching(int fd, int[] wfds);
    }

        这是就是一个线程,在run()方法中开启native层的监控。可以看见FileObserver的开启和停止监控的方法都有对应的native层方法。

        FileObserver的startWatching()

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

        FileObserver的stopWatching()

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

        其实都是调用了ObserverThread中的native底层方法,开启和暂停监控。



4、坑点


4.1、onEvent()方法中不能操作View

        前面已经看到了是在ObserverThread子线程中调用FileObserver的onEvent()方法,子线程中不能操作UI,所以一定要注意不要重写该方法的时候在里面操作UI,应该回到主线程处理。

       onEvent()在子线程中调用,也比较合理,因为操作文件/目录是比较耗时的操作。


4.2、带锁的方法

        startWatching和stopWatching都存在加锁操作,调用FileObserver的 startWatching时,尽量不要放在自己实现的对象锁中实现,可能会引发死锁操作,可能导致ANR。


4.3、收不到onEvent回调
        同一个进程中,如果有两个不同的FileObserver同时监控一个Path,只有后调用startWatching的FileObserver能够收到onEvent回调。


4.4、手机适配

        锤子手机:不能使用File对象作为构造参数(参考Android中的各种File目录),否则构造FileObserver失败,报错:

        java.lang.NoSuchMethodError: No direct method <init>(Ljava/io/File;I)V in class Landroid/os/FileObserver; or its super classes (declaration of 'android.os.FileObserver' appears in /system/framework/framework.jar)


4.5、Android 10.0系统适配

        Android 10以后外部存储器目录权限收紧,getExternalStorageDirectory()不能随便访问文件和文件夹。注意适配Android 10级以后的安卓系统。    



不忘初心的阿甘
最新回复 (0)
    • 安卓笔记本
      2
        登录 注册 QQ
返回
仅供学习交流,切勿用于商业用途。如有错误欢迎指出:fluent0418@gmail.com