從inotify機制說到FileObserver實現原理

有些情況下,我們難免需要監控一些文件的變化情況,這該如何實現呢?自然而然的我們會想要利用一個線程,每個一段時間便去看看文件的情況,這種方式本質上就是基於時間調度的輪訓.雖然能夠實現我們的需求,但是這種方式只適合文件經常變化的情況,其他情況下都非常低效,並且可能丟掉某些類型的變化,也就是說,這種方式無法實現實時的文件監控.

inotify簡介

那還有其他的方式麼?熟悉linux的童鞋應該記得從linux kernel 2.6.13開始引入inotify機制,用於通知用戶相關文件變化情況:任何一個文件發生某種變化,都會產生一個相應的文件事件.

我們不僅好奇,文件的哪些事件能夠被監控,也就是說inotify支持監控文件的哪些變化呢?繼續往下看.

可監控事件類型

目前inotify能夠監控的以下文件變化事件:

事件類型 說明
IN_ACCESS 文件被訪問
IN_MODIFY 文件被修改
IN_ATTRIB 文件屬性被修改
IN_CLOSE_WRITE 可寫文件被關閉
IN_CLOSE_NOWRITE 不可寫文件被關閉
IN_CLOSE 文件被關閉,也就以上兩者的集合
IN_OPEN 文件被打開
IN_MOVED_FROM 文件被移來
IN_MOVED_TO 文件被移走
IN_MOVE 文件被移動,也就是以上兩者的集合
IN_CREATE 文件被創建
IN_DELETE 文件被刪除
IN_DELETE_SELF 自刪除,也就是一個可執行文件在執行時嘗試刪除自己
IN_MOVE_SELF 自移動,也就是一個可執行文件在執行時嘗試移動自己
IN_UNMOUNT 宿主文件系統被卸載

API說明

不難發現,inotify對文件監控的支持是非常全面的,足以滿足我們絕大部門的需求.接下來,我們對innotify的api做個簡單的說明:

方法 說明
inotify_init 用於創建一個inotify實例,返回一個指向該實例的的文件描述符
inotify_add_watch 添加對文件或者目錄的監控,可以指定需要監控那些文件變化事件
inotify_rm_watch 從監控列表中移除監控文件或者目錄
read 讀取事件信息
close 關閉文件描述符,並會移除所有在該描述符上監控

事件通知

在可監控事件中,我們已經瞭解inotify支持的文件事件.現在來看看這些當這些文件事件產生時,其發出通知的結構.在inotify中,事件通知結構用結構體inotify_event表示:

struct inotify_event
{
  int wd;               //監控目標的watch描述符
  uint32_t mask;        //事件掩碼
  uint32_t cookie;      //事件同步cookie
  uint32_t len;         //name字符串的長度
  char name __flexarr;  //被監視目標的路徑名
  };

這裏需要記住一點:name字段並不是什麼時候都有的:只有要監控的目標是一個目錄,且產生的事件與目錄內部的文件或子目錄相關,且與目錄本身無關時纔會提供相應的name字段.
cookie用於關聯被觀察對象的IN_MOVED_FROM事件和IN_MOVED_TO 事件.

使用流程

瞭解以上之後,那麼該怎麼用呢?要實現對文件或者目錄的監控需要經過以下幾個步驟:

1. 創建inotify實例

在應用程序中,首先需要創建inotify實例:

int fd=inotify_init(); 

該方法創建了一個inotify實例,並返回一個文件描述符以便能夠通過這個描述符訪問到inotify實例.

2. 添加監控

在獲得inotify實例產生的文件描述符之後,我們就可以爲其添加watch.另外,我們也可以使用mask(事件掩碼)來設置我們想監控的事件類型.當然可以我們也可以使用IN_ALL_EVENTS監控所有事件:

int wd=inotify_add_watch(fd,path,mask)

補充:
此處的fd即inotify_init()方法返回的文件描述符.每個文件描述符都有一個排序的事件序列.path則是需要監控的文件或者目錄的路徑.mask則是事件掩碼,它表示應用程序對哪些事件感興趣.

文件系統產生的事件由Watch對象來管理,該方法將返回的wd就是Watch對象的句柄.

3. 等待事件與循環處理

在爲inotify實例添加watch之後,接下來就是等待事件了.爲了能不斷的處理事件,我們將其放在循環體當中.
在循環中,通過read()方法可以一次獲得多個事件.在沒有事件產生時,read()被阻塞,一旦有事件產生,那麼我們就可以讀取事件到的我們設置的事件數組中,然後對事件數組進行處理,其簡單代碼如下:

//事件數組,自定義設置,這裏我們設置爲128
char event_buf[128];

while(true){
     int num_bytes=read(fd,event_buf,len);
     //處理事件
     handleEvent(event_buf);
     //....省略....
    }

補充
event_buf是一個事件數組,用於接受文件變化所產生的事件(inotify_event).len則指定了要讀的長度.通常來說,len大於事件數組的大小,很多時候,我們也會直接取事件數組的大小來作爲len.

4.停止監控

當需要停止監控的時候,需要爲文件描述符刪除watch:

int r=inotify_rm_watch(fd,wd);

此處的fd也是在創建inotify時返回的文件描述符,wd則是上面提到watch對象的句柄.

到現在,我們已經對inotity有了初步的理解,感興趣的童鞋可以自行研究.我們的重點還是Android中FileObserver的實現.接下來,我們真正的開始瞭解FileObserver的實現.


FileObserver實現原理

我們知道Android 1.5時對應的linux內核已經是2.6.26,因此完全可以在Android上利用inotify機制來實現對文件的監控.Google很顯然意識到了這一點,並且幫我們在inotify的基礎上進行封裝—FileObserver,以實現監聽文件訪問,創建,修改,刪除等操作

接下來,來看一下FileObserver如何藉助inotify實現文件監控的.

監控線程初始化

在FileObserver中存在一個靜態內部類ObserverThread,該線程類是實現了文件監控的過程:

public abstract class FileObserver {
    //可監控的事件
    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 class ObserverThread extends Thread {
        //....省略多行代碼....
    }

    private static ObserverThread s_observerThread;

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

       //....省略多行代碼....

}

不難發現發現FileObserver通過靜態代碼塊的方式構造了s_observerThread對象,我們來看一下其構造過程:

     public ObserverThread() {
            super("FileObserver");
            m_fd = init();
        }

這裏又調用natvie方法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_init();
#else
    return -1;
#endif
}

其實現非常簡單,就是調用inotify中的inotify_init()來創建一個inotify實例。回到FileObserver中來看s_ObserverThread的啓動:

public void run() {
            observe(m_fd);
        }

這裏同樣是調用natvie方法observe(int fd)

static void android_os_fileobserver_observe(JNIEnv* env, jobject object, jint fd)
{
#if defined(__linux__)

    //設置事件數組
    char event_buf[512];
    struct inotify_event* event;

    //循環處理讀到的事件
    while (1)
    {
        int event_pos = 0;
        //讀取事件
        int num_bytes = read(fd, event_buf, sizeof(event_buf));

        if (num_bytes < (int)sizeof(*event))
        {
            if (errno == EINTR)
                continue;

            ALOGE("***** ERROR! android_os_fileobserver_observe() got a short event!");
            return;
        }

        //處理事件數組
        while (num_bytes >= (int)sizeof(*event))
        {
            int event_size;
            event = (struct inotify_event *)(event_buf + event_pos);

            jstring path = NULL;

            if (event->len > 0)
            {
                path = env->NewStringUTF(event->name);
            }

           //調用ObserverThread中的onEvent方法 
            env->CallVoidMethod(object, method_onEvent, event->wd, event->mask, path);
            if (env->ExceptionCheck()) {
                env->ExceptionDescribe();
                env->ExceptionClear();
            }
            if (path != NULL)
            {
                env->DeleteLocalRef(path);
            }

            event_size = sizeof(*event) + event->len;
            num_bytes -= event_size;
            event_pos += event_size;
        }
    }

#endif
}

不難看出,此處的循環主要就是從inotity中取出事件,然後回調ObserverThread中的onEvent()方法.現在,回到ObserverThread中的onEvent()方法中:

public void onEvent(int wfd, int mask, String path) {
            // look up our observer, fixing up the map if necessary...
            FileObserver observer = null;

            synchronized (m_observers) {
                //根據wfd找出FileObserver對象
                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) {//observer已經被回收時,從m_observers中刪除該對象
                        m_observers.remove(wfd);
                    }
                }
            }

            // ...then call out to the observer without the sync lock held
            if (observer != null) {
                try {
                    //回調給FileObserver中的onEvent方法進行處理
                    observer.onEvent(mask, path);
                } catch (Throwable throwable) {
                    Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable);
                }
            }
        }

FileObserver中的onEvent()爲抽象方法,也就是要求你繼承FileObserver,並實現該方法,在其中做相關的操作.
到現在爲止我們已經明白了ObserverThread如何被啓動,以及如何獲取inotify中的事件,並回調給上層進行處理.

啓動監控

上面提到m_observers表,該表維護着已經註冊的FileObserver對象.接下來,我們就就來看看FileObserver中的startWatching()方法,該方法註冊FileObserver對象,也是啓動監控的過程:

public void startWatching() {
        if (m_descriptor < 0) {
            m_descriptor = s_observerThread.startWatching(m_path, m_mask, this);
        }
    }

具體的註冊操作委託給s_observerThread中的startWatching():

public int startWatching(String path, int mask, FileObserver observer) {
            //調用native方法startWatching,並得到一個watch對象的句柄
            int wfd = startWatching(m_fd, path, mask);

            Integer i = new Integer(wfd);
            if (wfd >= 0) {
                synchronized (m_observers) {
                    //將watch對象句柄和當前FileObserver關聯
                    m_observers.put(i, new WeakReference(observer));
                }
            }

            return i;
        }

該方法中同樣調用了native方法,其具體實現是:

static jint android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd, jstring pathString, jint mask)
{
    int res = -1;
#if defined(__linux__)
    if (fd >= 0)
    {
        const char* path = env->GetStringUTFChars(pathString, NULL);

        res = inotify_add_watch(fd, path, mask);

        env->ReleaseStringUTFChars(pathString, path);
    }
#endif
    return res;
}

不難看出,這裏通過inotify的inotify_add_watch()爲上面生成的inotify對象添加watch對象,並將watch對象的句柄返回給ObserverThread.

停止監控

到現在我們已經瞭解瞭如何註冊watch句柄到FileObserver對象.有了註冊的過程,當然少不了反註冊的過程.同樣,FileObserver爲我們提供了stopWatching()來實現反註冊,即停止監控的過程:

 public void stopWatching() {
        if (m_descriptor >= 0) {//已經註冊過的才能反註冊
            s_observerThread.stopWatching(m_descriptor);
            m_descriptor = -1;
        }
    }

具體的實現也是交給了s_observerThread的stopWatching()方法:

public void stopWatching(int descriptor) {
            stopWatching(m_fd, descriptor);
        }

接着委託給了natvie方法:

static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object, jint fd, jint wfd)
{
#if defined(__linux__)
    inotify_rm_watch((int)fd, (uint32_t)wfd);
#endif
}

這裏的實現非常簡單,就是調用inotify_rm_watch方法來解除inotify實例和watch實例的關係.

到現在爲止我們已經弄明白了FileObserver的實現原理,爲了方便理解,我們用一張簡單的圖來描述整個過程:
這裏寫圖片描述


使用說明

想必你已經瞭解了FileObserver的實現原理,接下里我們來看看如何使用.要想實現文件監控,我們只需要繼承FileObserver類,並在onEvent()處理相關事件即可,簡單的用代碼演示一下:

public class SDCardObserver extends FileObserver {
    public SDCardObserver(String path) {
        super(path);
    }

    @Override
    public void onEvent(int i, String s) {
        switch (i) {
            case FileObserver.ALL_EVENTS:
                //全部事件
                break;
            case FileObserver.CREATE:
                //文件被創建
                break;
            case FileObserver.DELETE:
                //文件被刪除
                break;
            case FileObserver.MODIFY:
                //文件被修改
                break;
        }
    }
}

注意事項

這裏我們需要注意兩個問題:
1. 謹慎的選擇你要處理的事件,否則可能造成死循環.
2. 當不再需要監聽時,請記得停止監控
3. 需要注意FileObserver對象被垃圾回收的情況,從上面的原理中我們知道,該對象被回收後將不會再觸發事件.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章