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。
同一个进程中,如果有两个不同的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级以后的安卓系统。