Android JNI实现fileObserver

Android JNI实现fileObserver记录

背景

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

思路

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

注意

  1. inotify在linux 2.6.13 以上的内核才支持
  2. Android中 SDK >= 23 需要增加权限声明和动态权限申请。
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

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 <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
说明

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 源码

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无敌的娃哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值