Android(安卓)JNI实现fileObserver
Android JNI实现fileObserver记录
背景
前段时间需要一个文件监听的功能,网上查了一下,Android自带的有一个FileObserver类可以实现此功能,就准备使用它来实现,不知为何有的手机能用,有的手机不能用,而且还不支持递归的监听,所以打算通过jni来实现。
思路
Android 系统底层核心是linux大家都很清楚,linux系统中有一个叫inotify的东西,它是linux的一个特性,它监控文件系统操作,比如读取、写入和创建。Inotify 反应灵敏,用法非常简单,并且比 cron 任务的繁忙轮询高效得多,结合epoll来实现多个目录文件的监听。
注意
- inotify在linux 2.6.13 以上的内核才支持
- Android中 SDK >= 23 需要增加权限声明和动态权限申请。
inotify
-
摘自百度百科
1)Inotify 是一个 Linux 内核特性,它监控文件系统,并且及时向专门的应用程序发出相关的事件警告,比如删除、读、写和卸载操作等。您还可以跟踪活动的源头和目标等细节。2)使用 inotify 很简单:创建一个文件描述符,附加一个或多个监视器(一个监视器 是一个路径和一组事件),然后使用 read 方法从描述符获取事件。read 并不会用光整个周期,它在事件发生之前是被阻塞的。
3)更好的是,因为 inotify 通过传统的文件描述符工作,您可以利用传统的 select 系统调用来被动地监控监视器和许多其他输入源。两种方法 — 阻塞文件描述符和使用 select— 都避免了繁忙轮询。
-
API
/*** 创建用于创建一个inotify的实例,然后返回inotify事件队列的文件描述符。 * 同样内核也提供了inotify_init1(int flags)接口函数,当flag等于0的时候,* 该函数等价于inotify_init(void)函数。*/**int inotify_init(void);**/**** 该函数用于添加“watch list”,也就是检测列表。 可以是一个新的watch,* 也可以是一个已经存在的watch。其中fd就是inotify_init的返回值,* pathname是要检测目录或者文件的路径,mask就是要检测的事件类型。* 该函数成功返回的是一个unique的watch描述符* IN_ACCESS File was accessed (read) (*). * IN_ATTRIB Metadata changed, e.g., permissions, timestamps, extended * attributes, link count (since Linux 2.6.25), UID, GID, etc.(*). * IN_CLOSE_WRITE File opened for writing was closed (*). * IN_CLOSE_NOWRITE File not opened for writing was closed (*). * IN_CREATE File/directory created in watched directory (*). * IN_DELETE File/directory deleted from watched directory (*). * IN_DELETE_SELF Watched file/directory was itself deleted. * IN_MODIFY File was modified (*). * IN_MOVE_SELF Watched file/directory was itself moved. * IN_MOVED_FROM File moved out of watched directory (*). * IN_MOVED_TO File moved into watched directory (*). * IN_OPEN File was opened (*).* */**inotify_add_watch(int fd, const char* pathname, uint32_t mask);**/*** 用于从watch list种移除检测的对象。*/inotify_rm_watch(int fd, int wd);/*** 监听的目标产生的事件结构*/struct inotify_event { int wd; /* Watch descriptor */ uint32_t mask; /* Mask of events */ uint32_t cookie; /* Unique cookie associating related events (for rename(2)) */ uint32_t len; /* Size of name field */ char name[]; /* Optional null-terminated name */ }; .wd: 就是检测的对象的watch descriptor.mask: 检测事件的mask.cookie: 和rename事件相关。.len: name字段的长度。.name: 检测对象的name。可以看到name字段的长度是0,也就是变长的。因为检测的对象的name不定,使用变长可以方便记录检测对象的name。
epoll
- 描述
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,下面说下我们在编程时epoll具体的用法。
(1)epoll_create系统调用,epoll_create在C库中的原型如下。
int epoll_create(int size);
epoll_create返回一个句柄,之后 epoll的使用都将依靠这个句柄来标识。参数 size是告诉 epoll所要处理的大致事件数目。不再使用 epoll时,必须调用 close关闭这个句柄。
注意:size参数只是告诉内核这个 epoll对象会处理的事件大致数目,而不是能够处理的事件的最大个数。在 Linux最新的一些内核版本的实现中,这个 size参数没有任何意义。
(2)epoll_ctl系统调用,epoll_ctl在C库中的原型如下。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
epoll_ctl向 epoll对象中添加、修改或者删除感兴趣的事件,返回0表示成功,否则返回–1,此时需要根据errno错误码判断错误类型。epoll_wait方法返回的事件必然是通过 epoll_ctl添加到 epoll中的。参数 epfd是 epoll_create返回的句柄,而op参数的意义见下表:
op的取值 | 意义 |
---|---|
EPOLL_CTL_ADD | 添加新的事件到epoll中 |
EPOLL_CTL_MOD | 修改epoll中的事件 |
EPOLL_CTL_DEL | 删除epoll中的事件 |
第3个参数 fd是待监测的连接套接字,第4个参数是在告诉 epoll对什么样的事件感兴趣,它使用了 epoll_event结构体,下面看一下 epoll_event的定义:
struct epoll_event{__uint32_t events;epoll_data_t data;};
目前先使用这两种
events取值 | 意义 |
---|---|
EPOLLIN | 当关联的文件可以执行 read ()操作时 |
EPOLLOUT | 当关联的文件可以执行 write ()操作时 |
epoll 事件 typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;} epoll_data_t;
(3)epoll_wait系统调用,epoll_wait在C库中的原型如下:
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);
收集在 epoll监控的事件中已经发生的事件,如果 epoll中没有任何一个事件发生,则最多等待timeout毫秒后返回。epoll_wait的返回值表示当前发生的事件个数,如果返回0,则表示本次调用中没有事件发生,如果返回–1,则表示出现错误,需要检查 errno错误码判断错误类型。
第1个参数 epfd是 epoll的描述符。
第2个参数 events则是分配好的 epoll_event结构体数组,epoll将会把发生的事件复制到 events数组中(events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存。内核这种做法效率很高)。
第3个参数 maxevents表示本次可以返回的最大事件数目,通常 maxevents参数与预分配的events数组的大小是相等的。
第4个参数 timeout表示在没有检测到事件发生时最多等待的时间(单位为毫秒),如果 timeout为0,则表示 epoll_wait在 rdllist链表中为空,立刻返回,不会等待。
主要代码实现
#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include#define TAG "fileobserver"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型#define MAX_FILES 1000#define EPOLL_COUNT 1000#define MAXCOUNT 500jclass gl_class; /*类*/JavaVM *gl_jvm; /*java虚拟机*/jobject gl_object; /*引用类型*/int RUN = 1;char *pathName[4096] = {NULL}; //save fd--->path nameint inotifyWd[4096] = {-1}; //保存 inotify_add_watch 返回值,删除的时候需要char monitorPath[1024]={0}; //用来保存监听的目录static char *epoll_files[MAX_FILES];static struct epoll_event mPendingEventItems[EPOLL_COUNT];int mINotifyFd,mEpollFd,i;char inotifyBuf[MAXCOUNT];char epollBuf[MAXCOUNT];typedef struct t_name_fd {int fd;char name[30];} T_name_fd;T_name_fd t_name_fd[100];int count_name_fd;int getfdFromName(char* name){int i;for(i=0; iGetStaticMethodID(env, cls, "CreateEvent", "(Ljava/lang/String;)V"); if (jmethodid == NULL) { LOGE("create event jmethodid == null"); return ; } jstring str = (*env)->NewStringUTF(env, path); (*env)->CallStaticVoidMethod(env, cls, jmethodid, str); /*delete local reference*/ (*env)->DeleteLocalRef(env, str);}/** * *delete event * */void DeleteEvent(JNIEnv *env, jclass cls,char *path){ jmethodID jmethodid = NULL; jmethodid = (*env)->GetStaticMethodID(env, cls, "DeleteEvent", "(Ljava/lang/String;)V"); if (jmethodid == NULL) { LOGE("delete event jmethodid == null"); return ; } jstring str = (*env)->NewStringUTF(env, path); (*env)->CallStaticVoidMethod(env, cls, jmethodid, str); /*delete local reference*/ (*env)->DeleteLocalRef(env, str);}struct inotify_event* curInotifyEvent;char name[30];int readCount = 0;int fd;void scan_dir(const char *dir, int depth) // 定义目录扫描函数{DIR *dp; // 定义子目录流指针struct dirent *entry; // 定义dirent结构指针保存后续目录struct stat statbuf; // 定义statbuf结构保存文件属性struct epoll_event eventItem; //epoll eventstruct inotify_event inotifyEvent;//inotify eventint lnotifyFD;int lwd; //inotify_add_watch 返回值char path[1024] = {0};if((dp = opendir(dir)) == NULL) // 打开目录,获取子目录流指针,判断操作是否成功{LOGE("can't open dir ------> %d\n", errno);return;}chdir (dir); // 切换到当前目录while((entry = readdir(dp)) != NULL) // 获取下一级目录信息,如果未否则循环{lstat(entry->d_name, &statbuf); // 获取下一级成员属性if(S_IFDIR &statbuf.st_mode) // 判断下一级成员是否是目录{if (strcmp(".", entry->d_name) == 0 || strcmp("..", entry->d_name) == 0)continue;//printf("%*s%s/\n", depth, "", entry->d_name); // 输出目录名称char *path1 = realpath("./", NULL);if (NULL != path1){lnotifyFD = inotify_init();sprintf(path, "%s/%s", path1, entry->d_name); //get absolute pathlwd = inotify_add_watch(lnotifyFD, path, IN_DELETE | IN_CREATE);//监听xxx目录下的 delete、create事件if (-1 == lwd){ LOGE("-1 == LWD\n");continue;}eventItem.events = EPOLLIN;eventItem.data.fd = lnotifyFD;epoll_ctl(mEpollFd, EPOLL_CTL_ADD, lnotifyFD, &eventItem); //add to epollif (lnotifyFD < 4096){char *p = malloc(strlen(path) + 1);memset(p, 0, strlen(path) + 1);if (NULL != p){memcpy(p, path, strlen(path));pathName[lnotifyFD] = p;inotifyWd[lnotifyFD] = lwd;}}free(path1);}scan_dir(entry->d_name, depth+4); // 递归调用自身,扫描下一级目录的内容}}chdir(".."); // 回到上级目录closedir(dp); // 关闭子目录流}char creatPath[1024] = {0};char deletePath[1024] = {0};int a = -1;int *fileObserver_init(const char *path){ int i = 0;struct epoll_event eventItem; //epoll eventstruct inotify_event inotifyEvent; //inotify eventJNIEnv *env;jmethodID jmethodid = NULL;if (gl_jvm == NULL) { LOGE("gl_jvm is NULL"); return (int *)-1; }(*gl_jvm)->AttachCurrentThread(gl_jvm, &env, NULL);//0. add sub dir inotifyif (NULL == path){ LOGE("path == null \n");a = -1;return &a;}mEpollFd = epoll_create(1000);// 1. init inotify & epollint homeINotifyFd = inotify_init();char *p = malloc(strlen(path));if (NULL == p){ LOGE("malloc failed = NULL \n");a = -1;return &a;}memset(p, 0, strlen(path));memcpy(p, path, strlen(path));pathName[homeINotifyFd] = p;//LOGE("pathName[homeINotifyFd] = %s\n", pathName[homeINotifyFd]);// 2.add inotify watch dirint lwd = inotify_add_watch(homeINotifyFd, pathName[homeINotifyFd], IN_DELETE | IN_CREATE);//监听xxx目录下的 delete、create事件if (-1 == lwd){ LOGE(" inotify_add_watch -------> errno = %d\n", errno);return &a;}inotifyWd[homeINotifyFd] = lwd;// 3. add inotify fd to epolleventItem.events = EPOLLIN;eventItem.data.fd = homeINotifyFd;epoll_ctl(mEpollFd, EPOLL_CTL_ADD, homeINotifyFd, &eventItem);scan_dir(path, 0);while(RUN){// 4.epoll检测文件的可读变化int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_COUNT, -1);for(i=0; i = sizeof(inotifyEvent)){if (curInotifyEvent->len > 0){if(curInotifyEvent->mask & IN_CREATE){if (pathName[mPendingEventItems[i].data.fd] != NULL){memset(creatPath, 0, sizeof(creatPath));sprintf(creatPath, "%s/%s", pathName[mPendingEventItems[i].data.fd], curInotifyEvent->name);//LOGE("create event path = %s\n", creatPath);CreateEvent(env, gl_class,creatPath);}else{LOGE("create name[mPendingEventItems[i].data.fd] == NULL\n");}}else if(curInotifyEvent->mask & IN_DELETE){if (pathName[mPendingEventItems[i].data.fd] != NULL){memset(deletePath, 0, sizeof(deletePath));sprintf(deletePath, "%s/%s", pathName[mPendingEventItems[i].data.fd], curInotifyEvent->name);//LOGE("delete event path = %s\n", deletePath);DeleteEvent(env, gl_class,deletePath);}else{LOGE("delete name[mPendingEventItems[i].data.fd] == NULL\n");}}}curInotifyEvent --;readCount -= sizeof(inotifyEvent);}}} (*gl_jvm)->DetachCurrentThread(gl_jvm); LOGE("退出");return 0;}/** * *释放 * */int FileObserverDestroy(){int i = 0;for (i = 0; i < 4096; i ++) //这里释放inotify的fd和申请的内存{if (pathName[i] != NULL){RUN = 0;free(pathName[i]);inotify_rm_watch(i, inotifyWd[i]);}}return 0;}pthread_t thread_1 = -1;int FileObserverInit(const char *path){if (-1 == thread_1){pthread_create(&thread_1, NULL, (void * (*)(void *))fileObserver_init, path);}return 0;}#ifdef __cplusplusextern "C" {#endif/* * Class: com_jiangc_fileobserver_FileObserverJni * Method: FileObserverInit * Signature: (Ljava/lang/String;)I */JNIEXPORT jint JNICALL Java_com_jiangc_receiver_FileObserverJni_FileObserverInit(JNIEnv *env, jclass clazz, jstring path) { const char *str = (*env)->GetStringUTFChars(env, path, 0); memset(monitorPath, 0, sizeof(monitorPath)); memcpy(monitorPath, str, strlen(str)); /*获取全局的JavaVM以及object*/ (*env)->GetJavaVM(env, &gl_jvm); if (NULL == gl_jvm) { LOGE("gl_jvm = NULL"); } gl_class = (*env)->NewGlobalRef(env, clazz); LOGE(""); FileObserverInit(monitorPath); (*env)->ReleaseStringUTFChars(env, path, str); return 0; }/* * Class: com_jiangc_fileobserver_FileObserverJni * Method: FileObserverDestroy * Signature: ()I */JNIEXPORT jint JNICALL Java_com_jiangc_receiver_FileObserverJni_FileObserverDestroy(JNIEnv *env, jclass cls){ (*env)->DeleteGlobalRef(env, gl_class); //释放全局的object FileObserverDestroy(); return 0; }#ifdef __cplusplus}#endif
说明
Android JNI 监控指定目录下的文件以及子目录及子目录下的文件,目前只支持创建和删除,其他暂不支持,主要使用linux inotify和epoll实现
使用方式
主要API: /** * Create a new file observer for a certain file or directory And start it. * @param path The file or directory to monitor * @param mask The event or events (added together) to watch for */ public FileObserverJni(String path, int mask) //推荐使用 /** * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS). */ public FileObserverJni(String path) 例子: String path = Environment.getExternalStorageDirectory().getAbsolutePath(); FileObserverJni fileObserverJni = new FileObserverJni(path + "/ftpFile", FileObserverJni.ALL_EVENTS); fileObserverJni.setmCallback(new FileObserverJni.Callback() { @Override public void FileObserverEvent(String path, int mask) { //这里根据mask做事件的判断 } });
修改记录:
2019/5/9
1.修改默认监听创建删除事件为可传入mask参数
2.修改使用接口,仿FileObserver
3.修改回调方法为统一方法回调
适配发现
1.锤子坚果pro事件有问题,除了删除,其他都不好用
github 源码
更多相关文章
- Android(安卓)Studio使用Butterknife时出现空指针问题解决
- 哥哥手把手教你认识AIDL的详细使用,来了老弟
- Android(安卓)Touch事件传递机制解析
- 为Android修改hosts-无需重启
- android实现拍照功能
- Android(安卓)Studio 工程依赖问题
- android:weight属性的使用——android开发之xml布局文件
- Android知识梳理之BroadcastReceiver整理
- Android(安卓)学习手札(一) 应用程序架构