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 需要增加权限声明和动态权限申请。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
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 <assert.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <memory.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <sys/epoll.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <jni.h>
#include<android/log.h>
#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 500
jclass gl_class; /*类*/
JavaVM *gl_jvm; /*java虚拟机*/
jobject gl_object; /*引用类型*/
int RUN = 1;
char *pathName[4096] = {NULL}; //save fd--->path name
int 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; i<MAX_FILES; i++)
{
if (!epoll_files[i])
continue;
if(0 == strcmp(name,epoll_files[i]))
{
return i;
}
}
return -1;
}
/**
*
*create event
*/
void CreateEvent(JNIEnv *env, jclass cls, char *path)
{
jmethodID jmethodid = NULL;
jmethodid = (*env)->GetStaticMethodID(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 event
struct inotify_event inotifyEvent;//inotify event
int 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 path
lwd = 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 epoll
if (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 event
struct inotify_event inotifyEvent; //inotify event
JNIEnv *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 inotify
if (NULL == path)
{
LOGE("path == null \n");
a = -1;
return &a;
}
mEpollFd = epoll_create(1000);
// 1. init inotify & epoll
int 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 dir
int 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 epoll
eventItem.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 <pollResult; i++)
{
readCount = 0;
readCount = read(mPendingEventItems[i].data.fd,inotifyBuf,MAXCOUNT);
if (readCount < sizeof(inotifyEvent))
{
LOGE("error inotify event \n");
return &a;
}
// cur 指针赋值
curInotifyEvent = (struct inotify_event*)inotifyBuf;
while(readCount >= 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 __cplusplus
extern "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
说明
时间仓促,只实现了默认的创建和删除
上一篇: APPARENT DEADLOCK