android FileObserver实战使用案例
首先继承自定义一个DataFileObserver
class DataFileObserver extends FileObserver {
public DataFileObserver(String path) {
super(path,CLOSE_WRITE | MOVED_TO | DELETE | DELETE_SELF);
}
@Override
public void onEvent(int event, @Nullable String path) {
//一旦监测文件有啥变化都会调到这里
Log.i("test","onEvent "+path + " event = " +event);
}
}
然后再进行启动观察
DataFileObserver mFileObserver = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mFileObserver == null) {
mFileObserver = new DataFileObserver("/data/local/tmp/aaa.txt");
mFileObserver.startWatching();
}
//省略其他
上面使用FileObserver监听某个文件非常简单,只需要以下几步既可以:
1、自定义一个DataFileObserver继承FileObserver类,重写onEvent方法,一旦有文件相关变化时候就会回调这个方法
2、构造出自定义的DataFileObserver类,注意参数就是需要监测的文件或者目录
3、调用FileObserver的startWatching方法启动对文件监测
4、当然在不需要监测时候需要调用stopWatching
源码剖析FileObserver原理:
先看看官方注释:
frameworks/base/core/java/android/os/FileObserver.java
android.os public abstract class FileObserver
extends Object
Monitors files (using inotify ) to fire an event after files are accessed or changed 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 can monitor multiple files or directories. 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.
Warning: If a FileObserver is garbage collected, it will stop sending events. To ensure you keep receiving events, you must keep a reference to the FileObserver instance from some other live object.
上面信息可以看出,FileObserver其实底层原理是使用的inotify来进行文件监听,监听的文件变化类型有以下:
/** Event type: Data was read from a file */
public static final int ACCESS = 0x00000001;
/** Event type: Data was written to a file */
public static final int MODIFY = 0x00000002;
/** Event type: Metadata (permissions, owner, timestamp) was changed explicitly */
public static final int ATTRIB = 0x00000004;
/** Event type: Someone had a file or directory open for writing, and closed it */
public static final int CLOSE_WRITE = 0x00000008;
/** Event type: Someone had a file or directory open read-only, and closed it */
public static final int CLOSE_NOWRITE = 0x00000010;
/** Event type: A file or directory was opened */
public static final int OPEN = 0x00000020;
/** Event type: A file or subdirectory was moved from the monitored directory */
public static final int MOVED_FROM = 0x00000040;
/** Event type: A file or subdirectory was moved to the monitored directory */
public static final int MOVED_TO = 0x00000080;
/** Event type: A new file or subdirectory was created under the monitored directory */
public static final int CREATE = 0x00000100;
/** Event type: A file was deleted from the monitored directory */
public static final int DELETE = 0x00000200;
/** Event type: The monitored file or directory was deleted; monitoring effectively stops */
public static final int DELETE_SELF = 0x00000400;
/** Event type: The monitored file or directory was moved; monitoring continues */
public static final int MOVE_SELF = 0x00000800;
/** Event mask: All valid event types, combined */
@NotifyEventType
public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE
| CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE
| DELETE_SELF | MOVE_SELF;
初始化
在我们构造对应的FileObserver最后会调用到下面
/**
* Equivalent to FileObserver(path, FileObserver.ALL_EVENTS).
*
* @deprecated use {@link #FileObserver(File)} instead.
*/
@Deprecated
public FileObserver(String path) {
this(new File(path));
}
/**
* Version of {@link #FileObserver(File, int)} that allows callers to monitor
* multiple files or directories.
*
* @param files The files or directories to monitor
* @param mask The event or events (added together) to watch for
*/
public FileObserver(@NonNull List<File> files, @NotifyEventType int mask) {
mFiles = files;
mMask = mask;
}
再看看调用startWatching方法
/**
* Start watching for events. The monitored file or directory must exist at
* this time, or else no events will be reported (even if it appears later).
* If monitoring is already started, this call has no effect.
*/
public void startWatching() {
if (mDescriptors == null) {
mDescriptors = s_observerThread.startWatching(mFiles, mMask, this);
}
}
这里可以看到核心其实是调用了s_observerThread的,那么s_observerThread是哪里来的呢?
在FileObserver类中封装有一个static的线程类,并且在静态代码块中直接构造出来。
private static ObserverThread s_observerThread;
static {
s_observerThread = new ObserverThread();
s_observerThread.start();
}
也就静态构造的ObserverThread,那么看看ObserverThread类实现
private static class ObserverThread extends Thread {
/** Temporarily retained; appears to be missing UnsupportedAppUsage annotation */
private HashMap<Integer, WeakReference> m_observers = new HashMap<Integer, WeakReference>();
private SparseArray<WeakReference> mRealObservers = new SparseArray<>();
private int m_fd;
public ObserverThread() {
super("FileObserver");
m_fd = init();//调用到了native的init方法获取fd
}
public void run() {
observe(m_fd);//调用native的observe同时传递fd
}
public int[] startWatching(List<File> files,
@NotifyEventType int mask, FileObserver observer) {
final int count = files.size();
final String[] paths = new String[count];
for (int i = 0; i < count; ++i) {
paths[i] = files.get(i).getAbsolutePath();
}
final int[] wfds = new int[count];
Arrays.fill(wfds, -1);
startWatching(m_fd, paths, mask, wfds);//最后也是调用到了native的startWatching
final WeakReference<FileObserver> fileObserverWeakReference =
new WeakReference<>(observer);
synchronized (mRealObservers) {
for (int wfd : wfds) {
if (wfd >= 0) {
mRealObservers.put(wfd, fileObserverWeakReference);
}
}
}
return wfds;
}
public void stopWatching(int[] descriptors) {
stopWatching(m_fd, descriptors);
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void onEvent(int wfd, @NotifyEventType int mask, String path) {
// look up our observer, fixing up the map if necessary...
FileObserver observer = null;
synchronized (mRealObservers) {
WeakReference weak = mRealObservers.get(wfd);
if (weak != null) { // can happen with lots of events from a dead wfd
observer = (FileObserver) weak.get();
if (observer == null) {
mRealObservers.remove(wfd);
}
}
}
// ...then call out to the observer without the sync lock held
if (observer != null) {
try {
observer.onEvent(mask, path);//这里就是回调到FileObserve的onEvent
} catch (Throwable throwable) {
Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable);
}
}
}
//核心的native方法
private native int init();
private native void observe(int fd);
private native void startWatching(int fd, String[] paths,
@NotifyEventType int mask, int[] wfds);
private native void stopWatching(int fd, int[] wfds);
}
上面原理主要方法也有注释,这里可以看出ObserverThread主要还是一个线程类,主要核心业务方法都是调用到了native层面。
主要针对以下几个native方法:
private native int init();
private native void observe(int fd);
private native void startWatching(int fd, String[] paths,
@NotifyEventType int mask, int[] wfds);
private native void stopWatching(int fd, int[] wfds);
下面看看native的代码:
init方法
static jint android_os_fileobserver_init(JNIEnv* env, jobject object)
{
#if defined(__linux__)
return (jint)inotify_init1(IN_CLOEXEC);//可以看出调用实际上是inotify_init1
#else
return -1;
#endif
}
observe
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)
{
//读取inotify的对应fd的数据,在监测文件有变化时候就可以读取到
int num_bytes = read(fd, event_buf, sizeof(event_buf));
while (num_bytes >= (int)sizeof(*event))
{
//读取event数据后回调到java层
env->CallVoidMethod(object, method_onEvent, event->wd, event->mask, path);
}
}
#endif
}
startWatching方法
static void android_os_fileobserver_startWatching(JNIEnv* env, jobject object, jint fd,
jobjectArray pathStrings, jint mask,
jintArray wfdArray)
{
//这里核心就是调用了inotify_add_watch方法,监测path和指定对应mask
wfds[i] = inotify_add_watch(fd, path.c_str(), mask);
}
stopWatching方法
static void android_os_fileobserver_stopWatching(JNIEnv* env, jobject object,
jint fd, jintArray wfdArray)
{
//调用是inotify_rm_watch,移除监听
inotify_rm_watch((int)fd, (uint32_t)wfds[i]);
}
上面可以看出FileObserver本质都是调用的linux相关inotify接口,下面针对linux的inotify监听文件变化采用一个demo实战案例方便大家立即。
linux层面的inotify实战案例:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/inotify.h>
#include <limits.h>
#include <unistd.h>
int main() {
int inotifyFd, watchDescriptor;
int length, i = 0;
char event_buf[1024] __attribute__ ((aligned(8)));
// 初始化 inotify
inotifyFd = inotify_init();
if (inotifyFd < 0) {
perror("inotify_init");
return 1;
}
// 添加监控,这里监控当前目录(".")的变化
watchDescriptor = inotify_add_watch(inotifyFd, "/home/test/disk2/demos/inotify",
IN_ACCESS|IN_ATTRIB|IN_CLOSE_WRITE|IN_CLOSE_NOWRITE|IN_MODIFY | IN_CREATE |IN_MOVE_SELF|IN_MOVED_FROM|IN_MOVED_TO| IN_DELETE|IN_OPEN);
if (watchDescriptor < 0) {
perror("inotify_add_watch");
close(inotifyFd);
return 1;
}
struct inotify_event* event;
// 循环读取事件
while (i < 10) { // 示例中只处理10个事件,实际应用中可以根据需要调整或使用循环
i++;
int event_pos = 0;
fflush(stdout);
int num_bytes = read(inotifyFd, event_buf, sizeof(event_buf));
printf("read num_bytes = %d \n", num_bytes);
fflush(stdout);
if (num_bytes < (int)sizeof(*event))
{
if (errno == EINTR)
continue;
}
while (num_bytes >= (int)sizeof(*event))
{
int event_size;
event = (struct inotify_event *)(event_buf + event_pos);
printf("event name = %s mask %d \n", event->name,event->mask);
fflush(stdout);
event_size = sizeof(*event) + event->len;
num_bytes -= event_size;
event_pos += event_size;
}
i = 0; // 重置计数器以处理所有事件
}
// 移除监控
inotify_rm_watch(inotifyFd, watchDescriptor);
close(inotifyFd);
return 0;
}
可以看得出linux的inotify使用也是一样的流程:
测试结果如下:
编译执行inotify程序
gcc inotify.cpp -o inotify;
./inotify
另一个终端在当前目录创建一个文件
test@test:~/disk2/demos/inotify$ touch 3.txt
再看看./inotify执行终端的打印
read num_bytes = 64
event name = 3.txt mask 256
event name = 3.txt mask 32
read num_bytes = 64
event name = 3.txt mask 4
event name = 3.txt mask 8
这些mask对应变化是啥,可以用上面java文件FileObserver的来匹配,也可以用通过查linux的函数手册来看看:
man inotify
inotify events
The inotify_add_watch(2) mask argument and the mask field of the inotify_event structure returned when read(2)ing an inotify file descriptor are both bit masks identifying inotify events.
The following bits can be specified in mask when calling inotify_add_watch(2) and may be returned in the mask field returned by read(2):
IN_ACCESS (+)
File was accessed (e.g., read(2), execve(2)).
IN_ATTRIB (*)
Metadata changed—for example, permissions (e.g., chmod(2)), timestamps (e.g., utimensat(2)), extended attributes (setxattr(2)), link count (since Linux 2.6.25; e.g., for the tar‐
get of link(2) and for unlink(2)), and user/group ID (e.g., chown(2)).
IN_CLOSE_WRITE (+)
File opened for writing was closed.
IN_CLOSE_NOWRITE (*)
File or directory not opened for writing was closed.
IN_CREATE (+)
File/directory created in watched directory (e.g., open(2) O_CREAT, mkdir(2), link(2), symlink(2), bind(2) on a UNIX domain socket).
IN_DELETE (+)
File/directory deleted from watched directory.
IN_DELETE_SELF
Watched file/directory was itself deleted. (This event also occurs if an object is moved to another filesystem, since mv(1) in effect copies the file to the other filesystem and
then deletes it from the original filesystem.) In addition, an IN_IGNORED event will subsequently be generated for the watch descriptor.
IN_MODIFY (+)
File was modified (e.g., write(2), truncate(2)).
IN_MOVE_SELF
Watched file/directory was itself moved.
IN_MOVED_FROM (+)
Generated for the directory containing the old filename when a file is renamed.
更多framework实战开发,关注下面“千里马学框架”