美文网首页
Android JNI 学习实践

Android JNI 学习实践

作者: 杨充211 | 来源:发表于2023-06-27 09:15 被阅读0次

    目录介绍

    • 01.学习JNI开发流程
      • 1.1 JNI开发概念
      • 1.2 JNI和NDK的关系
      • 1.3 JNI实践步骤
      • 1.4 NDK使用场景
      • 1.5 学习路线说明
    • 02.NDK架构分层
      • 2.1 NDK分层构建层
      • 2.2 NDK分层Java层
      • 2.3 Native层
    • 03.JNI基础语法
      • 3.1 JNI三种引用
      • 3.2 JNI异常处理
      • 3.3 C和C++互相调用
      • 3.4 JNI核心原理
      • 3.5 注册Native函数
      • 3.6 JNI签名是什么
    • 04.一些必备操作
      • 4.1 so库生成打包
      • 4.2 so库查询操作
      • 4.3 so库如何反编译
    • 05.实践几个案例
      • 5.1 Java静态调用C/C++
      • 5.2 C/C++调用Java
      • 5.3 Java调三方so中API
      • 5.4 Java动态调C++
    • 06.一些技术原理
      • 6.1 JNIEnv创建和释放
      • 6.2 动态注册的原理
      • 6.3 注册JNI流程图
    • 07.JNI遇到的问题
      • 7.1 混淆的bug
      • 7.2 注意字符串编译

    01.学习JNI开发流程

    1.1 JNI开发概念

    • .SO库是什么东西
      • NDK为了方便使用,提供了一些脚本,使得更容易的编译C/C<ins>代码。在Android程序编译中会将C/C</ins> 编译成动态库 so 文件,类似java库.jar文件一样,它的生成需要使用NDK工具来打包。
      • so是shared object的缩写,见名思义就是共享的对象,机器可以直接运行的二进制代码。实质so文件就是一堆C、C++的头文件和实现文件打包成一个库。
    • JNI是什么东西
      • JNI的全称是Java Native Interface,即本地Java接口。因为 Java 具备跨平台的特点,所以Java 与 本地代码交互的能力非常弱。
      • 采用JNI特性可以增强 Java 与本地代码交互的能力,使Java和其他类型的语言如C++/C能够互相调用。

    1.2 JNI和NDK的关系

    • JNI和NDK学习内容太难
      • 其实难的不是JNI和NDK,而是C/C++语言,JNI和NDK只是个工具,很容易学习的。
    • JNI和NDK有何联系
      • 学习JNI之前,首先得先知道JNI、NDK、Java和C/C++之间的关系。
      • 在Android开发中,有时为了性能和安全性(反编译),需要使用C/C++语言,但是Android APP层用的是Java语言,怎么才能让这两种语言进行交流呢,因为他们的编码方式是不一样的,这是就需要JNI了。
      • JNI可以被看作是代理模式,JNI是java接口,用于Java与C/C<ins>之间的交互,作为两者的桥梁,也就是Java让JNI代其与C/C</ins>沟通。
      • NDK是Android工具开发包,帮助快速开发C/C++动态库,相当于JDK开发java程序一样,同时能帮打包生成.so库

    1.3 JNI实践步骤

    • 操作实践步骤
      • 第一步,编写native方法。
      • 第二步,根据此native方法编写C文件。
      • 第三步,使用NDK打包成.so库。
      • 第四步,使用.so库然后调用api。
    • 如何使用NDK打包.so库
      • 1,编写Android.mk文件,此文件用来告知NDK打包.so库的规则
      • 2,使用ndk-build打包.so库
    • 相关学习文档

    1.4 NDK使用场景

    • NDK的使用场景一般在:
      • 1.为了提升这些模块的性能,对图形,视频,音频等计算密集型应用,将复杂模块计算封装在.so或者.a文件中处理。
      • 2.使用的是C/C++进行编写的第三方库移植。如ffmppeg,OpenGl等。
      • 3.某些情况下为了提高数据安全性,也会封装so来实现。毕竟使用纯Java开发的app是有很多逆向工具可以破解的。

    1.5 学习路线说明

    • JNI学习路线介绍
      • 1.首先要有点C/C++的基础,这个我是在 菜鸟教程 上学习的
      • 2.理解NDK和JNI的一些概念,以及NDK的一个大概的架构分层,JNI的开发步骤是怎样的
      • 3.掌握案例练习,前期先写案例,比如java调用c/c++,或者c/c++调用java。把这个案例写熟,跑通即可
      • 4.案例练习之后,然后在思考NDK是怎么编译的,如何打包so文件,loadLibrary的流程,CMake工作流程等一些基础的原理
      • 5.在实践过程中,先记录遇到的问题。这时候可能不一定懂,先放着,先实现案例或者简单的业务。然后边实践边琢磨问题和背后的原理
    • 注意事项介绍
      • 避免一开始就研究原理,或者把C/C++整体学习一遍,那样会比较辛苦。焦点先放在JNI通信流程上,写案例学习
      • 把学习内容,分为几个不同类型:了解(能够扯淡),理解(大概知道什么意思),掌握(能够运用和实践),精通(能举一反三和分享讲清楚)

    02.NDK架构分层

    • 使用NDK开发最终目标是为了将C/C++代码编译生成.so动态库或者静态库文件,并将库文件提供给Java代码调用。
    • 所以按架构来分可以分为以下三层:
      • 1.构建层
      • 2.Java层
      • 3.native层

    2.1 NDK分层构建层

    • 要得到目标的so文件,需要有个构建环境以及过程,将这个过程和环境称为构建层。
      • 构建层需要将C/C++代码编译为动态库so,那么这个编译的过程就需要一个构建工具,构建工具按照开发者指定的规则方式来构建库文件,类似apk的Gradle构建过程。
    • 在讲解NDK构建工具之前,我们先来了解一些关于CPU架构的知识点:Android abi
      • ABI即Application Binary Interface,定义了二进制接口交互规则,以适应不同的CPU,一个ABI对应一种类型的CPU。
    • Android目前支持以下7种ABI:
      • 1.armeabi:第5代和6代的ARM处理器,早期手机用的比较多。
      • 2.armeabi-v7a:第7代及以上的 ARM 处理器。
      • 3.arm64-v8a:第8代,64位ARM处理器
      • 4.x86:一般用在平板,模拟器。
      • 5.x86_64:64位平板。
    • 常规的NDK构建工具有两种:
      • 1.ndk-build:
      • 2.Cmake
    • ndk-build其实就是一个脚本。早期的NDK开发一直都是使用这种模式
      • 运行ndk-build相当于运行一下命令:$GNUMAKE -f <ndk>/build/core/build-local.mk
      • $GNUMAKE 指向 GNU Make 3.81 或更高版本,<ndk> 则指向 NDK 安装目录
      • 使用ndk-build需要配合两个mk文件:Android.mk和Application.mk
    • Cmake是一个编译系统的生成器
      • 简单理解就是,他是用来生成makefile文件的,Android.mk其实就是一个makefile类文件,cmake使用一个CmakeLists.txt的配置文件来生成对应的makefile文件。
      • Cmake构建so的过程其实包括两步:步骤1:使用Cmake生成编译的makefiles文件;步骤2:使用Make工具对步骤1中的makefiles文件进行编译为库或者可执行文件。
      • Cmake优势在哪里呢?在生成makefile过程中会自动分析源代码,创建一个组件之间依赖的关系树,这样就可以大大缩减在make编译阶段的时间。
    • Cmake构建项目配置
      • 使用Cmake进行构建需要在build.gradle配置文件中声明externalNativeBuild

    2.2 NDK分层Java层

    • 如何选择正确的so库呢

      • 通常情况下,我们在编译so的时候就需要确定自己设备类型,根据设备类型选择对应abiFilters。
      • 注意:使用as编译后的so会自动打包到apk中,如果需要提供给第三方使用,可以到build/intermediates/cmake/debug or release 目录中copy出来。
    • Java层如何调用so文件中的函数

      • 对于Android上层代码来说,在将包正确导入到项目中后,只需要一行代码就可以完成动态库的加载过程。有两种方式:
      System.load("/data/local/tmp/native_lib.so"); 
      System.loadLibrary("native_lib");
      
      
      • 1.加载路径不同:load是加载so的完整路径,而loadLibrary是加载so的名称,然后加上前缀lib和后缀.so去默认目录下查找。
      • 2.自动加载库的依赖库的不同:load不会自动加载依赖库;而loadLibrary会自动加载依赖库。
    • 无论哪种方式,最终都会调用到LoadNativeLibrary()方法,该方法主要操作:

      • 1.通过dlopen打开动态库文件
      • 2.通过dlsym找到JNI_OnLoad符号所对应的方法地址
      • 3.通过JNI_OnLoad去注册对应的jni方法

    2.3 Native层

    • 如何理解JNI的设计思想
      • JNI(全名Java Native Interface)Java native接口,其可以让一个运行在Java虚拟机中的Java代码被调用或者调用native层的用C/C++编写的基于本机硬件和操作系统的程序。简单理解为就是一个连接Java层和Native层的桥梁。
      • 开发者可以在native层通过JNI调用到Java层的代码,也可以在Java层声明native方法的调用入口。
    • JNI注册方式
      • 当Java代码中执行Native的代码的时候,首先是通过一定的方法来找到这些native方法。JNI有静态注册和动态注册两种注册方式。
      • 静态注册先由Java得到本地方法的声明,然后再通过JNI实现该声明方法。动态注册先通过JNI重载JNI_OnLoad()实现本地方法,然后直接在Java中调用本地方法。

    03.JNI基础语法

    3.1 JNI三种引用

    • 在JNI规范中定义了三种引用:
      • 局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。
    • Local引用
      • JNI中使用 jobject, jclass, and jstring等来标志一个Java对象,然而在JNI方法在使用的过程中会创建很多引用类型,如果使用过程中不注意就会导致内存泄露。
      • 直接使用:NewLocalRef来创建。Local引用其实就是Java中的局部引用,在声明这个局部变量的方法结束或者退出其作用域后就会被GC回收。
    • Global引用全局引用
      • 全局引用可以跨方法、跨线程使用,直到被开发者显式释放。一个全局引用在被释放前保证引用对象不被GC回收。
      • 和局部应用不同的是,能创建全局引用的函数只有NewGlobalRef,而释放它需要使用ReleaseGlobalRef函数。
    • Weak引用
      • 弱引用可以使用全局声明的方式。弱引用在内存不足或者紧张的时候会自动回收掉,可能会出现短暂的内存泄露,但是不会出现内存溢出的情况。

    3.2 JNI异常处理

    • native层异常
      • 处理方式1:native层自行处理
      • 处理方式2:native层抛出给Java层处理

    3.4 JNI核心原理

    • java运行在jvm,jvm本身就是使用C/C<ins>编写的,因此jni只需要在java代码、jvm、C/C</ins>代码之间做切换即可

      • [图片上传失败...(image-3ae615-1687914888789)]
    • JNIEnv是什么?

      • JINEnv是当前Java线程的执行环境,一个JVM对应一个JavaVM结构体,一个JVM中可能创建多个Java线程,每个线程对应一个JNIEnv结构,它们保存在线程本地存储TLS中。
      • 因此不同的线程JNIEnv不同,而不能相互共享使用。 JavaEnv结构也是一个函数表,在本地代码通过JNIEnv函数表来操作Java数据或者调用Java方法。

    3.5 注册Native函数

    • JNI静态注册:
      • 步骤1.在Java中声明native方法,比如:public native String stringFromJNI()
      • 步骤2.在native层新建一个C/C++文件,并创建对应的方法(建议使用AS快捷键自动生成函数名),比如:testjnilib.cpp: Line 8
    • JNI动态注册
      • 通过RegisterNatives方法把C/C++中的方法映射到Java中的native方法,而无需遵循特定的方法命名格式,这样书写起来会省事很多。
      • 动态注册其实就是使用到了前面分析的so加载原理:在最后一步的JNI_OnLoad中注册对应的jni方法。这样在类加载的过程中就可以自动注册native函数。比如:
      • 与JNI_OnLoad()函数相对应的有JNI_OnUnload()函数,当虚拟机释放该C库的时候,则会调用JNI_OnUnload()函数来进行善后清除工作。
    • 那么如何选择使用静态注册or动态注册
      • 动态注册和静态注册最终都可以将native方法注册到虚拟机中,推荐使用动态注册,更不容易写错,静态注册每次增加一个新的方法都需要查看原函数类的包名。

    3.6 JNI签名是什么

    • 为什么JNI中突然多出了一个概念叫”签名”:
      • 因为Java是支持函数重载的,也就是说,可以定义相同方法名,但是不同参数的方法,然后Java根据其不同的参数,找到其对应的实现的方法。
      • 这样是很好,所以说JNI肯定要支持的,如果仅仅是根据函数名,没有办法找到重载的函数的,所以为了解决这个问题,JNI就衍生了一个概念——”签名”,即将参数类型和返回值类型的组合。
      • 如果拥有一个该函数的签名信息和这个函数的函数名,就可以顺序的找到对应的Java层中的函数。
    • 如何查看签名呢:可以使用javap命令。
      • javap -s -p MainActivity.class

    04.一些必备操作

    4.1 so库生成打包

    • 什么是so文件库
      • so库,即将C或者C++实现的功能进行打包,将其打包为共享库,让其他程序进行调用,这可以提高代码的复用性。
    • 关于.so文件的生成有两种方式
      • 可以提供给大家参考,一种是CMake自动生成法,另一种是传统打包法。
    • so文件在程序运行时就会加载
      • 所以想使用Java调用.so文件,必有某个Java类运行时load了native库,并通过JNI调用了它的方法。
    • cmake生成.so方案
      • 第一步:创建native C++ Project项目,创建native函数并实现,先测试本地JNI函数调通
      • 第二步:获取.so文件。将生成的.apk文件改为.zip文件,然后进行解压缩,就能看到.so文件。如果想支持多种库架构,则可在module的build.gradle中配置ndk支持。
      • 第三步:so文件测试。新建一个普通的Android程序,将so库放入程序,然后创建类(注意要相同的包名、文件名及方法名)去加载so库。
      • 总结一下:Android Studio自动创建的native C++项目默认支持CMake方式,它支持JNI函数调用的入口在build.gradle中。
    • 传统打包生成.so方案【不推荐这种方式】
      • 第一步:在Java类中声明一个本地方法。
      • 第二步:执行指令javah获得C声明的.h文件。
      • 第三步:获得.c文件并实现本地方法。创建Android.mk和Application.mk,并配置其参数,两个文件如不编写或编写正常会出现报错。
      • 第四步:打包.so库。cd到\app目录下,执行命令 ndk-build即可。生成so库后,最后测试ok即可。

    4.2 so库查询操作

    • so库如何查找所对应的位置

      • 第一步:在 app 模块的 build.gradle 中,追加以下代码:
      • 第二步:执行命令行:./gradlew assembleDebug 【注意如果遇到gradlew找不到,则输入:chmod +x gradlew】
    • so文件查询结果后。就可以查询到so文件属于那个lib库的!如下所示:libtestjnilib.so文件属于TestJniLib库的

      find so file: /Users/yc/github/YCJniHelper/TestJniLib/build/intermediates/library_jni/debug/jni/armeabi-v7a/libtestjnilib.so
      find so file: /Users/yc/github/YCJniHelper/SafetyJniLib/build/intermediates/library_jni/debug/jni/armeabi-v7a/libsafetyjnilib.so
      find so file: /Users/yc/github/YCJniHelper/SignalHooker/build/intermediates/library_jni/debug/jni/armeabi-v7a/libsignal-hooker.so
      
      

    05.实践几个案例

    5.1 Java静态调用C/C++

    • Java调用C/C++函数调用流程

      • Java层调用某个函数时,会从对应的JNI层中寻找该函数。根据java函数的包名、方法名、参数列表等多方面来确定函数是否存在。
      • 如果没有就会报错,如果存在就会就会建立一个关联关系,以后再调用时会直接使用这个函数,这部分的操作由虚拟机完成。
    • Java层调用C/C++方法操作步骤

      • 第一步:创建java类NativeLib,然后定义native方法stringFromJNI()
      public native String stringFromJNI();
      
      
      • 第二步:根据此native方法编写C文件,可以通过命令后或者studio提示生成C++对应的方法函数
      //java中stringFromJNI
      //extern “C”    指定以"C"的方式来实现native函数
      extern "C"
      //JNIEXPORT     宏定义,用于指定该函数是JNI函数。表示此函数可以被外部调用,在Android开发中不可省略
      JNIEXPORT jstring
      //JNICALL       宏定义,用于指定该函数是JNI函数。,无实际意义,但是不可省略
      JNICALL
      //以注意到jni的取名规则,一般都是包名 + 类名,jni方法只是在前面加上了Java_,并把包名和类名之间的.换成了_
      Java_com_yc_testjnilib_NativeLib_stringFromJNI(JNIEnv *env, jobject /* this */) {
          //JNIEnv 代表了JNI的环境,只要在本地代码中拿到了JNIEnv和jobject
          //JNI层实现的方法都是通过JNIEnv 指针调用JNI层的方法访问Java虚拟机,进而操作Java对象,这样就能调用Java代码。
          //jobject thiz
          //在AS中自动为我们生成的JNI方法声明都会带一个这样的参数,这个instance就代表Java中native方法声明所在的
          std::string hello = "Hello from C++";
      
          //思考一下,为什么直接返回字符串会出现错误提示?
          //return "hello";
          return env->NewStringUTF(hello.c_str());
      }
      
      
    • 举一个例子

      • 例如在 NativeLib 类的native stringFromJNI()方法,程序会自动在JNI层查找 Java_com_yc_testjnilib_NativeLib_stringFromJNI 函数接口,如未找到则报错。如找到,则会调用native库中的对应函数。

    5.2 C/C++调用Java

    • Native层调用Java层的类的字段和方法的操作步骤
      • 第一步:创建一个Native C++的Android项目,创建 Native Lib 项目
      • 第二步:在cpp文件夹下创建:calljnilib.cpp文件,calljnilib.h文件(用来声明calljnilib.cpp中的方法)。
      • 第三步:开始编写配置文件CmkaeLists.txt文件。使用add_library创建一个新的so库
      • 第四步:编写 calljnilib.cpp文件。因为要实现native层调用Java层字段和方法,所以这里定义了两个方法:callJavaField和callJavaMethod
      • 第五步:编写Java层的调用代码此处要注意的是调用的类的类名以及包名都要和c++文件中声明的一致,否则会报错。具体看:CallNativeLib
      • 第六步:调用代码进行测试。然后查看测试结果

    5.3 Java调三方so中API

    • 直接拿前面案例的 calljnilib.so 来测试,但是为了实现三方调用还需要对文件进行改造
      • 第一步:要实现三方so库调用,在 calljnilib.h中声明两个和 calljnilib.cpp中对应的方法:callJavaField和callJavaMethod,一般情况下这个头文件是第三方库一起提供的给外部调用的。
      • 第二步:对CMakeLists配置文件改造。主要是做一些库的配置操作。
      • 第三步:编写 third_call.cpp文件,在这内部调用第三方库。这里需要将第三方头文件导入进来,如果CmakeLists文件中没有声明头文件,就使用#include "include/calljnilib.h" 这种方式导入
      • 第四步:最后测试下:callThirdSoMethod("com/yc/testjnilib/HelloCallBack","updateName");

    5.4 Java动态调C++

    • 先说一下静态调C++的问题:
      • 在实现stringFromJNI()时,可以看到c++里面的方法名很长 Java_com_yc_testjnilib_NativeLib_stringFromJNI。
      • 这是jni静态注册的方式,按照jni规范的命名规则进行查找,格式为Java_类路径_方法名。Studio默认这种方式名字太长了,能否设置短一点。
      • 程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时。
    • 动态注册方法解决上面问题
      • 当程序在Java层运行System.loadLibrary("testjnilib");这行代码后,程序会去载入testjnilib.so文件。
      • 于此同时,产生一个Load事件,这个事件触发后,程序默认会在载入的.so文件的函数列表中查找JNI_OnLoad函数并执行。与Load事件相对,在载入的.so文件被卸载时,Unload事件被触发。
      • 此时,程序默认会去载入的.so文件的函数列表中查找JNI_OnLoad函数并执行,然后卸载.so文件。
      • 因此开发者经常会在JNI_OnLoad中做一些初始化操作,动态注册就是在这里进行的,使用env->RegisterNatives(clazz, gMethods, numMethods)。
    • 动态注册操作步骤:
      • 第一步:因为System.loadLibrary()执行时会调用此方法,实现JNI_OnLoad方法。
      • 第二步:调用FindClass找到需要动态注册的java类【定义要关联的对应Java类】,注意这个是native方法那个类的路径字符串
      • 第三步:定义一个静态数据(JNINativeMethod类型),里面存放需要动态注册的native方法,以及参数名称
      • 第四步:通过调用jni中的RegisterNatives函数将注册函数的Java类,以及注册函数的数组,以及个数注册在一起,这样就实现了绑定。
    • 动态注册优势分析
      • 相比静态注册,动态注册的灵活性更高,如果修改了native函数所在类的包名或类名,仅调整native函数的签名信息即可。
      • 还有一个优势:动态注册,java代码不需要更改,只需要更改native代码。
      • 效率更高:通过在.so文件载入初始化时,即JNI_OnLoad函数中,先行将native函数注册到VM的native函数链表中去,后续每次java调用native函数时都会在VM中的native函数链表中找到对应的函数,从而加快速度。

    06.一些技术原理

    6.1 JNIEnv创建和释放

    • JNIEnv的创建方式
      • C 中——JNIInvokeInterface:JNIInvokeInterface是C语言环境中的JavaVM结构体,调用 (AttachCurrentThread)(JavaVM, JNIEnv*, void) 方法,能够获得JNIEnv结构体;
      • C<ins>中 ——_JavaVM:_JavaVM是C</ins>中JavaVM结构体,调用jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) 方法,能够获取JNIEnv结构体;
    • JNIEnv的释放:
      • C 中释放:调用JavaVM结构体JNIInvokeInterface中的(DetachCurrentThread)(JavaVM)方法,能够释放本线程的JNIEnv
      • C++ 中释放:调用JavaVM结构体_JavaVM中的jint DetachCurrentThread(){ return functions->DetachCurrentThread(this); } 方法,就可以释放 本线程的JNIEnv
    • JNIEnv和线程的关系
      • JNIEnv只在当前线程有效:JNIEnv仅仅在当前线程有效,JNIEnv不能在线程之间进行传递,在同一个线程中,多次调用JNI层方便,传入的JNIEnv是同样的
      • 本地方法匹配多个JNIEnv:在Java层定义的本地方法,能够在不同的线程调用,因此能够接受不同的JNIEnv

    6.2 动态注册的原理

    • 在Android源码开发环境下,大多采用动态注册native方法。
      • 利用结构体JNINativeMethod保存Java Native函数和JNI函数的对应关系;
      • 在一个JNINativeMethod数组中保存所有native函数和JNI函数的对应关系;
      • 在Java中通过System.loadLibrary加载完JNI动态库之后,调用JNI_OnLoad函数,开始动态注册;
      • JNI_OnLoad中会调用AndroidRuntime::registerNativeMethods函数进行函数注册;
      • AndroidRuntime::registerNativeMethods中最终调用jni RegisterNativeMethods完成注册。
    • 动态注册原理分析
      • RegisterNatives 方式的本质是直接通过结构体指定映射关系,而不是等到调用 native 方法时搜索 JNI 函数指针,因此动态注册的 native 方法调用效率更高。
      • 此外,还能减少生成 so 库文件中导出符号的数量,则能够优化 so 库文件的体积。

    6.3 注册JNI流程图

    • 提到了注册 JNI 函数(建立 Java native 方法和 JNI 函数的映射关系)有两种方式:静态注册和动态注册。

      • [图片上传失败...(image-8ab717-1687914888789)]
    • 分析下静态注册匹配 JNI 函数的执行过程

      • 第一步:以 loadLibrary() 加载 so 库的执行流程为线索进行分析的,最终定位到 FindNativeMethod() 这个方法。
      • 第二步:查看java_vm_ext.cc中FindNativeMethod方法,然后看到jni_short_name和jni_long_name,获取native方法对应的短名称和长名称。
      • 第三步:在java_vm_ext.cc,通过FindNativeMethodInternal查找已经加载的so库中搜索,先搜索短名称,然后再搜索长名称
      • 第四步:建立内部数据结构,建立 Java native 方法与 JNI 函数的函数指针的映射关系,调用 native 方法,则直接调用已记录的函数指针。

    07.JNI遇到的问题

    7.1 混淆的bug

    • 在Android工程中要排除对native方法以及所在类的混淆(java工程不需要),否则要注册的java类和java函数会找不到。proguard-rules.pro中添加。

      # 设置所有 native 方法不被混淆
      -keepclasseswithmembernames class * {
          native <methods>;
      }
      # 不混淆类
      -keep class com.yc.testjnilib.** { *; }
      
      

    7.2 注意字符串编译

    • 比如:对于JNI方法来说,使用如下方法返回或者调用直接崩溃了,有点搞不懂原理?

      env->CallMethod(objCallBack,_methodName,"123");
      
      
    • 这段代码编译没问题,但是在运行的时候就报错了:

      JNI DETECTED ERROR IN APPLICATION: use of deleted global reference
      
      
    • 最终定位到是最后一个参数需要使用jstring而不能直接使用字符串表示。如下所示:

      //思考一下,为什么直接返回字符串会出现错误提示?为何这样设计……
      //return "hello";
      return env->NewStringUTF(hello.c_str());
      
      

    代码案例:https://github.com/yangchong211/YCJniHelper

    其他案例:https://github.com/yangchong211/YCAppTool

    相关文章

      网友评论

          本文标题:Android JNI 学习实践

          本文链接:https://www.haomeiwen.com/subject/mkzgydtx.html