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

前段时间做如数家珍这个 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访问权限,而不得不自己用两个成员变量保存构造函数中传入的这两个信息。

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

在 HTC Desire 实机上运行测试,递归搜索共监听898 个目录,也就是起初的遍历目录树很消耗时间,运行监听过程基本感觉不到有附加延迟。这里为了测试方便没有把 onEvent标记为抽象方法,实际使用方法和 FileObserver 相同,从 RecursiveFileObserver 类派生,重写onEvent 方法即可。如果 RecursiveFileObserver对象被垃圾回收了,将不再引发事件,因此必须保持对此对象的一个引用,通常是在成员变量中。onEvent方法是在对象内的一个特别的线程上调用的,其中的代码得考虑同步的问题,并且不能抛出任何异常。

遗留问题:FileObserver无法监听目录的建立和删除操作,导致在监听目录中创建的新目录无法被监控到,这还是一个很大的遗留问题,这一点等寻找到解决方案之后再作分解吧。

原文出处:http://blog.toraleap.com/articles/recursive_file_observer/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值