本帖最后由 大山哥哥 于 2017-1-20 10:09 编辑
hello,大家好,今天给大家带来点不一样的东西,不知道大家有没有注意过,有些app在卸载的时候同时将在sd卡下的缓存目录给删除了,是不是觉得很纳闷?
那今天呢,就由我来带大家玩玩这个东西,当然呢,要玩这个其实是需要jni的铺垫的哦~~~
先带着大家来了解下如何去做这个功能,首先卸载app的时候app自己能知道么?似乎不行吧?所以咱们没法通过自带的api去实现。
那么就废话少说,let's go!
关于jni这块,简单带着大家过下java调用c吧。
第一步,写java代码,,写一个natvie方法
public native String getServerInfo(String path);
第二步。创建jni目录,在目录中创建test.c文件,同时将jni.h文件 放入jni目录
写c代码,注意方法名,并且引入jni.h
#include <jni.h>
#include <stdio.h>
jstring Java_com_example_untitled_MyActivity_getServerInfo(JNIEnv* env, jobject thiz, jstring path) {
写方法。。。
}
注意:方法名的写法如下
Java_包名_类型_方法名(jvm虚拟机的指针,调用者对象)
这里生成方法名也可以通过ndk来实现的分为2步
* 通过dos命令使用javac -d . Test.java 命令编译带有native方法的.java文件,这样就会在当前目录中生成一个class文件
会生成com/b3a4a/jnitest/Test.class
* 通过javah命令来生成 例如 javah com.b3a4a.jnitest.Test,会生成.h的头文件
生成 com_b3a4a_jnitest_Test.h 文件,方法名就在.h头文件中
JNIEXPORT jstring JNICALL Java_com_b3a4a_jnitest_Test_getServerInfo
(JNIEnv *, jobject);
第三步,在jni目录中创建文件,andorid.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.c
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)
第四步,进入命令行,进入应用程序的包的目录,执行ndk-build命令,创建Application.mk,自己填写需要的处理器型号,生成不同的so文件
APP_ABI := armeabi armeabi-v7a x86
第五步,将生成的libtest.so 放入lib/armeabi目录(目录不需要自己建)
第六步,java代码里面,把动态链接库加载到jvm虚拟机
static{
System.loadLibrary("库文件名称不带前缀,后缀名");
}
第7步,java中直接调用native方法
JNI开发中的常见错误:
1. 写错了load的library
java.lang.UnsatisfiedLinkError: Couldn't load hell0: findLibrary returned null
2. Android.mk文件编写错误
/jni/Android.mk:4: *** missing separator. Stop.
3. LOCAL_MODULE配置不能有扩展名
//jni/Android.mk:hello.so: LOCAL_MODULE_FILENAME must not contain a file extension
4. c或者c++的源文件名称配置错误
objs/hello/helo.o'. Stop.
5. 如果使用了错误的cpu平台
java.lang.UnsatisfiedLinkError: Couldn't load hello: findLibrary returned null
添加多cpu平台的支持 APP_ABI := armeabi armeabi-v7a x86
6. c语言方法名称错误,导致java代码无法寻找到c代码
Caused by: java.lang.UnsatisfiedLinkError: Native method not found: com.itheima.hellojni.MainActivity.helloFromC:()Ljava/lang/String;
ok。jni的东西带着大家回顾完了~那么咱们开始具体实现咯~
关键代码其实就是c这块实现的,先贴代码,再和大家说下实现过程
[C] 纯文本查看 复制代码 #include <string.h>
#include <jni.h>
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <android/log.h>
#include <unistd.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#define MAX_PATH 1024
/* 宏定义begin */
#define MEM_ZERO(pDest, destSize) memset(pDest, 0, destSize)
//LOG宏定义
#define LOG_INFO(tag, msg) __android_log_write(ANDROID_LOG_INFO, tag, msg)
#define LOG_DEBUG(tag, msg) __android_log_write(ANDROID_LOG_DEBUG, tag, msg)
#define LOG_WARN(tag, msg) __android_log_write(ANDROID_LOG_WARN, tag, msg)
#define LOG_ERROR(tag, msg) __android_log_write(ANDROID_LOG_ERROR, tag, msg)
/* 内全局变量begin */
static char c_TAG[] = "onEvent";
static jboolean b_IS_COPY = JNI_TRUE;
jstring Java_com_itheima_untitled_MyActivity_init(JNIEnv* env, jobject thiz,
jstring path) {
jstring tag = (*env)->NewStringUTF(env, c_TAG);
//初始化log
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "init OK"), &b_IS_COPY));
//fork子进程,以执行轮询任务
pid_t pid = fork();
if (pid < 0) {
//出错log
LOG_ERROR((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "fork failed !!!"), &b_IS_COPY));
} else if (pid == 0) {
int fileDescriptor = inotify_init();
if (fileDescriptor < 0) {
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_init failed !!!"), &b_IS_COPY));
exit(1);
}
int watchDescriptor;
watchDescriptor = inotify_add_watch(fileDescriptor,
"/data/data/com.itheima.untitled", IN_DELETE);
if (watchDescriptor < 0) {
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &b_IS_COPY));
exit(1);
}
//分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event
void *p_buf = malloc(sizeof(struct inotify_event));
if (p_buf == NULL) {
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &b_IS_COPY));
exit(1);
}
//开始监听
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "start observer"), &b_IS_COPY));
size_t readBytes = read(fileDescriptor, p_buf,
sizeof(struct inotify_event));
//read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器
free(p_buf);
inotify_rm_watch(fileDescriptor, IN_DELETE);
//目录不存在log
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "uninstalled"), &b_IS_COPY));
//删除文件
execlp("rm", "rm", "-rf",
(*env)->GetStringUTFChars(env, path, &b_IS_COPY),
(char *) NULL);
} else {
//父进程直接退出,使子进程被init进程领养,以避免子进程僵死
}
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
简单解释下~咱们也知道android的内核是linux,所以我们只要引导着执行rm命令去删除对应的目录就ok了,但是估计大家也有疑问,为啥能删除?其实自定义缓存目录本身就是咱们创建的吧?所以删除是有权限的,但是如果借此去删除其他应用的目录的话就会失败了~~~当然root另说咯。当然关于权限这块其实就是linux的那套,比较简单,所以就不重复了。ok,关键代码提供了,剩下的对于大家来说是不是so easy了,编译好so库,直接调用方法就ok了
|
|