文章目录

  • 需求背景
  • 走进源码
  • 实现示例
  • 参考

需求背景

当某一个目录的文件发生变化(创建、修改、删除、移动)时,需要给一个回调事件给其他端调用。
其他场景:阅后即焚等等。

比如在 Android 的 VR 设备中,有一个用于部署的文件,在Android 系统中发生变化时,需要给 Unity 端的一个回调,Unity 端基于该回调做相应的操作。

涉及到的技术点:

Unity 和 Android 端的数据交互,Android系统中 接口的设计、以及 AIDL 跨进程的通信等等,此处不在展开,后期再更新。本文只介绍一下,文件监听的使用及注意事项。

android.os下的FileObserver类是一个用于监听文件访问、创建、修改、删除、移动等操作的监听器,基于linux的inotify。

FileObserver 是个抽象类,必须继承它才能使用。每个FileObserver对象监听一个单独的文件或者文件夹,如果监视的是一个文件夹,那么文件夹下所有的文件和级联子目录的改变都会触发监听的事件。

所能监听的事件类型如下:

  • ACCESS,即文件被访问
  • MODIFY,文件被 修改
  • ATTRIB,文件属性被修改,如 chmod、chown、touch 等
  • CLOSE_WRITE,可写文件被 close
  • CLOSE_NOWRITE,不可写文件被 close
  • OPEN,文件被 open
  • MOVED_FROM,文件被移走,如 mv
  • MOVED_TO,文件被移来,如 mv、cp
  • CREATE,创建新文件
  • DELETE,文件被删除,如 rm
  • DELETE_SELF,自删除,即一个可执行文件在执行时删除自己
  • MOVE_SELF,自移动,即一个可执行文件在执行时移动自己
  • CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
  • ALL_EVENTS,包括上面的所有事件

走进源码

/**FileObserver 类是一个用于监听文件访问、创建、修改、删除、移动等操作的监听器,基于linux的inotify。FileObserver 是个抽象类,必须继承它才能使用。每个FileObserver对象监听一个单独的文件或者文件夹,如果监视的是一个文件夹,那么文件夹下所有的文件和级联子目录的改变都会触发监听的事件。
**/
public abstract class FileObserver {/** @hide */@IntDef(flag = true, value = {ACCESS,MODIFY,ATTRIB,CLOSE_WRITE,CLOSE_NOWRITE,OPEN,MOVED_FROM,MOVED_TO,CREATE,DELETE,DELETE_SELF,MOVE_SELF})@Retention(RetentionPolicy.SOURCE)public @interface NotifyEventType {}/** Event type: Data was read from a file */public static final int ACCESS = 0x00000001;/** Event type: Data was written to a file */public static final int MODIFY = 0x00000002;/** Event type: Metadata (permissions, owner, timestamp) was changed explicitly */public static final int ATTRIB = 0x00000004;/** Event type: Someone had a file or directory open for writing, and closed it */public static final int CLOSE_WRITE = 0x00000008;/** Event type: Someone had a file or directory open read-only, and closed it */public static final int CLOSE_NOWRITE = 0x00000010;/** Event type: A file or directory was opened */public static final int OPEN = 0x00000020;/** Event type: A file or subdirectory was moved from the monitored directory */public static final int MOVED_FROM = 0x00000040;/** Event type: A file or subdirectory was moved to the monitored directory */public static final int MOVED_TO = 0x00000080;/** Event type: A new file or subdirectory was created under the monitored directory */public static final int CREATE = 0x00000100;/** Event type: A file was deleted from the monitored directory */public static final int DELETE = 0x00000200;/** Event type: The monitored file or directory was deleted; monitoring effectively stops */public static final int DELETE_SELF = 0x00000400;/** Event type: The monitored file or directory was moved; monitoring continues */public static final int MOVE_SELF = 0x00000800;/** Event mask: All valid event types, combined */@NotifyEventTypepublic static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE| CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE| DELETE_SELF | MOVE_SELF;private static final String LOG_TAG = "FileObserver";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);}@UnsupportedAppUsagepublic 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 wfdobserver = (FileObserver) weak.get();if (observer == null) {m_observers.remove(wfd);}}}// ...then call out to the observer without the sync lock heldif (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);}@UnsupportedAppUsageprivate static ObserverThread s_observerThread;static {s_observerThread = new ObserverThread();s_observerThread.start();}// instanceprivate final List<File> mFiles;private int[] mDescriptors;private final int mMask;/*** Equivalent to FileObserver(path, FileObserver.ALL_EVENTS).** @deprecated use {@link #FileObserver(File)} instead.*/@Deprecatedpublic FileObserver(String path) {this(new File(path));}/*** Equivalent to FileObserver(file, FileObserver.ALL_EVENTS).*/public FileObserver(@NonNull File file) {this(Arrays.asList(file));}/*** Equivalent to FileObserver(paths, FileObserver.ALL_EVENTS).** @param files The files or directories to monitor*/public FileObserver(@NonNull List<File> files) {this(files, ALL_EVENTS);}/*** Create a new file observer for a certain file or directory.* Monitoring does not start on creation!  You must call* {@link #startWatching()} before you will receive events.** @param path The file or directory to monitor* @param mask The event or events (added together) to watch for** @deprecated use {@link #FileObserver(File, int)} instead.*/@Deprecatedpublic FileObserver(String path, @NotifyEventType int mask) {this(new File(path), mask);}/*** Create a new file observer for a certain file or directory.* Monitoring does not start on creation!  You must call* {@link #startWatching()} before you will receive events.** @param file The file or directory to monitor* @param mask The event or events (added together) to watch for*/public FileObserver(@NonNull File file, @NotifyEventType int mask) {this(Arrays.asList(file), mask);}/*** Version of {@link #FileObserver(File, int)} that allows callers to monitor* multiple files or directories.** @param files The files or directories to monitor* @param mask The event or events (added together) to watch for*/public FileObserver(@NonNull List<File> files, @NotifyEventType int mask) {mFiles = files;mMask = mask;}protected void finalize() {stopWatching();}/*** Start watching for events.  The monitored file or directory must exist at* this time, or else no events will be reported (even if it appears later).* If monitoring is already started, this call has no effect.*/public void startWatching() {if (mDescriptors == null) {mDescriptors = s_observerThread.startWatching(mFiles, mMask, this);}}/*** Stop watching for events.  Some events may be in process, so events* may continue to be reported even after this method completes.  If* monitoring is already stopped, this call has no effect.*/public void stopWatching() {if (mDescriptors != null) {s_observerThread.stopWatching(mDescriptors);mDescriptors = null;}}/*** The event handler, which must be implemented by subclasses.** <p class="note">This method is invoked on a special FileObserver thread.* It runs independently of any threads, so take care to use appropriate* synchronization!  Consider using {@link Handler#post(Runnable)} to shift* event handling work to the main thread to avoid concurrency problems.</p>** <p>Event handlers must not throw exceptions.</p>** @param event The type of event which happened* @param path The path, relative to the main monitored file or directory,*     of the file or directory which triggered the event.  This value can*     be {@code null} for certain events, such as {@link #MOVE_SELF}.*/public abstract void onEvent(int event, @Nullable String path);
}

源码解读及注意事项:

相关实现类并不复杂,代码也不多,这里可以完整看一下,学习一下实现原理。

  1. ALL_EVENTS 这个事件由 “|”位运算实现,位运算相关知识回顾。这里用或运算,后面在监听时的回调 onEvent会用到。

    符号 描述 运算规则
    & 两个位都为1时,结果才为1
    | 两个位都为0时,结果才为0
    ^ 异或 两个位相同为0,相异为1
    ~ 取反 0变1,1变0
    << 左移 各二进位全部左移若干位,高位丢弃,低位补0
    >> 右移 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)
  2. onEvent的回调事件处理中,我们得注意 用 "&"来监听,否则会出现返回未确定定义的 event type.这里其实不是bug.是我们用错的方式。

    @Override
    public void onEvent(int event, String path) {Log.d(TAG, "event: " + event);/* event的值是与 0x40000000 进行或运算后的值,所以在 case 之前需要先和 FileObserver.ALL_EVENTS进行与运算*/int e = event & FileObserver.ALL_EVENTS;switch (e) {case FileObserver.CREATE:break;case FileObserver.DELETE:break;}
    }

    如果不做 与&运算,你会得到以下的测试数字,以为是 bug. 其实不是。我们了解一下位运算就知道了。

类型值 含义
1073742080 “文件夹”的创建(Create)操作
1073742336 “文件夹”的删除(Delete)操作
1073741888 “文件夹”的移出(MOVE_FROM) 操作
1073741952 “文件夹”的移入(MOVE_TO) 操作
32768 “文件夹” 的打开操作 (OPEN) 操作

实现示例

FileObserver是一个抽象类,使用的时候我们需要自己实现一个类来继承FileObserver。

/*** <pre>*     @author : JuneYang*     time   : 2023/01/20*     desc   :*     version: 1.0* </pre>*/
public class SDCardFileObServer extends FileObserver {public static final String TAG = SDCardFileObServer.class.getSimpleName();public SDCardFileObServer(String path) {/** 这种构造方法是默认监听所有事件的,如果使用 super(String,int)这种构造方法,* 则int参数是要监听的事件类型.*/super(path);}@RequiresApi(api = Build.VERSION_CODES.Q)public SDCardFileObServer(@NonNull File file, int mask) {super(file, mask);}@Override public void onEvent(int event, @Nullable String path) {//注意点int e = event & FileObserver.ALL_EVENTS;switch (e) {case FileObserver.CREATE:break;case FileObserver.DELETE:break;case FileObserver.MODIFY:break;default:break;}}// 调用public static void main(String[] args) {String path = "xx/xx/xx";// 初始化操作SDCardFileObServer sdCardFileObServer = new SDCardFileObServer(path);sdCardFileObServer.startWatching();// 服务结束后关闭监听sdCardFileObServer.stopWatching();}

测试用例:
以监听某个目录为例,当目录下发生文件的状态变化时,测试情况如下:

  1. 拷贝文件时,如果文件过大,modify 方法会每 50ms 左右回调一次接口,因为文件在一直变化,直到不再变化为止。
  2. 替换文件时,会回调 deletecreatemodify 方法。
  3. 该路径下的两个文件如果执行拷贝、删除、替换,有几个文件就会执行几个文件的几种状态的回调。
  4. 文件夹删除时也会执行删除 delete回调,文件夹新建时会有 create 回调.
  5. 文件夹合并时不会有回调

Tips: 在项目中,由于 FileObserver对象必须保持一个引用,确保不被垃圾收集器回收掉,否则就不会触发事件。我们可以考虑使用 Service 服务。

也就是说在 Service 中的 Oncreate中初始化(startWatching),在OnDestory中(stopWatching)。

参考

位运算在Java编程中的应用

Android中巧妙的位运算_钟秀的博客-CSDN博客_android 视图标志位 或运算

Android系统中Flag的位操作设计

Android 中关于 FileObserver类监听文件状态的实践相关推荐

  1. Java 使用Tailer类监听文件

    线程创建的三种方式:   FileDataListener listener = new FileDataListener(path,this);         //使用单线程池线程监听文件     ...

  2. hutool工具包Tailer类监听文件的bug

    使用hutool工具包Tailer类监听文件内容时,如果文件忽然被清空后在重新写入,此时无法监听到文件第一行数据 解决方法: 复写cn.hutool.core.io.file包下LineReadWat ...

  3. Android 开发中的View事件监听机制

    在开发过程中,我们常常根据实际的需要绘制自己的应用组件,那么定制自己的监听事件,及相应的处理方法是必要的.我们都知道Android中,事件的监听是基于回调机制的,比如常用的OnClick事件,你了解它 ...

  4. Android 监听通话状态(挂断 监听 来电)实现自动外呼 按顺序拨出电话 间隔5秒

    最近项目也是添加一个小功能 监听通话状态实现一个自动外呼的功能 我在这里简单的实现了一下转了几天的博客可各大网站找出了一个最简单也好理解的一个方法(至今为止) 其实就用到了我们Android源生自带的 ...

  5. C# FileSystemWatcher监听文件夹

    用FileSystemWatcher监听文件夹 很久没有写windows服务了,这两天做了一个监听文件夹的服务,用FileSystemWatcher类监听文件夹,如果有新创建的xml文件,就把xml文 ...

  6. unity中监听文件夹并且创建文件夹后做资源更新

    unity中监听文件夹并且创建文件夹后做资源更新 有时候我们在设计的时候,可能对项目的文件内容进行监听,也可能需要在监听某个文件夹的操作,并且做出相对应的处理,例如项目资源的大小监听等,以下就提供两种 ...

  7. woo如何监听目录和文件变动,woo目录中的文件被改变,监听文件被修改权限

    记录下woo语言监听文件和和文件夹变动后触发事件 非常完整的例子了. 注意由于文件监听属于工具类,则需要使用wop而不是woo print('file notify')-- 初始化监听实列 local ...

  8. android 2.1 监听电话状态并自动接听来电

    一.开发环境       Elispse5.5,JDK1.6,Aadroid 2.1 二.开发中使用到的重点技术点:       距离感应(SENSOR_SERVICE ),音讯管理(AUDIO_SE ...

  9. Android实时监听网络状态

    Android开发实时监听网络状态变化一般有两种方法: 1. 新建一个基类BasicActivity.class,在基类中注册网络监听广播NetworkChangeReceiver.class,所有页 ...

最新文章

  1. cad自动标注界址点_CAD制图中的5个小技巧
  2. docker选择安装位置_如何使用docker 1.13版本更改centos 7中的docker安装目录
  3. [C++STL]C++实现vector容器
  4. codeforces1471 D. Strange Definition
  5. mysql的每隔1分钟定时_mysql定时任务
  6. Leecode刷题热题HOT100(14)——最长公共前缀
  7. python editor_Python+Weditor
  8. SQLServer2019安装教程
  9. HTML5期末大作业:仿天猫购物网站设计——仿天猫购物商城(7页) 网页设计作业,网页制作作业, 学生网页作业, 网页作业成品, 网页作业模板
  10. 计算机报名验证码不出现怎么办,电脑显示验证码很慢或验证码显示不出来怎么办...
  11. 微信公众号网页开发和小程序开发之路
  12. 黄牛用高性能服务器抢票,还在找黄牛“加速”抢票?成功抢票旅客:黄牛让我去12306候补...
  13. 利用python实现判断两条直线是否平行,若相交,输出交点。
  14. java中类名指的是什么_JAVA 类名.class是什么意思?
  15. 加菲猫台词 (请对号入座-:))
  16. w锋ndows用户组设置,第2章Wndows+Server+2008本地用户和组.ppt
  17. 为向南太平洋地区扩张 华为在马来西亚设立地区总部
  18. vue中base64图片展示提示 图片报错:GET data:image/png;base64,undefined net::ERR_INVALID_URL
  19. 挂微群发软件需要什么服务器信,用云服务器挂群发软件
  20. 如何选择合适的Joomla模板

热门文章

  1. 致逝去的大学四年和工作一年后的自己
  2. [Cocos2d塔防游戏开发]Cocos2dx-3.X完成塔防游戏《王国保卫战》--敌人(二)之BOSS
  3. AI应届博士年薪80万起步,BAT校招数据大披露:缺人!
  4. SLAM--ORB特征点提取--啰里啰嗦的代码解析
  5. python27.dll是系统自带的吗_正版《GTA5》自带“神仙辅助”功能?官方认可外挂属实厉害...
  6. 知识图谱赵军学习笔记(一)--概论
  7. 【学习笔记asp.net】WebService创建、发布及在IIS上部署
  8. Movavi视频套件2022功能简介
  9. 神雕侠侣2手游暂无服务器信息,神雕侠侣2手游开服表
  10. IT168 年初的采访稿