欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android JNI实现fileObserver

程序员文章站 2022-06-12 21:18:32
...

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

说明

时间仓促,只实现了默认的创建和删除

github 源码

相关标签: JNI