FileObserver:监听文件/目录变化

QuibblerQuibbler 2020-03-27 约 16 分钟 5290 次阅读

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常量

有ACCESS,MODIFY,ATTRIB,CLOSE_WRITE,CLOSE_NOWRITE,OPEN,MOVED_FROM,MOVED_TO,CREATE,DELETE,DELETE_SELF和MOVE_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级以后的安卓系统。    

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w