让JNI告诉你 你的应用为什么被卸载

简介: 让JNI告诉你 你的应用为什么被卸载

 前言

Android Jni开发相信多数Android开发者都有所了解,但是网上很多教程分为两种,一种是告诉你如何配置NDK环境变量,建个helloWorld的Demo,另一种就是太过于高端,C语言一大片,云里雾里,虽然很多公司开发都会有单独的人员来写C,但是从Android开发人员角度来说,学习C还是很有必要的,一切源码终归C.

一  这篇文章你可以学到什么

    • 1.Java语言如何调用C代码,以C语言验证用户名和密码为例
    • 2.C语言如何调用Java代码,以C语言调用Java方法为例
    • 3.如何使用C语言,实现简单实用的功能,以APP卸载反馈为例

    好了,如果你对以上内容感兴趣,那就接着往下来,我要说明的是这篇文章不会告诉你如何配置NDK环境,如果你解决不了,怎么办?关注我,关注我~

    image.gif

    二  实例演示

    首先我们要明白的是,为什么有些项目中要使用C,原因很简单,哪怕是一个计算,C的效率也要高于Java,Java做的C可以做,Java不可以做的C也可以做,所以有些复杂的处理操作或者是底层相关的逻辑都可以交给C去做,像美图秀秀,播放器等软件都用了大量的C代码处理业务。

    image.gif

      • 2.1 Java调用C代码,以验证用户名密码为例

      验证用户名密码我们肯定要将用户名和密码传给C,我们新建一个JNI类,在类中新建一个返回整形的方法,如下所示。

      public native int checkUser(String name, String pass);

      image.gif

      记得使用关键字native,这个时候我们就要在C中编写相应的方法,像什么,javah生成头文件什么的那种我在前言中说了,就不讲解了,在studio工具中生成,鼠标点击到方法,Alt + Enter快捷方式自动生成如下方法

      #include <jni.h>
      JNIEXPORT jint JNICALL
      Java_jnidemo_hlq_com_jnidemo_JNI_checkUser(JNIEnv *env, jobject instance, jstring name_,
                                                 jstring pass_) {
          const char *name = (env)->GetStringUTFChars(name_, 0);
          const char *pass = (env)->GetStringUTFChars(pass_, 0);
          // TODO
          (env)->ReleaseStringUTFChars( name_, name);
          (env)->ReleaseStringUTFChars( pass_, pass);
      }

      image.gif

      在这里要注意一下,这里我们建的是.cpp文件,至于.c 和 .cpp 就是一个是c一个是c++

      c++中代码是

      const char *name = (env)->GetStringUTFChars(name_, 0);

      c中对应的就是

      const char *name = (*env)->GetStringUTFChars(env,name_, 0);

      接下来我们要在cmake中进行配置

      add_library( # Sets the name of the library.
                  checkuser
                   SHARED
                   src/main/cpp/cheruser.cpp
                       )

      image.gif

      checkuser 就是配置生成的so名称为libcheckuser.so,SHARED配置库文件是共享, src/main/cpp/cheruser.cpp就是对应的路径了

      target_link_libraries( # Specifies the target library.
                             checkuser
                             # Links the target library to the log library
                             # included in the NDK.
                             ${log-lib} )

      image.gif

      checkuser保持和上面名字对应就可以了。

      这样我们就可以在JNI类中,加载这个库

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

      image.gif

      在C代码中我们已经得到了name和pass

      const char *name = (env)->GetStringUTFChars(name_, 0);

      const char *pass = (env)->GetStringUTFChars(pass_, 0);

      直接和用户名密码比较即可,这里在代码中将变量名定义为name 密码为123

      const char *tureName = "name";
      const char *turePass = "123";

      image.gif

      使用strcmp函数来比较,两个字符串相等则返回0,记得引用string.h头文件

      #include <string.h>

      image.gif

      if (strcmp(name,tureName) == 0 && strcasecmp(pass,turePass) == 0){
          return 1;
      } else{
          return 0;
      }

      image.gif

      我们在Activity中输入用户名密码,调用C方法,若返回1则说明登陆成功,若返回0则说明用户名密码不正确,登陆失败

      if (new JNI().checkUser("name", "123") == 1) {
          Toast.makeText(MainActivity.this, "登陆成功", Toast.LENGTH_LONG).show();
      } else {
          Toast.makeText(MainActivity.this, "登陆shibai", Toast.LENGTH_LONG).show();
      }

      image.gif

      image.gif

      2.2 c语言调用Java方法

      首先我们在JNI类中新建一个sum方法,返回两数之和

      public int sum(int i, int j) {
          Log.d("---", "我是java 我被c调用了" + (i + j));
          return i + j;
      }

      image.gif

      C调用Java肯定要Java调用C的某个方法,在这个方法中调用java方法,所以我们再来新建一个testHello方法

      public native String testHello();

      image.gif

      默认生成的C方法为

      JNIEXPORT jstring JNICALL
      Java_jnidemo_hlq_com_jnidemo_JNI_testHello(JNIEnv *env, jobject) {
          return (env)->NewStringUTF("huanglinqing");
      }

      image.gif

      我们要调用的java方法在JNI类中,想想java可以通过反射来调用另一个类的方法,那么C其实也是通过反射的,首先我们定义要调用方法的路径,JNI类全路径为jnidemo.hlq.com.jnidemo.JNI,在C中将.替换为/

      const char *className = "jnidemo/hlq/com/jnidemo/JNI";

      image.gif

      方法名为sum

      const char *sum = "sum";

      image.gif

      通过findClass获取class对象,然后通过AllocObject获取类的实例

      jclass jclass1 = env->FindClass(className);
      jobject jobject1 = env->AllocObject(jclass1);

      image.gif

      然后我们获取到要调用方法的methodId

      jmethodID jmethodID1 = env->GetMethodID(jclass1, sum,"(II)I");

      image.gif

      第一个参数是class对象,第二个参数是函数名,第三个参数是方法签名

      image.gif

      复制项目app\build\intermediates\classes\debug文件路径,打开cmd,进入路径,(如果之前没有编译过项目记得先编译一下,这样才能获取class文件),使用命令 javap -s jnidemo.hlq.com.jnidemo.JNI jnidemo.hlq.com.jnidemo.JNI是调用方法的全路径。

      image.gif

      运行可以看到sum方法的签名是(II)I

      获取到方法的jmethodID1之后调用CallIntMethod即可调用方法

      jint value = env->CallIntMethod(jobject1, jmethodID1,1,2);

      image.gif

      第一个参数是类的实例,第二个参数是获取的jmethodID1,后面就是sum函数依次对应的参数。

      代码整体为:

      const char *className = "jnidemo/hlq/com/jnidemo/JNI";
      const char *sum = "sum";
      jclass jclass1 = env->FindClass(className);
      jmethodID jmethodID1 = env->GetMethodID(jclass1, sum,"(II)I");
      jobject jobject1 = env->AllocObject(jclass1);
      jint value = env->CallIntMethod(jobject1, jmethodID1,1,2);
      printf("c 运行结果为 %d",value);

      image.gif

      我们在activity中调用

      new JNI().testHello();

      image.gif

      image.gif

      上述即为C语言调用了java的方法

        • 2.3 检测APP的卸载

        相信很多伙伴在面试的时候,总会被问到APP保活的问题,如果你回答不上来,面试官还会一脸鄙视的看着你,APP如何保活?

        websocket心跳?三方推送?JNI fork进程?其实我觉得都是扯淡,系统版本越高Google限制的越严格,我们自己做的APP除非是大厂,有白名单,否则不可能做到保活,而这个问题其实问的也没有多大的意义。我曾经试过fork保活,杀死也是秒死。

        检测APP卸载就是,当APP被用户卸载之后,自动打开浏览器网页跳转到一个调查问卷让用户去填写为什么会卸载,这个功能PC端软件经常可以看到,APP用的不多,但是也是挺有意思的,但是和保活一样这个功能很鸡肋,版本稍微高一点,就彻底死了,但是我们了解一下还是很有必要的。

        首先,我们定义一个方法,传递当前应用包名和当前系统版本

        public native void uninstall(String packageName, int versionCode);

        image.gif

        在c中使用

        int code = fork();

        image.gif

        记得引入头文件

        #include "unistd.h"

        image.gif

        当fork的值>=0的时候 说明fork子进程和父进程成功,可以去做判断,当然一般都是子进程成功才去判断

        app安装之后默认目录都是

        /data/data/包名

        image.gif

        所以我们做一个1秒定时循环去fopen这个文件夹,当文件夹不存在的时候说明APP被卸载了,

        if (code >= 0) {
            int flag = 1;
            while (flag) {
                sleep(1);
                FILE *file;
                try {
                    try {
                        file = fopen("/data/data/jnidemo.hlq.com.jnidemo", "rt");
                    } catch (_JNIEnv env) {
                        LOGD("--- %s", "i一场了");
                    }
                    if (file == NULL) {
                        flag = 0;
                        if (versionCode < 17) {
                            execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d",
                                   "http://baidu.com", NULL);
                        } else {
                            execlp("am", "am", "start", "--user", "0", "-a",
                                   "android.intent.action.VIEW",
                                   "-d", "http://baidu.com", (char *) NULL);
                        }
                    } else {
                        fclose(file);
                        LOGD("---%s", "我还在");
                    }

        image.gif

        这里我们看到LOGD就是我们定义的log 这样可以将c代码中的日志输出到控制台,定义如下

        #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)

        image.gif

        当file为null的时候我们使用execlp命令 去操作android的一个意图android.intent.action.VIEW,打开百度的网址

        当然,我自己在测试的时候,可以完美运行的只有一个4.0的3G手机,其他高版本手机也是无济于事。

        好了,JNI就是这样了,另外偷偷告诉你,如果你想做个美图秀秀的软件,直接下载一个美图秀秀,解压,获取里面的so文件,和JNI方法类就可以了,你可能会说都混淆了去哪里找,你可能忘了,JNI反法是不能混淆的。

        image.gif

        之前写过一篇JNI的应用,QQ变声功能的实现。


        目录
        相关文章
        |
        5月前
        |
        存储 Java C语言
        Windows 下 JNI 调用动态链接库 dll
        Windows 下 JNI 调用动态链接库 dll
        141 0
        |
        开发框架 .NET Java
        C#下反射动态加载dll后如何卸载?
        C#下反射动态加载dll后如何卸载?
        |
        Java
        MAC编译的JDK执行出错: [libjvm.dylib+0x482a49] PerfDataManager::destroy()+0xab
        MAC编译的JDK执行出错: [libjvm.dylib+0x482a49] PerfDataManager::destroy()+0xab
        115 0
        |
        Java Linux iOS开发
        JNI用C加载JDK产生JVM虚拟机,并运行JAVA类main函数(MACOS/LINUX/WINDOWS)
        JNI用C加载JDK产生JVM虚拟机,并运行JAVA类main函数(MACOS/LINUX/WINDOWS)
        139 0
        |
        IDE Java 开发工具
        JNI学习(2)——生成动态链接.dll文件
        JNI学习(2)——生成动态链接.dll文件
        151 0
        JNI学习(2)——生成动态链接.dll文件
        |
        Java Android开发 C++
        【Android 安装包优化】使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(一)
        【Android 安装包优化】使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(一)
        200 0
        【Android 安装包优化】使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(一)
        |
        Java Android开发 C++
        【Android 安装包优化】使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(二)
        【Android 安装包优化】使用 lib7zr.so 动态库处理压缩文件 ( jni 中 main 函数声明 | 命令行处理 | jni 调用 lib7zr.so 函数库处理压缩文件完整代码 )(二)
        179 0
        |
        Linux 编译器 C语言
        Linux下静态库、动态库的创建与调用
        Linux下静态库、动态库的创建与调用
        121 0
        |
        Java Linux API
        linux jna调用so动态库
        文中提到:为什么命名为libtest.so而不是test.so呢?因为jna在找so文件的时候,要匹配前缀为lib的so文件 http://zhenaihua0213.
        2791 0
        |
        Java Linux C语言
        java 通过jni调用linux so动态库
        java 通过jni调用linux so动态库准备开发javaC++总结java 通过jni调用linux so动态库欢迎转载 地址:https://blog.csdn.net/qq_15122663/article/details/96732890 最近有个项目需要java调用C++的动态库,所以重温一下操作步骤记录一下。
        3130 0