Android FileObserver–The Underlying inotify Mechanism and an Example

Android FileObserver is an interface provided by Android OS to monitor file access and modification. It’s based on the Linux inotify file change notification system.

This blog will give a brief introduction to inotify and illustrate the usage of Android FileObserver API with an example.

inotify

inotify is a simple but powerful file change notification system. It’s a mechanism built into the Linux 2.6.13 kernel and above.

The mechanism is exposed to the userspace program through a set of 3 system calls, including inotify_init, inotify_add_watch and inotify_rm_watch.

These system calls are declared in /usr/include/sys/inotify.h. One needs to include the header file as below in order to use them.

#include <sys/inotify.h>

 

The inotify_init function has the following prototype,

int inotify_init (void);

 

This function will create an inotify instance and return a file descriptor which all events are read from. Each instance is associated with a unique, ordered queue for events.

The inotify_add_watch function is declared as,

int inotify_add_watch (int __fd, const char *__name, uint32_t __mask);

 

where __fd is the file descriptor returned from the inotify_init function, __name is the path to the object (either file or directory) to watch, and __mask indicates the types of event to watch. The return value can be used to refer to the watch added, called watch descriptor.

The inotify_rm_watch system call is declared as,

int inotify_rm_watch (int __fd, int __wd);

 

where __fd is the return value of inotify_init, and __wd is the return value of inotify_add_watch. This function will remove the watch from the queue.

With this set of system call and normal file IO operations, one can monitor the file changes in Linux system. Please refer reference 1 and 2 for further details of inotify.

Android FileObserver

Android FileObserver is based on FileObserver. It provides similar monitoring mechanism as inotify does. One thing worth-mentioning is that the API documentation says “If a directory is monitored, events will be triggered for all files and subdirectories (recursively) inside the monitored directory”, but the FileObserver API is actually not recursive.

In other words, a FileObserver is attached to a folder, then only the files and sub-folders inside it are monitored, but its sub sub-folders and files are not. For example, there is a folder B inside folder A, and a folder C inside folder B. If a FileObserver is created to monitor folder A, then any modifications done for folder B is detected, but not for folder C.

An Android Sample App Based on FileObserver

As FileObserver is an abstract class, one must create a subclass that extends FileObserver and implement the event handler onEvent(int, string).

Below is an example,

package roman10.tutorial.fileobserver;

import android.os.FileObserver;

public class MyFileObserver extends FileObserver {
    public String absolutePath;

    public MyFileObserver(String path) {
        super(path, FileObserver.ALL_EVENTS);
        absolutePath = path;
    }

    @Override
    public void onEvent(int event, String path) {
        if (path == null) {
            return;
        }
        //a new file or subdirectory was created under the monitored directory
        if ((FileObserver.CREATE & event)!=0) {
            FileAccessLogStatic.accessLogMsg += absolutePath + "/" + path + " is createdn";
        }
        //a file or directory was opened
        if ((FileObserver.OPEN & event)!=0) {
            FileAccessLogStatic.accessLogMsg += path + " is openedn";
        }
        //data was read from a file
        if ((FileObserver.ACCESS & event)!=0) {
            FileAccessLogStatic.accessLogMsg += absolutePath + "/" + path + " is accessed/readn";
        }
        //data was written to a file
        if ((FileObserver.MODIFY & event)!=0) {
            FileAccessLogStatic.accessLogMsg += absolutePath + "/" + path + " is modifiedn";
        }
        //someone has a file or directory open read-only, and closed it
        if ((FileObserver.CLOSE_NOWRITE & event)!=0) {
            FileAccessLogStatic.accessLogMsg += path + " is closedn";
        }
        //someone has a file or directory open for writing, and closed it 
        if ((FileObserver.CLOSE_WRITE & event)!=0) {
            FileAccessLogStatic.accessLogMsg += absolutePath + "/" + path + " is written and closedn";
        }
        //[todo: consider combine this one with one below]
        //a file was deleted from the monitored directory
        if ((FileObserver.DELETE & event)!=0) {
            //for testing copy file
// FileUtils.copyFile(absolutePath + "/" + path);
            FileAccessLogStatic.accessLogMsg += absolutePath + "/" + path + " is deletedn";
        }
        //the monitored file or directory was deleted, monitoring effectively stops
        if ((FileObserver.DELETE_SELF & event)!=0) {
            FileAccessLogStatic.accessLogMsg += absolutePath + "/" + " is deletedn";
        }
        //a file or subdirectory was moved from the monitored directory
        if ((FileObserver.MOVED_FROM & event)!=0) {
            FileAccessLogStatic.accessLogMsg += absolutePath + "/" + path + " is moved to somewhere " + "n";
        }
        //a file or subdirectory was moved to the monitored directory
        if ((FileObserver.MOVED_TO & event)!=0) {
            FileAccessLogStatic.accessLogMsg += "File is moved to " + absolutePath + "/" + path + "n";
        }
        //the monitored file or directory was moved; monitoring continues
        if ((FileObserver.MOVE_SELF & event)!=0) {
            FileAccessLogStatic.accessLogMsg += path + " is movedn";
        }
        //Metadata (permissions, owner, timestamp) was changed explicitly
        if ((FileObserver.ATTRIB & event)!=0) {
            FileAccessLogStatic.accessLogMsg += absolutePath + "/" + path + " is changed (permissions, owner, timestamp)n";
        }
    }
}

Once the subclass is defined, one needs to create instances of this class in order to use it.

MyFileObserver fileOb = new MyFileObserver("/sdcard/testf/");

 

Similar to inotify_add_watch and inotify_remove_watch, one can call startWatching() and stopWatching() methods to start or stop monitoring.

fileOb.startWatching();
fileOb.stopWatching();

For a complete sample app which monitors the /sdcard directory of the Android OS, and output the results to screen, you can refer to my github repo here, or simply click here to download the source code zip file.

Below is a screenshot of the sample app.

device

Figure 1. Screenshot of File Modification Monitor

References

1. Kernel Korner – Intro to inotify: http://www.linuxjournal.com/article/8478

2. inotify – a powerful yet simple file change notification system: http://www.kernel.org/doc/Documentation/filesystems/inotify.txt

3. Android FileObserver API documentation: http://developer.android.com/reference/android/os/FileObserver.html

14 thoughts on “Android FileObserver–The Underlying inotify Mechanism and an Example”

  1. Wow, this is extremely helpful. I’m trying to develop an instantaneous syncing mechanism–would you mind if I borrowed your source to modify & combine with rsync?

    Thanks,

    Mike

  2. Hey..thank you for the examples..can you also tell me the life cycle of the file observer..I mean when will it stop monitoring the folder(except for stopWatching())..if I define it in activity A..and then move to activity B (finishing Activity A)..will it still keep observing the folder? Thanks!

  3. Hi Roman,
    Thank you very much for this excellent example! I installed it on my mobile and it works perfect. It helped me to resolve two very important cases in my current job. So, thank you again for your very good job!

  4. Hi
    This is what I was looking for my small school project. I’m newbie to the ANdroid and entirely new to FileObserver. I got the source from GIt and compiled it successfully. But when i run nothing logs on the screen other than 4 buttons. I take a photo or delete a file or folder nothing logs. Please let me know how to test it.
    Thanks in advance
    Karthik

Leave a Reply

Your email address will not be published. Required fields are marked *