Android 文件或目录监听使用及底层原理详解(FileObserver)
创始人
2025-05-29 00:45:28

Android 文件(目录)监听详解(FileObserver)


如果我们想要对 sdcard 中的文件及文件夹的打开、创建、移动和删除操作等进行监听,怎么实现呢?

今天我们来学习一下文件监听的实现方式和原理。

Android 系统 API 提供了 FileObserver 抽象类( Linux 的 INotify 机制)来监听系统 /sdcard 中的文件或文件夹。它可以实现监视文件(使用 inotify )在文件被设备上的任何进程(包括本文)访问或更改后触发事件。

FileObserver 介绍

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 使用

FileObserver 是一个抽象类,所以在 Java 层使用 FileObserver,必须继承 FileObserver 类,并且实现 onEvent() 回调方法,这样就可以在目标文件发生变化时,回调 onEvent 方法,我们就可以在该方法中进行处理了。

注意:FileObserver 不支持子目录的递归监控。

继承 FileObserver 类

    private class MyFileObserver extends FileObserver{……@Overridepublic void onEvent(int event, String path) {……}}

构造方法

接下来就是创建 FileObserver 的实例对象,它有2种构造方法:

  1. 第一种是只传路径参数
public FileObserver (String path)
  1. 第二种是传递路径和需要监听的事件名称
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 实现文件监听的原理。

初始化

在 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。

相关内容

热门资讯

最新消息“新二号大厅到底有没有... 亲.新二号大厅这款游戏是可以开挂的,确实是有挂的,通过添加客服【3671900】很多玩家在这款游戏中...
玩家实测“开心联盟拼三张到底有... 您好:开心联盟拼三张这款游戏可以开挂,确实是有挂的,需要了解加客服微信【6355786】很多玩家在开...
玩家必看“悟空大厅有挂吗”20... 您好:悟空大厅这款游戏可以开挂,确实是有挂的,需要软件加微信【8487422】很多玩家在这款游戏中打...
假设面试:作为一名架构师,面对... 业务需求搜集 flexport作为一个货运系统,首先我们要明白我们的客户是谁ÿ...
科技通报“77竞技馆到底有没有... 您好:【77竞技馆】这款游戏可以开挂,确实是有挂的,需要了解加客服微信【6948699】很多玩家在这...