前段时间做如数家珍这个 Android应用的时候,为了保证软件数据库和SD卡上的实际文件系统同步,需要监听SD卡上的文件变化。苦于不熟悉 linux机制,埋头想了许多办法,Google数次无果,最后无奈选用了一个笨方法:检测SD卡剩余空间是否有变动,若有变动表明数据库过期,下次进入软件进行全盘扫描。这个方式明显是非常不精确的,但是也有其独特的优点,检测过程消耗小且不占用系统资源;致命伤是若有变动只能依靠全盘扫描来寻找改动。目前我的SD 卡文件数已达到 4000+,全盘扫描这个过程还是要消耗数秒,对一个主打实时搜索的软件来说无疑是致命的。
这个问题长期以来一直挂在心里放不下,经过旷日持久的搜索关键词大战,终于在 linux系统发现了线索,inotify这个库可以监听文件系统的改动。于是大喜,想查查怎么通过 JNI 在 Android里实现这个功能,意外发现 Android 自从 API Level1 就已经有了 FileObserver 这个类了,位于android.os 包中,基于 linux 的 inotify实现监听文件系统操作,包括访问、创建、修改、删除、移动、关闭等操作。
FileObserver 这个类很少有资料提到,doc里也没有过多的提及,要命的是其 doc 内容是还是错误的,当然这是后话。FileObserver 是个抽象类,必须继承并重写onEvent 事件处理方法。
Each FileObserver instancemonitors a single file or directory. If a directory is monitored,events will be triggered for all files and subdirectories(recursively) inside the monitored directory.
每个 FileObserver对象监听一个单独的文件或者目录,如果监视的是一个目录,那么此目录下所有的文件的改变都会触发监听的事件。为什么要把英文原文贴上来,因为doc 里明确写出了 recursively,但是经过测试并不支持递归,对于监听目录的子目录中的文件改动,FileObserver对象是无法收到事件回调的,不仅这样,监听目录的子目录本身的变动也收不到事件回调。大概调查了一下,这是由 linux 的 inotify机制本身决定的,基于 inotify 实现的 FileObserver 自然也不支持递归监听。
为了监听整个文件系统的变化,必须得实现这个递归监听,怎么办呢?先查查大家是怎么用inotify 实现递归监听的,搜索结果说明了一切,对每个子目录递归的调用 inotify,也就是说在 Android中,也得通过遍历目录树,建立一系列 FileObserver对象来实现这个功能,很笨吧。本来很担心这样做对系统的效率影响极大,但是大家都是这么实现的,我也本着实践出真知的原则,写下了如下的RecursiveFileObserver 类:
package com . toraleap . testprj;import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Stack;
import android.os.FileObserver; import android.util.Log;
publicclassRecursiveFileObserverextendsFileObserver { publicstaticintCHANGES_ONLY = CREATE | DELETE | CLOSE_WRITE | MOVE_SELF | MOVED_FROM |MOVED_TO;
List mObservers; String mPath; intmMask;
publicRecursiveFileObserver(String path) { this(path,ALL_EVENTS); }
publicRecursiveFileObserver(String path, int mask) { super(path,mask); mPath = path; mMask = mask; }
@Override publicvoidstartWatching() { if(mObservers != null) return ;
mObservers= new ArrayList(); Stack stack = new Stack(); stack.push(mPath);
while(!stack.isEmpty()) { String parent = stack.pop(); mObservers.add(new SingleFileObserver(parent, mMask)); File path = new File(parent); File[]files = path.listFiles(); if(null== files) continue; for(File f: files) { if(f.isDirectory() &&!f.getName().equals(".") &&!f.getName() .equals("..")) { stack.push(f.getPath()); } } }
for(SingleFileObserversfo: mObservers) { sfo.startWatching(); } }
@Override publicvoidstopWatching() { if(mObservers == null) return ;
for(SingleFi leObserver sfo:mObservers) { sfo.stopWatching(); } mObservers.clear(); mObservers = null; }
@Override publicvoidonEvent(int event, String path) { switch(event) { caseFileObserver.ACCESS: Log.i("RecursiveFileObserver", "ACCESS: " + path); break; caseFileObserver.ATTRIB: Log.i("RecursiveFileObserver", "ATTRIB: " + path); break; caseFileObserver.CLOSE_NOWRITE: Log.i("RecursiveFileObserver", "CLOSE_NOWRITE: " + path); break; caseFileObserver.CLOSE_WRITE: Log.i("RecursiveFileObserver", "CLOSE_WRITE: " + path); break; caseFileObserver.CREATE: Log.i("RecursiveFileObserver", "CREATE: " + path); break; caseFileObserver.DELETE: Log.i("RecursiveFileObserver", "DELETE: " + path); break; caseFileObserver.DELETE_SELF: Logan>.i("RecursiveFileObserver", "DELETE_SELF: " + path); break; caseFileObserver.MODIFY: Log.i("RecursiveFileObserver", "MODIFY: " + path); break; caseFileObserver.MOVE_SELF: Log.i("RecursiveFileObserver", "MOVE_SELF: " + path); break; caseFileObserver.MOVED_FROM: Log.i("RecursiveFileObserver", "MOVED_FROM: " + path); break; caseFileObserver.MOVED_TO: Log.i("RecursiveFileObserver", "MOVED_TO: " + path); break; caseFileObserver.OPEN: Log.i("RecursiveFileObserver", "OPEN: " + path); break; default: Log.i("RecursiveFileObserver", "DEFAULT(" + event + "): " + path); break; } }
classSingleFileObserverextendsFileObserver { String mPath;
publicSingleFileObserver(String path) style="color: rgb(0,0,0)">{ this(path,ALL_EVENTS); mPath = path; }
publicSingleFileObserver(String path, int mask) { super(path,mask); mPath = path; }
@Override publicvoidonEvent(int event, String path) { String newPath = mPath + "/" + path; RecursiveFileObserver.this.onEvent(event, newPath); } } }
虽然 RecursiveFileObserver类的行为是管理一系列子对象,其整体行为完全和 FileObserver 不同,为了保持用法尽量与 FileObserver一致而采用了继承这个抽象类,其实如果可能,这里应该是实现接口的。FileObserver类并没有在构造函数中进行耗费资源的操作,这里用继承还是可以接受的。新增加了一个掩码常数CHANGES_ONLY,仅监控会导致文件系统发生变化的事件。m_path 和 m_mask 两个成员变量在父类是 private访问权限,而不得不自己用两个成员变量保存构造函数中传入的这两个信息。
在 HTC Desire 实机上运行测试,递归搜索共监听898 个目录,也就是起初的遍历目录树很消耗时间,运行监听过程基本感觉不到有附加延迟。这里为了测试方便没有把 onEvent标记为抽象方法,实际使用方法和 FileObserver 相同,从 RecursiveFileObserver 类派生,重写onEvent 方法即可。如果 RecursiveFileObserver对象被垃圾回收了,将不再引发事件,因此必须保持对此对象的一个引用,通常是在成员变量中。onEvent方法是在对象内的一个特别的线程上调用的,其中的代码得考虑同步的问题,并且不能抛出任何异常。
遗留问题:FileObserver无法监听目录的建立和删除操作,导致在监听目录中创建的新目录无法被监控到,这还是一个很大的遗留问题,这一点等寻找到解决方案之后再作分解吧。
原文出处:http://blog.toraleap.com/articles/recursive_file_observer/
