您的位置:首页 > 其它

FileObserver 研究及其递归监听初步实现

2016-08-15 17:22 302 查看
前段时间做如数家珍这个 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 类:

packagecom.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/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: