如果我们想要对 sdcard 中的文件及文件夹的打开、创建、移动和删除操作等进行监听,怎么实现呢?
今天我们来学习一下文件监听的实现方式和原理。
Android 系统 API 提供了 FileObserver 抽象类( Linux 的 INotify 机制)来监听系统 /sdcard 中的文件或文件夹。它可以实现监视文件(使用 inotify )在文件被设备上的任何进程(包括本文)访问或更改后触发事件。
Android 将 inotify 直接封装成了 FileObserver 类,可以直接在 Java 代码中使用。当然在 jni 中自己调用 inotify 也是很容易的。
FileObserver 是一个抽象类,子类必须实现事件处理程序 onEvent(int, String) 。
FileObserver可以监听两种类型的文件:一种是单个文件,另一种是文件目录。如果监视目录,则会针对受监视目录内的所有文件和子目录触发事件。
警告 :如果 FileObserver 被垃圾收集,它将停止发送事件。所以,为确保您继续接收事件,我们必须保留对某个其他活动对象的 FileObserver 实例的引用。
根据官方给出的文档,有以下几种事件的响应,对应不同的事件常量:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qpBWmytL-1678964517829)(evernotecid://2F3307D2-4160-48DD-8D24-A7E7D4FF4F1D/appyinxiangcom/9896050/ENResource/p2298)]
这些常量都定义在 FileObserver 类里面的,因此可以通过 FileObserver.XXX 来引用。
FileObserver 是一个抽象类,所以在 Java 层使用 FileObserver,必须继承 FileObserver 类,并且实现 onEvent() 回调方法,这样就可以在目标文件发生变化时,回调 onEvent 方法,我们就可以在该方法中进行处理了。
注意:FileObserver 不支持子目录的递归监控。
private class MyFileObserver extends FileObserver{……@Overridepublic void onEvent(int event, String path) {……}}
接下来就是创建 FileObserver 的实例对象,它有2种构造方法:
public FileObserver (String path)
public FileObserver (String path, int mask)
我们可以创建一个实例对象:
FileObserver fileObserver = new MyFileObserver("/data/anr/", CLOSE_WRITE);
这里我们创建了一个用来监听 “/data/anr/” 目录,“有人打开文件或目录进行写入并将其关闭”的行为。
调用 FileObserver 的 startWatching() 实例方法开始对文件进行监控。
fileObserver.startWatching();
调用 FileObserver 的 stopWatching() 实例方法停止对文件进行监控。
fileObserver.stopWatching();
我们来分析一下 FileObserver 实现文件监听的原理。
在 FileObserver 类中封装有一个 static 的线程类,文件的监听是通过它来实现的:
private static class ObserverThread extends Thread {……
}
ObserverThread 的构造函数:
public ObserverThread() {super("FileObserver");m_fd = init();}private native int init();
ObserverThread 构造方法中调用了 native 方法 init()。
init() 方法的实现,在 /frameworks/base/core/jni/android_util_FileObserver.cpp 中:
static jint android_os_fileobserver_init(JNIEnv* env, jobject object){#if defined(__linux__)return (jint)inotify_init1(IN_CLOEXEC);#elsereturn -1;#endif
}
这里判断是否是 linux 内核,然后调用了 inotify_init1(),初始化 inotify。
public void startWatching() {if (mDescriptors == null) {mDescriptors = s_observerThread.startWatching(mFiles, mMask, this);}}
这里直接调用 ObserverThread 的 startWatching 方法:
public int[] startWatching(List 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 fileObserverWeakReference =new WeakReference<>(observer);synchronized (mRealObservers) {for (int wfd : wfds) {if (wfd >= 0) {mRealObservers.put(wfd, fileObserverWeakReference);}}}return wfds;}
这里调用了 native 方法 startWatching ,对应的代码实现如下:
103 static void android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd,
104 jobjectArray pathStrings, jint mask,
105 jintArray wfdArray)
106 {
107 ScopedIntArrayRW wfds(env, wfdArray);
108 if (wfds.get() == nullptr) {
109 jniThrowException(env, "java/lang/IllegalStateException", "Failed to get ScopedIntArrayRW");
110 }
111
112 #if defined(__linux__)
113
114 if (fd >= 0)
115 {
116 size_t count = wfds.size();
117 for (jsize i = 0; i < count; ++i) {
118 jstring pathString = (jstring) env->GetObjectArrayElement(pathStrings, i);
119
120 ScopedUtfChars path(env, pathString);
121
122 wfds[i] = inotify_add_watch(fd, path.c_str(), mask);
123 }
124 }
125
126 #endif
127 }
调用 inotify_add_watch 添加监听并返回监听描述符。
在 ObserverThread 线程运行的 run()方法中:
public void run() {observe(m_fd);}
observe 的 native 层实现:
51 static void android_os_fileobserver_observe(JNIEnv* env, jobject object, jint fd)
52 {
53 #if defined(__linux__)
54
55 char event_buf[512];
56 struct inotify_event* event;
57
58 while (1)
59 {
60 int event_pos = 0;
61 int num_bytes = read(fd, event_buf, sizeof(event_buf));
62
63 if (num_bytes < (int)sizeof(*event))
64 {
65 if (errno == EINTR)
66 continue;
67
68 ALOGE("***** ERROR! android_os_fileobserver_observe() got a short event!");
69 return;
70 }
71
72 while (num_bytes >= (int)sizeof(*event))
73 {
74 int event_size;
75 event = (struct inotify_event *)(event_buf + event_pos);
76
77 jstring path = NULL;
78
79 if (event->len > 0)
80 {
81 path = env->NewStringUTF(event->name);
82 }
83
84 env->CallVoidMethod(object, method_onEvent, event->wd, event->mask, path);
85 if (env->ExceptionCheck()) {
86 env->ExceptionDescribe();
87 env->ExceptionClear();
88 }
89 if (path != NULL)
90 {
91 env->DeleteLocalRef(path);
92 }
93 //指向下一个inotify_event结构
94 event_size = sizeof(*event) + event->len;
95 num_bytes -= event_size;
96 event_pos += event_size;
97 }
98 }
99
100 #endif
101 }
这里进行监听,并在监听到文件操作时,调用 java 层 ObserverThread 类的 onEvent 方法:env->CallVoidMethod(object, method_onEvent, event->wd, event->mask, path);
FileObserver.stopWatching() --> ObserverThread.stopWatching()—> linux( inotify_rm_watch() )
FileObserver 的 stopWatching() 方法:
public void stopWatching() {if (mDescriptors != null) {s_observerThread.stopWatching(mDescriptors);mDescriptors = null;}}
调用 ObserverThread 的 stopWatching() 方法:
public void stopWatching(int[] descriptors) {stopWatching(m_fd, descriptors);}private native void stopWatching(int fd, int[] wfds);
最终调用 native 层的 android_os_fileobserver_stopWatching 方法实现:
129 static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object,
130 jint fd, jintArray wfdArray)
131 {
132 #if defined(__linux__)
133
134 ScopedIntArrayRO wfds(env, wfdArray);
135 if (wfds.get() == nullptr) {
136 jniThrowException(env, "java/lang/IllegalStateException", "Failed to get ScopedIntArrayRO");
137 }
138 size_t count = wfds.size();
139 for (size_t i = 0; i < count; ++i) {
140 inotify_rm_watch((int)fd, (uint32_t)wfds[i]);
141 }
142
143 #endif
144 }
注:Android 源码基于 android 13。