Android JNI实现fileObserver记录

背景

前段时间需要一个文件监听的功能,网上查了一下,Android自带的有一个FileObserver类可以实现此功能,就准备使用它来实现,不知为何有的手机能用,有的手机不能用,而且还不支持递归的监听,所以打算通过jni来实现。

思路

Android 系统底层核心是linux大家都很清楚,linux系统中有一个叫inotify的东西,它是linux的一个特性,它监控文件系统操作,比如读取、写入和创建。Inotify 反应灵敏,用法非常简单,并且比 cron 任务的繁忙轮询高效得多,结合epoll来实现多个目录文件的监听。

注意

  1. inotify在linux 2.6.13 以上的内核才支持
  2. Android中 SDK >= 23 需要增加权限声明和动态权限申请。
        

inotify

  1. 摘自百度百科
    1)Inotify 是一个 Linux 内核特性,它监控文件系统,并且及时向专门的应用程序发出相关的事件警告,比如删除、读、写和卸载操作等。您还可以跟踪活动的源头和目标等细节。

    2)使用 inotify 很简单:创建一个文件描述符,附加一个或多个监视器(一个监视器 是一个路径和一组事件),然后使用 read 方法从描述符获取事件。read 并不会用光整个周期,它在事件发生之前是被阻塞的。

    3)更好的是,因为 inotify 通过传统的文件描述符工作,您可以利用传统的 select 系统调用来被动地监控监视器和许多其他输入源。两种方法 — 阻塞文件描述符和使用 select— 都避免了繁忙轮询。

  2. 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

  1. 描述
    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 源码

更多相关文章

  1. Android(安卓)Studio使用Butterknife时出现空指针问题解决
  2. 哥哥手把手教你认识AIDL的详细使用,来了老弟
  3. Android(安卓)Touch事件传递机制解析
  4. 为Android修改hosts-无需重启
  5. android实现拍照功能
  6. Android(安卓)Studio 工程依赖问题
  7. android:weight属性的使用——android开发之xml布局文件
  8. Android知识梳理之BroadcastReceiver整理
  9. Android(安卓)学习手札(一) 应用程序架构

随机推荐

  1. Android学习
  2. Cocos2d-x编译Android环境
  3. This Android SDK requires Android Deve
  4. android gridview按钮边框和定制点击颜色
  5. [Android GMS 认证] CTS 问题列表之 CtsS
  6. android checkbox 定制(修改checkbox 的图
  7. AES加密解密Android版
  8. android 自定义输入框
  9. ANDROID轻量级JSON序列化和反序列化[转]
  10. android技术牛人的博客