Android NDK开发(九)——应用监听自身卸载升级版,使用Inotify监听安装目录

转载请注明出处:http://blog.csdn.net/allen315410/article/details/42555415

在上一篇博客中,我们讲了一个小小的案例,用NDK监听应用程序自身卸载,并且打开内置浏览器加载用户调用页面。关于监听应用程序自身卸载的原理和实现方案可以在上篇博客中找到,地址是:http://blog.csdn.net/allen315410/article/details/42521251,这里就不再复述了。

值得注意的是,在上篇博客中我也已经引述了一个案例中存在的问题,就是在监听应用程序安装目录是否被删除时,使用了while(true)这种死循环,让C代码每隔1秒钟去自动执行一次检查应用程序安装目录是否还存在,这样做效果是完全可以实现的,但是弊端也是显而易见的,由于使用了死循环,这样代码是不环保的,不可避免的重复执行,重复打印LOG,占用cpu计算资源,这是一种不太好的解决方案。

在这里,我来介绍一个比较好的解决方案,就是使用Linux系统的一个内核特性——Inotify,来监听应用程序安装目录的变化,inotify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知,这种机制是为了弥补Linux系统在桌面领域的不足而产生,在Linux2,.6内核中被添加,值得庆幸的是我们伟大的Android系统就是构建在Linux2.6内核的基础上的,所以Android里也就包含了Inotify机制。

关于Inotify:

在用户态,inotify 通过三个系统调用和在返回的文件描述符上的文件 I/O 操作来使用,使用 inotify 的第一步是创建 inotify 实例:

int fd = inotify_init ();

每一个 inotify 实例对应一个独立的排序的队列。

文件系统的变化事件被称做 watches 的一个对象管理,每一个 watch 是一个二元组(目标,事件掩码),目标可以是文件或目录,事件掩码表示应用希望关注的 inotify 事件,每一个位对应一个 inotify 事件。Watch 对象通过 watch描述符引用,watches 通过文件或目录的路径名来添加。目录 watches 将返回在该目录下的所有文件上面发生的事件。

下面函数用于添加一个 watch:

int wd = inotify_add_watch (fd, path, mask);

fd 是 inotify_init() 返回的文件描述符,path 是被监视的目标的路径名(即文件名或目录名),mask 是事件掩码, 在头文件 linux/inotify.h 中定义了每一位代表的事件。可以使用同样的方式来修改事件掩码,即改变希望被通知的inotify 事件。Wd 是 watch 描述符。

下面是mask事件掩码的可选值:

IN_ACCESS,即文件被访问

IN_MODIFY,文件被 write

IN_ATTRIB,文件属性被修改,如 chmod、chown、touch 等

IN_CLOSE_WRITE,可写文件被 close

IN_CLOSE_NOWRITE,不可写文件被 close

IN_OPEN,文件被 open

IN_MOVED_FROM,文件被移走,如 mv

IN_MOVED_TO,文件被移来,如 mv、cp

IN_CREATE,创建新文件

IN_DELETE,文件被删除,如 rm

IN_DELETE_SELF,自删除,即一个可执行文件在执行时删除自己

IN_MOVE_SELF,自移动,即一个可执行文件在执行时移动自己

IN_UNMOUNT,宿主文件系统被 umount

IN_CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)

IN_MOVE,文件被移动,等同于(IN_MOVED_FROM | IN_MOVED_TO)

注:上面所说的文件也包括目录。

下面的函数用于删除一个 watch:

int ret = inotify_rm_watch (fd, wd);

fd 是 inotify_init() 返回的文件描述符,wd 是 inotify_add_watch() 返回的 watch 描述符。Ret 是函数的返回值。

文件事件用一个 inotify_event 结构表示,它通过由 inotify_init() 返回的文件描述符使用通常文件读取函数 read 来获得:

struct inotify_event {
        __s32           wd;             /* watch descriptor */
        __u32           mask;           /* watch mask */
        __u32           cookie;         /* cookie to synchronize two events */
        __u32           len;            /* length (including nulls) of name */
        char            name[0];        /* stub for possible name */
};

结构中的 wd 为被监视目标的 watch 描述符,mask 为事件掩码,len 为 name字符串的长度,name 为被监视目标的路径名,该结构的 name 字段为一个桩,它只是为了用户方面引用文件名,文件名是变长的,它实际紧跟在该结构的后面,文件名将被 0 填充以使下一个事件结构能够 4 字节对齐。注意,len 也把填充字节数统计在内。

通过 read 调用可以一次获得多个事件,只要提供的 buf 足够大。

size_t len = read (fd, buf, BUF_LEN);

buf 是一个 inotify_event 结构的数组指针,BUF_LEN 指定要读取的总长度,buf 大小至少要不小于 BUF_LEN,该调用返回的事件数取决于 BUF_LEN 以及事件中文件名的长度。Len 为实际读去的字节数,即获得的事件的总长度。

可以在函数 inotify_init() 返回的文件描述符 fd 上使用 select() 或poll(), 也可以在 fd 上使用 ioctl 命令 FIONREAD 来得到当前队列的长度。close(fd)将删除所有添加到 fd 中的 watch 并做必要的清理。

int inotify_init (void);
int inotify_add_watch (int fd, const char *path, __u32 mask);
int inotify_rm_watch (int fd, __u32 mask);

注意:上述资料参考了IBM Developerworks,IBM Developerworks是个非常非常优秀的技术学习,我们可以在上面找到很多牛逼的资料来学习,如果你想更深入的了解Inotify机制,请点击http://www.ibm.com/developerworks/cn/linux/l-inotifynew/

代码实现:

关于代码的编写,大部分可以参考上篇博客的案例代码,因为大部分代码和配置文件以及编译步骤在上篇博客中以及写的比较详尽了,这里就不重复编写了,唯一需要改的部分就是,将C代码中的while(true)死循环部分删掉,改成用Inotify机制去监听应用目录的变化:

编译的头文件:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_appuninstalldemo_MainActivity */

#ifndef _Included_com_example_appuninstalldemo_MainActivity
#define _Included_com_example_appuninstalldemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_appuninstalldemo_MainActivity
 * Method:    uninstall
 * Signature: (Ljava/lang/String;I)V
 */
JNIEXPORT void JNICALL Java_com_example_appuninstalldemo_MainActivity_uninstall
  (JNIEnv *, jobject, jstring, jint);

#ifdef __cplusplus
}
#endif
#endif

C的代码实现:

#include <stdio.h>
#include <jni.h>
#include <malloc.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <fcntl.h>
#include <stdint.h>
#include "com_example_appuninstalldemo_MainActivity.h"
#include <android/log.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

/**
 * 返回值 char* 这个代表char数组的首地址
 * Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串
 */
char* Jstring2CStr(JNIEnv* env, jstring jstr) {
	char* rtn = NULL;
	jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String
	jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一个java字符串 "GB2312"
	jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",
			"(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
	jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,
			strencode); // String .getByte("GB2312");
	jsize alen = (*env)->GetArrayLength(env, barr); // byte数组的长度
	jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
	if (alen > 0) {
		rtn = (char*) malloc(alen + 1); //"\0"
		memcpy(rtn, ba, alen);
		rtn[alen] = 0;
	}
	(*env)->ReleaseByteArrayElements(env, barr, ba, 0); //
	return rtn;
}

JNIEXPORT void JNICALL Java_com_example_appuninstalldemo_MainActivity_uninstall(
		JNIEnv * env, jobject obj, jstring packageDir, jint sdkVersion) {
	// 1,将传递过来的java的包名转为c的字符串
	char * pd = Jstring2CStr(env, packageDir);

	// 2,创建当前进程的克隆进程
	pid_t pid = fork();

	// 3,根据返回值的不同做不同的操作,<0,>0,=0
	if (pid < 0) {
		// 说明克隆进程失败
		LOGD("current crate process failure");
	} else if (pid > 0) {
		// 说明克隆进程成功,而且该代码运行在父进程中
		LOGD("crate process success,current parent pid = %d", pid);
	} else {
		// 说明克隆进程成功,而且代码运行在子进程中
		LOGD("crate process success,current child pid = %d", pid);

		// 4,在子进程中监视/data/data/包名这个目录
		//初始化inotify进程
		int fd = inotify_init();
		if (fd < 0) {
			LOGD("inotify_init failed !!!");
			exit(1);
		}

		//添加inotify监听器
		int wd = inotify_add_watch(fd, pd, IN_DELETE);
		if (wd < 0) {
			LOGD("inotify_add_watch failed !!!");
			exit(1);
		}

		//分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event
		void *p_buf = malloc(sizeof(struct inotify_event));
		if (p_buf == NULL) {
			LOGD("malloc failed !!!");
			exit(1);
		}

		//开始监听
		LOGD("start observer");
		ssize_t readBytes = read(fd, p_buf,sizeof(struct inotify_event));

		//read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器
		free(p_buf);
		inotify_rm_watch(fd, IN_DELETE);

		// 应用被卸载了,通知系统打开用户反馈的网页
		LOGD("app uninstall,current sdkversion = %d", sdkVersion);
		if (sdkVersion >= 17) {
			// Android4.2系统之后支持多用户操作,所以得指定用户
			execlp("am", "am", "start", "--user", "0", "-a",
					"android.intent.action.VIEW", "-d", "http://www.baidu.com",
					(char*) NULL);
		} else {
			// Android4.2以前的版本无需指定用户
			execlp("am", "am", "start", "-a", "android.intent.action.VIEW",
					"-d", "http://www.baidu.com", (char*) NULL);
		}

	}
}

代码如上所示,大家可以根据上面的Inotify介绍和代码中注释来看,实现的代码基本上不难,但是了解实现原理还得好好理解一下Inotify的实现机制,这样才能事半功倍啊。

Java层代码很简单,直接看:

public class MainActivity extends Activity {

	static {
		System.loadLibrary("uninstall");
	}

	public native void uninstall(String packageDir, int sdkVersion);

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		String packageDir = "/data/data/" + getPackageName();
		int sdkVersion = android.os.Build.VERSION.SDK_INT;
		uninstall(packageDir, sdkVersion);
	}

}

运行之后卸载应用程序,效果如下:

源码请在这里下载

时间: 2024-08-29 05:34:03

Android NDK开发(九)——应用监听自身卸载升级版,使用Inotify监听安装目录的相关文章

Android NDK开发(八)——应用监听自身卸载,弹出用户反馈调查

转载请注明出处:http://blog.csdn.net/allen315410/article/details/42521251 监听卸载情景和原理分析 1,情景分析 在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用处不大,除了练练NDK功底.这篇博客,我将讲述一下一个各大应用中很常见的一个功能,同样也是基于JNI开发的Android应用小Demo,看完这个之后,不仅可以加深对NDK开发的理解,而且该Demo也可

Android NDK 开发

NDK全称:Native Development Kit. 1.NDK是一系列工具的集合. * NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk.这些工具对开发者的帮助是巨大的. * NDK集成了交叉编译器,并提供了相应的mk文件隔离平台.CPU.API等差异,开发人员只需要简单修改mk文件(指出"哪些文件需要编译"."编译特性要求"等),就可以创建出so. * NDK可以自动地将so和Java应用一起

windows下用ADT进行android NDK开发的详细教程(从环境搭建、配置到编译全过程)

郑重申明:如需转载本博客,请注明出处,谢谢! 这几天在学习android NDK的开发,那么首先让我们来看看android NDK开发的本质是什么. NDK(Native Development Kit),即本地开发工具,简单地说,就是在开发android应用程序的时候,在java类中调用native函数,而native函数的接口也是在java类中定义的,但是native函数最终由本地的C/C++代码实现.简单地说,就是在java中调用C/C++函数.至于为什么要用NDK,我总结了一下,大致有以

Android NDK开发(六)——使用开源LAME转码mp3

转载请注明出处:http://blog.csdn.net/allen315410/article/details/42456661 在本专栏的前面几篇博客中讲述了一些Android NDK开发的基础,从环境搭建一直到利用JNI进行Java端和C端代码的互相调用,并且的讲解的Demo也是很简单易懂的,相信掌握前面博客的大部分内容,就可以着手在实际项目中利用JNI进行NDK开发了,那么既然基础过了,接下来我在这里尝试去使用真实项目中去.我们知道,C语言因为高效,而且又是最早期的高级编程之一,一直存活

Android NDK开发篇(五):Java与原生代码通信(数据操作)

尽管说使用NDK能够提高Android程序的运行效率,可是调用起来还是略微有点麻烦.NDK能够直接使用Java的原生数据类型,而引用类型,由于Java的引用类型的实如今NDK被屏蔽了,所以在NDK使用Java的引用类型则要做对应的处理. 一.对引用数据类型的操作 尽管Java的引用类型的实如今NDK被屏蔽了,JNI还是提供了一组API,通过JNIEnv接口指针提供原生方法改动和使用Java的引用类型. 1.字符串操作 JNI把Java的字符串当作引用来处理,在NDK中使用Java的字符串,须要相

Android NDK 开发(三)--常见错误锦集合Log的使用【转】

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41826511  Android NDK开发经常因某些因素会出现一些意想不到的错误,很多时候调试这些错误的时候,显得比调试Java代码要复杂,一方面是导致错误的原因很多很杂,另一方面NDK开发涉及到C/C++代码的编写,很多程序员对此不熟悉.那么这篇博客就总结一下,在NDK开发中经常出现的一些问题,并且尝试提供一些正确的解决方案,方便在开发时能够快速定位到错误,更改错误,当然了,错

C++开发安卓、windows下搭建Android NDK开发环境

1. NDK(Native Development Kit) 1.1 NDK简介 Android NDK是一套允许开发人员使用本地代码(如C/C++)进行Android APP功能开发的工具,通过这个工具,我们可以把用C/C++代码编译成可以直接运行在Android平台上的本地代码,这些本地代码以动态链接库( *.so )的形式存在,也正因为这样,我们可以通过复用这些动态链接库从而复用本地代码. 那么,通过NDK这个开发工具包,那么我们是否可以将一个APK完全使用C/C++来编写呢? 答案是不可

Android NDK 开发(四)java传递数据到C【转】

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41845701 前面几篇文章介绍了Android NDK开发的简单概念.常见错误及处理和从第一个Hello World开始实际做一个简单的JNI开发示例,相信看完之后,大家对NDK开发有了一个概念上的认识了,那么接下来我们需要再深入一下NDK的开发,我们知道NDK开发就是使用JNI这层“协议”在Java和C之间起个“桥梁”的作用,将Java和Native C之间联立起来,让Java

跟我学Android NDK开发(一)

Android NDK 开发跟其它开发一样,首先需要配置好开发环境,本文以 Ubuntu系统为例介绍如何进行 Android NDK 开发环境的配置. 1. 简介 什么是 Android NDK 呢? NDK(Native Development Kit) 是一个允许开发者用一些本地语言(C/C++)编写 Android App 的部分功能的工具集.对于一些特定的 App,NDK 非常有利于我们直接使用现成的用 C/C++ 编写的代码库(但对于大多数 App 来说,NDK 是没有必要的).使用