The practice of monitoring file status in FileObserver class in Android

demand background

When a file in a directory changes (create, modify, delete, move), a callback event needs to be called to other ends.
Other scenes: burn after reading, etc.

For example, in the Android VR device, there is a file for deployment. When there is a change in the Android system, a callback needs to be given to the Unity side, and the Unity side will perform corresponding operations based on the callback.

Technical points involved:

The data interaction between Unity and Android, the interface design in the Android system, and the AIDL cross-process communication, etc., will not be expanded here, and will be updated later. This article only introduces the use and precautions of file monitoring.

The FileObserver class under android.os is a listener for monitoring file access, creation, modification, deletion, movement and other operations, based on inotify of linux.

FileObserver is an abstract class and must be inherited before it can be used. Each FileObserver object listens to a single file or folder. If a folder is monitored, changes to all files and cascaded subdirectories under the folder will trigger the monitored event.

The types of events that can be monitored are as follows:

  • ACCESS, that is, the file is accessed
  • MODIFY, the file was modified
  • ATTRIB, file attributes are modified, such as chmod, chown, touch, etc.
  • CLOSE_WRITE, the writable file is closed
  • CLOSE_NOWRITE, non-writable files are closed
  • OPEN, the file is opened
  • MOVED_FROM, the file is removed, such as mv
  • MOVED_TO, the file is moved, such as mv, cp
  • CREATE, create a new file
  • DELETE, the file is deleted, such as rm
  • DELETE_SELF, self-deletion, that is, an executable file deletes itself when it is executed
  • MOVE_SELF, self-moving, that is, an executable file moves itself during execution
  • CLOSE, the file is closed, equivalent to (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
  • ALL_EVENTS, including all events above

into the source code

/**
	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);
}

Source code interpretation and precautions:

The relevant implementation classes are not complicated, and the code is not much. Here you can take a complete look and learn the implementation principle.

  1. ALL_EVENTS This event is realized by the "|" bit operation, and the relevant knowledge of bit operation is reviewed. The OR operation is used here, onEventwhich will be .

    symbol describe Algorithm
    & and The result is 1 only when both bits are 1
    | or The result is 0 only when both bits are 0
    ^ XOR Two bits are equal to 0 and different to 1
    ~ reconciliation 0 to 1, 1 to 0
    << move left All the binary bits are shifted to the left by several bits, the high bits are discarded, and the low bits are filled with 0
    >> move right All binary bits are shifted to the right by several bits. For unsigned numbers, the high bits are filled with 0, and for signed numbers, the processing methods of each compiler are different. Some complement the sign bit (arithmetic right shift), and some complement 0 (logic right shift).
  2. In onEventthe callback event processing, we have to pay attention to using "&" to monitor, otherwise there will be an undefined event type returned. This is actually not a bug. It is the wrong way we used it.

    exist

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

    If you don't do the AND &operation, you will get the following test numbers, thinking it is a bug. It is not. We know about bit operations.

type value meaning
1073742080 Create operation of "folder"
1073742336 "Folder" delete (Delete) operation
1073741888 Move out (MOVE_FROM) operation of "folder"
1073741952 Move-in (MOVE_TO) operation of "folder"
32768 Open operation (OPEN) operation of "folder"
insert image description here

Implementation example

FileObserver is an abstract class. When using it, we need to implement a class to inherit 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();
    }

Test case:
Take listening to a certain directory as an example, when the state of a file in the directory changes, the test situation is as follows:

  1. When copying a file, if the file is too large, modifythe method will call back the interface every 50ms or so, because the file keeps changing until it stops changing.
  2. When replacing a file, deletethe and createand modifymethods are called back.
  3. If the two files under this path are copied, deleted, and replaced, there will be callbacks for several states of several files.
  4. The deletion deletecallback , and there will be createa callback when the folder is created.
  5. There will be no callback when folders are merged

Tips: In the project, since the FileObserver object must maintain a reference, make sure it is not reclaimed by the garbage collector, otherwise the event will not be triggered. We can consider using the Service service.

That is to say, it is initialized in ( ) Oncreatein Service and in ( ).startWatchingOnDestorystopWatching

reference

Application of Bit Operation in Java Programming

Clever Bit Operations in Android

Bit operation design of Flag in Android system

Guess you like

Origin blog.csdn.net/jun5753/article/details/128741700