努力每一天 發表於 2024-9-5 17:12:00

Android之JNI开发

<p>JNI<br>JNI是Java Native Interface的缩写,俗称Java本地接口,是Java语言提供的用于Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以通过JNI调用Java代码。</p>
<p>那什么场景下可能会用到JNI呢?<br>1、需要提升性能时,比如说做一些底层的开发,例如音视频处理之类的,通常都会用到JNI。<br>2、增加破解难度,例如需要提升代码的保护级别,需要将一些敏感信息放到底层隐藏起来。<br>3、需要使用到一些较为成熟的底层C/C++库时。</p>
<p>NDK<br>要在安卓上使用JNI就需要用到NDK,而NDK一系列工具的集合,它提供了一系列的工具帮助开发者快使得C/C++代码能够交叉编译生成可在安卓系统上运行的动态库库或者静态库。例如我们想要将基于C的音视频处理库FFmpeg移植到安卓平台上使用的话就需要用到NDK进行交叉编译。</p>
<p>动态库和静态库<br>1、静态库</p>
<p>静态库是指在链接阶段,编译器将汇编生成的.o目标文件与库文件一起链接打包到可执行文件中,或者说一起链接生成最后的可执行文件。因此对应的链接方式称为静态链接。</p>
<p>因为静态库是在编译期间连接库文件的,所以静态库存在着可执行文件体积较大,更新维护不方便等问题。</p>
<p>2、动态库</p>
<p>动态库又叫共享库,或动态链接库。动态库在程序编译时并不会被链接到目标代码中,而是在程序运行时才被载入,不同的应用程序如果要调用同一个库,在内存中只需要有一份该共享库即可,这样就规避了空间浪费的问题。而且动态库是在程序运行时才被载入,所以相对静态库来说动态库还具有更新、部署方便等优点。<br>正因为动态库的这些优点,所以目前的大多数的SDK普遍才有动态库的方式。<br>————————————————</p>
<p>&nbsp;</p>
<p>使用Android Studio新建一个<span style="color: rgba(255, 0, 0, 1)">Native C++工程</span></p>
<p>&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">JNIEXPORT
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> JNIEXPORT是一个宏,它们在不同的平台有着不同的定义,它的主要作用是保证在本库中声明的方法,能够在其他项目中可以被调用。</span>
例如attribute___((visibility (<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">default</span><span style="color: rgba(128, 0, 0, 1)">"</span>))) 表示外部可见,类似于public修饰符 (即:可以被外部调用),而attribute___((visibility (<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hidden</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)))表示隐藏,类似于private修饰符 (即:只能被内部调用)。

JNICALL
JNICALL也是一个宏,同样在不同的平台也有着不同的定义,用来表示函数的调用规范(如:__stdcall),目前在linux平台还是是空定义。

函数名
例如:Java_com_fly_jnitest_MainActivity_stringFromJNI
这是一个静态注册的函数名,它是与对于的Java方法有着对应关系的,他们的对应关系是:
Java_</span>&lt;包名&gt;_&lt;类名&gt;_&lt;方法名&gt;<span style="color: rgba(0, 0, 0, 1)">

JNIEnv
JNIEnv</span>* 是指向JVM函数表的指针,JNIEnv代表了Java环境,通过JNIEnv*<span style="color: rgba(0, 0, 0, 1)">就可以对Java端的代码进行操作,例如创建Java对象、访问Java对象方法、获取Java对象的属性等。
总之它是Java方法与C</span>/C++<span style="color: rgba(0, 0, 0, 1)">方法沟通的桥梁。

需要注意的是:一个JNIEnv指针仅在其相关联的线程中有效。不能将这个指针从一个线程中传递给另一个线程,或者在多线程中缓存和使用它。Java虚拟机在同一个线程传递给本地方法相同的JNIEnv指针,但是从不同线程中调用本地方法时传递的是不同的JNIEnv指针</span></pre>
</div>
<h2>数据类型映射关系</h2>
<p>在JNI中每种Java的基本数据类型都与C/C++的基本数据类型形成映射关系。</p>
<p>下图展示的是Java中的基本数据类型与JNI中的基本数据类型的映射关系:</p>
<p><img src="https://img2024.cnblogs.com/blog/1128896/202409/1128896-20240905171937611-499558765.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>[使用]</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>[开发]</p>
<p>安卓开发需要依赖NDK, 需要再Ndk Tools中包含 NDK 和CMake</p>
<p><img src="https://img2024.cnblogs.com/blog/1128896/202409/1128896-20240905172753492-1000847742.png" alt="" loading="lazy"></p>
<p>&nbsp;</p>
<p>1) 需要在cpp目录下,新建C文件,例如Utils.c</p>
<div class="cnblogs_code">
<pre>#include &lt;jni.h&gt;<span style="color: rgba(0, 0, 0, 1)">
JNIEXPORT jint
JNICALL Java_com_lanlang_demo_Utils_v1(JNIEnv </span>*<span style="color: rgba(0, 0, 0, 1)">env, jclass clazz,jint v1,jint v2) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> v1+<span style="color: rgba(0, 0, 0, 1)">v2;
}</span></pre>
</div>
<p>2)在Java下新建一个类,编写静态方法</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">package com.justin.s8day12;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Utils {<br></span></pre>
<pre>   static<span> {
      System.loadLibrary("utils"<span>);
    }</span></span></pre>
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> native <span style="color: rgba(0, 0, 255, 1)">int</span> v1(<span style="color: rgba(0, 0, 255, 1)">int</span> a1,<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> a2); }</span></pre>
</div>
<p>3) 引入静态文件</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> {
      System.loadLibrary(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">utils</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    }</span></pre>
</div>
<p>4)在CMakeLists.txt中加入编写的c文件</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">
    add_library(
      utils # 起个名字,我们叫utils
      SHARED
      # 指定我们新建的c文件
      utils.c)
   
   
    target_link_libraries(
      utils# 加入我们自己写的utils,空格分割,写多个
      ${log</span>-lib})</pre>
</div>
<p>5) 在Java代码中调用</p>
<div class="cnblogs_code">
<pre>tv.setText(String.valueOf(Utils.v1(<span style="color: rgba(128, 0, 128, 1)">33</span>,<span style="color: rgba(128, 0, 128, 1)">44</span>)));</pre>
</div>
<p>&nbsp;</p>
<p>编译打包完成之后,逆向流程:</p>
<div class="cnblogs_code">
<pre>-<span style="color: rgba(0, 0, 0, 1)">使用压缩工具把 apk解压
    </span>-进入lib的arm64-<span style="color: rgba(0, 0, 0, 1)">v8a目录,看到so文件
    </span>-<span style="color: rgba(0, 0, 0, 1)">把so文件拖动到IDA中
    </span>-<span style="color: rgba(0, 0, 0, 1)">选择exports导出
    </span>-<span style="color: rgba(0, 0, 0, 1)">双击函数名,看到汇编
    </span>-按F5,把混编进行反编译<br><br>Java中类文件的加载与实际文件的关系:</pre>
<p># System.loadLibrary("dynamic"); --&gt;libdynamic.so<br># System.loadLibrary("utils"); --&gt;libutils.so</p>
<p>&nbsp;</p>
</div>
<p>一般都是会加上lib的前缀</p>
<p>&nbsp;</p>
<p>注册方式:<br>静态注册:</p>
<div class="cnblogs_code">
<pre>反编译自己app----》找到了jni调用位置---》通过System.loadLibrary(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">utils</span><span style="color: rgba(128, 0, 0, 1)">"</span>)---》确定是哪个so文件---》去so文件中,通过 静态注册方案找</pre>
</div>
<p>&nbsp;</p>
<p>动态注册:</p>
<div class="cnblogs_code">
<pre>反编译so后,找JNI_OnLoad</pre>
</div>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/xingxia/p/18398866/android_jni
頁: [1]
查看完整版本: Android之JNI开发