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

news2024/9/30 1:34:39

文章目录

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

需求背景

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

比如在 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 */
    @NotifyEventType
    public 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);
        }

        @UnsupportedAppUsage
        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);
    }

    @UnsupportedAppUsage
    private static ObserverThread s_observerThread;

    static {
        s_observerThread = new ObserverThread();
        s_observerThread.start();
    }

    // instance
    private final List<File> mFiles;
    private int[] mDescriptors;
    private final int mMask;

    /**
     * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS).
     *
     * @deprecated use {@link #FileObserver(File)} instead.
     */
    @Deprecated
    public 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.
     */
    @Deprecated
    public 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的位操作设计

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/174354.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle P6 Professional专业版 22.12 中的热门新功能

目录 并排查看项目 在复制与 WBS 元素的关系时具有更大的灵活性 更轻松地确定要分配的正确基线 复制并粘贴电子表格中的单元格区域 更好地控制导入数据 检查 P6 专业版中提供的时间表报告 在排序对话框中排列字段顺序 创建导入和导出模板的副本 指定完成日期筛选器如何…

光流估计(一) 光流的简介与操作

今天是大年29&#xff0c;明天要贴春联了&#xff01;算是在年前赶出来一篇文章发&#xff08;太长时间没发东西了O。o&#xff09;&#xff0c;也算是自己在光流估计深度学习部分研究的开始~ 明年开学就是研二下学期了&#xff0c;时间过得飞快&#xff0c;毕设、实习、工作等…

MyBatis | 使用插件better-mybatis-generator自动生成dao、pojo

0️⃣简介&#x1f5fc;简介在我们编写MyBatis的项目时&#xff0c;常常需要为数据表编写大量的SQL语句以及dao类。better-mybatis-generator作为一款IDEA插件&#xff0c;可以自动为我们生成所需要的pojo类、dao类&#xff0c;并提供相当多的SQL单表查询操作。利用该插件&…

Python小技巧:富比较方法的妙用,__lt__、__le__、__eq__、__ne__、__gt__、__ge__。。。

前言 这里是Python小技巧的系列文章。这是第二篇&#xff0c;富比较方法的妙用。 在 Python中&#xff0c;富比较方法共6个&#xff0c;如下表所示&#xff1a; 见名知意&#xff0c;富比较主要用于比较。 富比较方法使用释义释义object.__lt__(self, other)x.__lt__(y)x<…

Springboot+mybatis使用PageHelper实现vue前端分页

Springbootmybatis使用PageHelper实现vue前端分页1、未分页前的vue前端效果图2、Springbootmybatis使用PageHelper分页逻辑&#xff1a;&#xff08;1&#xff09;Springboot、mybatis、PageHelper的版本&#xff1a;&#xff08;2&#xff09;yml文件配置pagehelper&#xff1…

带你了解docker是什么----初始篇

docker容器docker简介docker、虚拟环境与虚拟机docker 的核心概念Docker 镜像Docker 仓库Docker容器镜像、容器、仓库&#xff0c;三者之间的联系容器 容器一词的英文是container&#xff0c;其实container还有集装箱的意思&#xff0c;集装箱绝对是商业史上了不起的一项发明&…

11.3 关联容器操作

文章目录关联容器迭代器关键字成员不可修改&#xff0c;值可修改关于泛型算法添加元素向set插入元素向map插入数据insert操作总结检测insert的返回值展开递增语句向multiset和multimap添加元素删除元素map下标操作访问元素类型别名&#xff1a;类型别名说明key_type关键字类型&…

第一个Spring、第一个SpringBoot、Spring-Mybatis整合、SpringBoot-Mybatis整合

目录一、第一个Spring程序二、第一个SpringBoot三、Spring-Mybatis整合四、SpringBoot-Mybatis整合第一个程序一、第一个Spring程序 添加依赖——用以支持spring <dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</a…

线程池的简单介绍以及实现一个线程池

文章目录1、线程池存在的意义2、什么是线程池&#xff1f;3、线程池的使用2、java标准库中的线程池3、认识一下不同的线程池&#xff1a;4、认识一下线程池里的参数&#xff1a;4、实现一个简单的线程池1、线程池存在的意义 线程存在的意义&#xff1a;使用进程来实现并发编程…

鼠标右键没有git bash here(图文详解)

升级Win11后突然发现右键没有git bash here了解决&#xff1a;1. winr键&#xff0c;打开命令窗口,输入regedit打开注册表2. 在注册表中按照路径打开\HKEY_CLASSES_ROOT\Directory\Background\shell\3. 在shell上右键新建项&#xff0c;取名Git Bash Here&#xff0c;再点击Git…

SpringCloudConsul

上篇文章注册中心选出了Consul 和 K8S&#xff0c;现在我需要把他们集成到SpringCloud里&#xff0c;体验一下他们的服务注册发现、动态配置与权限分配难易 问题&#xff0c;以便选出更适合我们的。SpringCloudConsul首先用Docker搭建出Consul集群&#xff0c;这一步忽略了&…

8、Ubuntu22.4Server安装MariaDB10.10初始化密码Navicat远程登录

安装MariaDB10.10 查找源 apt search mariadb 在Ubuntu系统上从MariaDB存储库安装MariaDB10.10时&#xff0c;需要运行以下命令 sudo apt-get install apt-transport-https curl sudo curl -o /etc/apt/trusted.gpg.d/mariadb_release_signing_key.asc https://mariadb.org…

【微服务】Feign远程调用

本系列介绍的是Spring Cloud中涉及的知识点&#xff0c;如有错误欢迎指出~ 一.引子 我们以前基于RestTemplate发起的http请求远程调用服务&#xff1a; 存在下面的问题&#xff1a; 代码可读性差&#xff0c;编程体验不统一 参数复杂URL难以维护&#xff0c;字符串拼接硬编码…

逆卷积(ConvTranspose2d)是什么?

上图是一个卷积操作&#xff08;蓝色为输入&#xff0c;绿色为输出&#xff09;。 输入的特征图为x&#xff1a;( 4&#xff0c;4 &#xff0c;channels_in&#xff09;其中channels_in表示通道数。 卷积核设置&#xff1a;无padding&#xff0c; kernel size为3*3&#xff0c…

<关键字(1)>——《C语言深度剖析》

目录 关键字 - 第一讲 1.关键字分类 2.定义与声明 2.1 什么是变量(是什么) 2.2如何定义变量(怎么用) 2.3为什么要定义变量(为什么) 2.4 变量定义的本质 2.5 变量声明的本质 3. 最宽宏大量的关键字 - auto 3.1 变量的分类 3.2 变量的作用域 3.3 变量的生命周期 …

汇编语言(第四版)第八章 实验7 习题解答

Power idea 公司从1975年成立一直到1995年的基本情况如下&#xff1a; 下面的程序中&#xff0c;已经定义好了这些数据&#xff1a; assume cs:codesg,ds:datasgdatasg segmentdb 1975,1976,1977,1978,1979,1980,1981,1982,1983db 1984,1985,1986,1987,1988,1989,1990,1991,19…

【12】C语言_几个循环的经典练习

目录 1. 打印n的阶乘; 2、计算 1!2!3!......10! 3、用二分查找在一个有序数组中查找一个数 4、打印如下 5、输入三次密码 6、写一个猜数字游戏 7、如题 8、打印1到100之间 3的倍数 9、给两个数&#xff0c;求出最大公约数 10、找出从1000到2000之间的闰年 11、找出10…

Java是编译性语言还是解释型语言 ?

首先我们应该了解这两种语言的概念 . 高级语言在计算机上执行 , 有两种方式 , 分为编译型语言和解释型语言 . 编译型语言 : 编写源代码–>编译–>链接. 典型的编译型语言 : C/C . 特点 : 源代码中一处有错 , 就不允许编译 ; 编译过程中出现一处错误 , 就停止编译 . 优…

论文投稿指南——中文核心期刊推荐(武器工业)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

4个免费数据恢复软件:免费恢复您的数据

意外的文件删除或文件损坏可能会令人头疼&#xff0c;尤其是在您不使用云存储的情况下。两者通常都支持已删除的文件恢复和版本控制&#xff0c;以帮助您摆脱此类困境。如果您不使用云&#xff0c;通常唯一的机会就是使用数据恢复软件来找回丢失或损坏的数据。 这种方法有两个…