最近在开发过程中出现了下面的场景:主程序控制着数据文件的更新逻辑,但是文件的更新动作并没有及时通知插件数据文件更新了。在插件中为了兼顾性能,往往会对数据文件在内存中进行缓从而导致数据文件无法实时刷新的情况。为了解决这种情况需要找到一种高效、实时的数据文件刷新解决方案。
FileObserver介绍
inotify机制是从linux kernel 2.6.13开始引入,Android 1.5对应的linux内核已经是2.6.26了。因此完全可以在Android上利用inotify达到反调试的目的。而且Android将inotify直接封装成了FileObserver类,可以直接在Java代码中使用。当然在jni中自己调用inotify也是很容易的。
FileObserver的使用方法
想要在Java层使用FileObserver,必须先继承FileObserver类,实现其onEvent()函数。
private class SingleFileObserver extends FileObserver{
//......
@Override
public void onEvent(int i, String s) {
//.....
}
}
通过**startWatching()**开始监视,**stopWatching()**停止监视。
考虑到FileObserver不支持子目录的递归,将FileObserver封装了一层,以达到可以递归监视的目的。
public class RecursiveFileObserver{
private ArrayList<SingleFileObserver> mSingleObservers = new ArrayList<SingleFileObserver>();
public RecursiveFileObserver(String path){
//解析子目录
Stack<String> pathStack = new Stack<String>();
pathStack.push(path);
while (!pathStack.isEmpty()){
String parentPath = pathStack.pop();
if (mSingleObservers.add(new SingleFileObserver(parentPath))){
Log.d("C&C","add observer success"+parentPath);
}
File parent = new File(parentPath);
if (parent.isDirectory()){
File[] files = parent.listFiles();
for (int i =0;i<files.length;i++){
File f = files[i];
if (f.isDirectory() &&
(f.getName().equals(".") || f.getName().equals(".."))){
//跳过 "." ".." 目录
}else {
pathStack.push(f.toString());
//pathStack.push(f.getAbsolutePath());
Log.d("C&C","file list:"+f.toString());
}
}
}
}
}
public void startWatching() {
for (int i = 0;i<mSingleObservers.size();i++){
mSingleObservers.get(i).startWatching();
}
}
public void stopWatching() {
for (int i = 0;i<mSingleObservers.size();i++){
mSingleObservers.get(i).stopWatching();
}
}
private class SingleFileObserver extends FileObserver{
protected String mPath ;
protected int mMask;
public static final int DEFAULT_MASK = CREATE | MODIFY | DELETE;
public SingleFileObserver(String path){
this(path , DEFAULT_MASK);
}
public SingleFileObserver(String path , int mask){
super(path , mask);
mPath = path;
mMask = mask;
}
@Override
public void onEvent(int i, String s) {
int event = i&FileObserver.ALL_EVENTS;
switch (event){
case MODIFY:
//查看是否被调试
if (isDebugged(s)){
Log.d("C&C","is debugged");
}
}
}
}
}
需要注意的是在使用FileObserver的时候需要提前申请号权限,不然会导致无法监控相关事件
FileObserver的实现原理
Android已经将linux下的inotify机制封装成了FileObserver抽象类,必须继承FileObserver类才能使用。
android.os.FileObserver
Monitors files (using inotify) to fire an event after files are accessed or changed by by any process on the device (including this one). FileObserver is an abstract class; subclasses must implement the event handler onEvent(int, String).
Each FileObserver instance monitors a single file or directory. If a directory is monitored, events will be triggered for all files and subdirectories inside the monitored directory.
An event mask is used to specify which changes or actions to report. Event type constants are used to describe the possible changes in the event mask as well as what actually happened in event callbacks.
Android sdk的官方文档说的是监视一个目录,则该目录下所有的文件和子目录的改变都会触发监听的事件。经过测试,其实对于监听目录的子目录的文件改动,FileObserver对象是无法接收到事件回调的。
FileObserver可以监听的类型:
name | descrption | tips |
---|---|---|
ACCESS | Data was read from a file,访问文件 | |
MODIFY | Data was written to a file,修改文件 | |
ATTRIB | Metadata (permissions, owner, timestamp) was changed explicitly,修改文件属性 | 如chmod、chown等 |
CLOSE_WRITE | Someone had a file or directory open for writing, and closed it,以可写属性打开的文件被关闭 | |
CLOSE_NOWRITE | Someone had a file or directory open read-only, and closed it,以不可写属性被打开的文件被关闭 | |
OPEN | A file or directory was opened,文件被打开 | |
MOVED_FROM | A file or subdirectory was moved from the monitored directory,文件被移走 | 如mv |
MOVED_TO | A file or subdirectory was moved to the monitored directory,移入新文件 | 如mv、cp |
CREATE | A new file or subdirectory was created under the monitored directory,文件被创建 | |
DELETE | A file was deleted from the monitored directory,文件被删除 | 如rm |
DELETE_SELF | The monitored file or directory was deleted; monitoring effectively stops,自删除,监听的对象被删除,监听会自动停止 | |
MOVE_SELF | The monitored file or directory was moved; monitoring continues,自移动,被监听的对象发生了移动,监听会继续保持 | |
ALL_EVENTS | All valid event types, combined,上述的所有事件 |
初始化
在FileObserver类中封装有一个static的线程类,并且在静态代码块中直接构造出来。
static {
s_observerThread = new ObserverThread();
s_observerThread.start();
}
private static class ObserverThread extends Thread {
...
public ObserverThread() {
super("FileObserver");
m_fd = init();
}
public void run() {
observe(m_fd);
}
public int startWatching(String path, int mask, FileObserver observer) {
int wfd = startWatching(m_fd, path, mask);
...
}
...
private native int init();
private native void observe(int fd);
private native int startWatching(int fd, String path, int mask);
private native void stopWatching(int fd, int wfd);
}
在这个线程的初始化代码中的init()方法调用了native方法init()。
FileObserver的cpp源码位置是/frameworks/base/core/jni/android_util_FileObserver.cpp
static jint android_os_fileobserver_init(JNIEnv* env, jobject object)
{
return (jint)inotify_init();
}
可以看到,java层的初始化实际上只是简单调用了inotify_init(),用来初始化inotify。
监控逻辑
在构造了ObserverThread()后,还调用了它的start方法,执行了observe(m_fd),这个方法也是个native方法,具体实现如下:
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);
}
//调用java层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);
}
//指向下一个inotify_event结构
event_size = sizeof(*event) + event->len;
num_bytes -= event_size;
event_pos += event_size;
}
}
#endif
}
inotify_event的结构说明:
struct inotify_event {
__s32 wd; /* watch descriptor */
__u32 mask; /* watch mask */
__u32 cookie; /* cookie to synchronize two events */
__u32 len; /* length (including nulls) of name */
char name[0]; /* stub for possible name */
};
wd: 被监视目标的 watch 描述符
mask : 事件掩码
name: 被监视目标的路径名,文件名被0填充,使得下一个事件结构能够以4字节对齐
len : name字符串的长度
调用的onEvent()方法:
在FileObserver的run方法中会调用上述的这个在c++中循环执行的监控逻辑。
不停地读取inotify的消息,并做解析,如果是合法的消息就会调用java方法的onEvent进行通知。
监控的启停
开始监控:使用要先调用FileObserver.startWatching()—>ObserverThread.startWatching()—>linux( inotify_add_watch() )
停止监控:FileObserver.stopWatching() --> ObserverThread.stopWatching()—> linux( inotify_rm_watch() )
FileOberserver的优化
- WeakReference导致FileObserver被释放
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) {
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) {
m_observers.remove(wfd);
}
}
}
...
}
android虚拟机演变gc对内存的处理更加激进。WeakReference甚至是SoftReference都很容易让FileObserver被回收。
- jni中没有及时close fd
FileObserver的jni层打开inotify获取到fd后,没有任何FileObserver也不会主动退出监听,这是因为jni层设计没有考虑close fd
inotify_add_watch返回并不是一个fd,而只是一个标识
对于同一个path,inotify_add_watch将返回相同的标识。不需要调用close关闭,但需要调用inotify_rm_watch来删除。
在对文件进行读、写、关闭监控时需要注意这个特性。但inotify_init返回的是一个真正的fd,因此需要调用close关闭它。
可以新增jni方法
static JNICALL void release(JNIEnv *env, jobject object, int fd) {
if (fd >= 0)close(fd);
}
public void stopWatching(int descriptor) {
stopWatching(m_fd, descriptor);
synchronized (m_observers) {
m_observers.remove(Integer.valueOf(descriptor));
if (m_observers.isEmpty()) {
release(m_fd);
m_fd = -1;
}
}
}
这样可以有效保证关闭了fd。