A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 大山哥哥 于 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了

3 个回复

正序浏览
多谢分享
回复 使用道具 举报
回复 使用道具 举报

回帖奖励 +1

这个好,实用
来自宇宙超级黑马专属苹果客户端来自宇宙超级黑马专属苹果客户端
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马