Android NDK开发总结
<p>一、准备Android NDK开发环境</p><p>NDK:android原生开发工具包,这套工具集允许您为 Android 使用 C 和 C++ 代码,并提供众多平台库,让您可以管理原生 Activity 和访问物理设备组件,例如传感器和触摸输入。</p>
<p>CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。</p>
<p>LLDB:一种调试程序,Android Studio 使用它来调试原生代码。</p>
<p> </p>
<p>可以从 SDK 管理器中安装 LLDB、CMake 和 NDK</p>
<p>1.在打开的项目中,从菜单栏选择 <strong>Tools > Android > SDK Manager</strong>。</p>
<p>2.点击 <strong>SDK Tools</strong> 标签。</p>
<p>3.选中 <strong>LLDB</strong>、<strong>CMake</strong> 和 <strong>NDK</strong> 旁的复选框,如图所示。</p>
<p>4.点击 <strong>Apply</strong>,然后在弹出式对话框中点击 <strong>OK。</strong></p>
<p>5.安装完成后,点击 <strong>Finish</strong>,然后点击 <strong>OK。</strong></p>
<p><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190626171135075-1494031711.png" alt="" width="563" height="371"></p>
<p>本片文章使用ndk-build来构建ndk项目,cmake方式本篇不做阐述。</p>
<p>二、要了解的知识点</p>
<p>Android.mk:</p>
<p>位于项目JNI目录的子目录中,用于向编译系统描述源文件和共享库。它实际上是编译系统解析一次或多次的微小 GNU makefile 片段。Android.mk文件用于定义Application.mk、编译系统和环境变量所未定义的项目范围设置。它还可替换特定模块的项目范围设置。</p>
<p>内容解析:</p>
<pre class="lang-makefile"><code><span class="pln">LOCAL_PATH <span class="pun">:=<span class="pln"> $<span class="pun">(<span class="pln">call <span class="kwd">my<span class="pun">-<span class="pln">dir<span class="pun">)//编译系统提供宏函数 <code>my-dir</code> 返回当前目录(<code>Android.mk</code>文件本身所在的目录)的路径。<br></span></span></span></span></span></span></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln">include $<span class="pun">(<span class="pln">CLEAR_VARS<span class="pun">)//<code>CLEAR_VARS</code>变量指向一个特殊的 GNU Makefile,后者会清除许多 <code>LOCAL_XXX</code> 变量,例如 <code>LOCAL_MODULE</code>、<code>LOCAL_SRC_FILES</code> 和 <code>LOCAL_STATIC_LIBRARIES,但是不会清除不会清除 <code>LOCAL_PATH。在描述每个模块之前,必须声明(重新声明)此变量<br></code></code></span></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln">LOCAL_MODULE <span class="pun">:=<span class="pln"> hello<span class="pun">-<span class="pln">jni//每个模块名称必须唯一,且不含任何空格。编译系统在生成最终共享库文件时,会对您分配给 <code>LOCAL_MODULE</code> 的名称自动添加正确的前缀和后缀。例如,上述示例会生成名为 <code>libhello-jni.so</code> 的库</span></span></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln">LOCAL_SRC_FILES <span class="pun">:=<span class="pln"> hello<span class="pun">-<span class="pln">jni<span class="pun">.<span class="pln">c//列举要编译到模块中的C、C++源文件,以空格分隔多个文件<br></span></span></span></span></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln">LOCAL_CPP_EXTENSION <span class="pun">:=<span class="pln"> <span class="pun">.<span class="pln">cxx //使用此可选变量为 C++源文件指明<code>.cpp</code>以外的文件扩展名。例如<code>.cxx</code></span></span></span></span></span></code></pre>
<pre>LOCAL_SHARED_LIBRARIES <code><span class="pun">:=<span class="pln"> lib<span class="pun"><span class="pln">z //此变量会列出此模块在运行时依赖的共享库模块,如libz.so。此信息是链接时必需的信息,用于将相应的信息嵌入到生成的文件中</span></span></span></span></code></pre>
<pre><code><span class="pun"><span class="pln"><span class="pun"><span class="pln">LOCAL_STATIC_LIBRARIES := libz </span></span></span></span></code><code>//此变量用于存储当前模块依赖的静态库模块列表,如libz.a。此信息是链接时必需的信息,用于将相应的信息嵌入到生成的文件中(需要先预编译libz.a才能找到)</code></pre>
<pre class="lang-makefile"><code><span class="pln">LOCAL_LDLIBS <span class="pun">:=<span class="pln"> <span class="pun">-<span class="pln">lz //此变量列出了在编译共享库或可执行文件时使用的额外链接器标记,可使用<code>-l</code>前缀传递特定系统库的名称</span></span></span></span></span></code><code>,如</code><code><span class="pln"><span class="pun"><span class="pln"><span class="pun">-<span class="pln">lz表示</span></span></span></span></span></code><code>libz.so。<br><br></code>LOCAL_CFLAGS //只编译 C++ 源文件时将传递的一组可选编译器标记。它们将出现在编译器命令行中的 <code>LOCAL_CFLAGS</code> 后面。使用 <code>LOCAL_CFLAGS</code> 为 C 和 C++ 指定标记。比如:= -frtti -fexceptions --std=c++11 -DANDROID -DNDEBUG</pre>
<pre class="lang-makefile"><code><span class="pln">LOCAL_C_INCLUDES <span class="pun">:=<span class="pln"> sources<span class="pun">/<span class="pln">foo //可以使用此可选变量指定相对于 NDK <code>root</code> 目录的路径列表,以便在编译所有源文件(C、C++ 和 Assembly)时添加到 include 搜索路径</span></span></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln">include $<span class="pun">(<span class="pln">BUILD_SHARED_LIBRARY<span class="pun">)//将所有内容连接到一起<br></span></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln">include $<span class="pun">(<span class="pln">PREBUILT_SHARED_LIBRARY<span class="pun">) //指向用于指定预编译共享库的编译脚本<br></span></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln">include $<span class="pun">(<span class="pln">PREBUILT_STATIC_LIBRARY)//预编译静态库<br><br></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln">LOCAL_ARM_MODE <span class="pun">:=<span class="pln"> arm //默认情况下,编译系统会在 thumb 模式下生成 ARM 目标二进制文件,其中每条指令都是 16 位宽,并与 <code>thumb/</code> 目录中的 STL 库关联。将此变量定义为 <code>arm</code> 会强制编译系统在 32 位 <code>arm</code> 模式下生成模块的对象文件</span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln"><span class="pun"><span class="pln"><span class="pun"> <br>Application.mk<br>位于项目JNI目录的子目录中,用于指定项目范围设置。(在Android.mk中设置的模块选项优先于项目范围选项)<br></span></span></span></span></code></pre>
<pre class="lang-makefile"><code><span class="pln"><span class="pun"><span class="pln"><span class="pun"><code><code>内容解析:<br></code></code></span></span></span></span></code></pre>
<pre>APP_OPTIM := release //将此可选变量定义为 <code>release</code> 或 <code>debug</code>。默认情况下,将编译发布模式的二进制文件,发布模式会启用优化。</pre>
<pre class="lang-makefile"><code><span class="pln">APP_ABI <span class="pun">:=<span class="pln"> armeabi<span class="pun">-<span class="pln">v7a arm64<span class="pun">-<span class="pln">v8a x86 //默认情况下,NDK 编译系统会为所有非弃用 ABI 生成代码。您可以使用 <code>APP_ABI</code> 设置为特定 ABI 生成代码<br><br><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190627094724179-356469751.png" alt="" width="717" height="191"></span></span></span></span></span></span></span></code></pre>
<pre><br>APP_PLATFORM := android-16 //声明编译此应用所面向的 Android API 级别,并对应于应用的 <code>minSdkVersion</code></pre>
<pre>APP_STL :=c++_static //用于此应用的 C++ 标准库。默认情况下使用 <code>system</code> STL。其他选项包括 <code>c++_shared</code>、<code>c++_static</code> 和 <code>none</code><code><span class="pln"><span class="pun"><span class="pln"><span class="pun"><code><code> <br><br>java与jni数据类型对照以及使用:<br>1.综述:<br></code></code></span></span></span></span></code></pre>
<p>1)java中的返回值void与jni中的void是完全对应的。</p>
<p>2)java中的基本数据类型(byte,short,int,long,float,double,boolean,char)在jni中对应的数据类型在前面加上j (jbyte,jshort,jint,jlong,jfloat,jdouble,jboolean,jchar)。</p>
<p>3)java中的对象,包括类库中定义的类、接口,都对应jni中的jobject。</p>
<p>4)java中基本数据类型的数组对应与jni中的jarray类型(type就是上面说的8中基本数据类型</p>
<p>5)java中对象的数组对应于jni中jobjectArray类型</p>
<p> </p>
<p>2.java数据类型与jni类型映射表</p>
<p><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190628154848576-105829709.png" alt=""></p>
<p>3.java类型和jni签名的关系</p>
<p><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190628155017980-2033759088.png" alt=""></p>
<p> </p>
<p>4.常用的函数:</p>
<p>jclass (*FindClass)(JNIEnv*, const char*);</p>
<p>jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*);</p>
<p>jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);</p>
<p>jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);</p>
<p>void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);</p>
<p>jsize (*GetStringLength)(JNIEnv*, jstring);</p>
<p>......</p>
<pre><code><span class="pln"><span class="pun"><span class="pln"><span class="pun"><code><code>三、自定义Android Studio工具<br></code></code></span></span></span></span></code></pre>
<p>1.在打开的项目中,从菜单栏选择 <strong>Tools > Android > SDK Manager</strong>。</p>
<p>2.点击 <strong>Tools > External Tools</strong></p>
<p><strong><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190628105906709-1993670310.png" alt="" width="726" height="370"></strong></p>
<p>3.点击OK保存。这时候就可以在菜单栏中看到javah选项,后续就可以直接右键生成java文件对应的C++头文件了。</p>
<pre><code><span class="pln"><span class="pun"><span class="pln"><span class="pun"><code><code> <img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190628110247258-921602853.png" alt="" width="311" height="357"></code></code></span></span></span></span></code></pre>
<p>四、简单的Demo</p>
<p>1.创建Android 工程,在java同级目录下创建jni目录,并创建Android.mk和Application.mk文件</p>
<p>2.在Android.mk中将原生库添加到 LOCAL_LDLIBS变量中,例如要链接liblog.so,添加代码如下:<code><br></code></p>
<p>LOCAL_LDLIBS := -llog; //编译系统会自动链接标准 C 库和 C++ 库,无需在这里添加</p>
<p>使用空格作为分隔符列出多个原生库。</p>
<p>3.在Java层创建接口文件HelloWorld.java</p>
<div class="cnblogs_code">
<pre><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)"> HelloWorld {
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">native</span><span style="color: rgba(0, 0, 0, 1)"> String ToastHelloFromC(String javaStr);
}</span></pre>
</div>
<p>4.右键javah,生成对应的JNI头文件</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">JNIEXPORT jstring JNICALL Java_com_axu_ndkdemo_HelloWorld_ToastHelloFromC
(JNIEnv </span>*, jobject, jstring);</pre>
</div>
<p>5.根据上一步生成的头文件生成C++源文件并编写逻辑</p>
<div class="cnblogs_code">
<pre>jstring JNICALL Java_com_axu_ndkdemo_HelloWorld_ToastHelloFromC(JNIEnv *<span style="color: rgba(0, 0, 0, 1)"> env, jobject jobj, jstring javaStr)
{
</span><span style="color: rgba(0, 0, 255, 1)">const</span> <span style="color: rgba(0, 0, 255, 1)">char</span> * javaChar_ptr = env -><span style="color: rgba(0, 0, 0, 1)"> GetStringUTFChars(javaStr,NULL);
</span><span style="color: rgba(0, 0, 255, 1)">char</span> jniChar[] = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">and 我来自JNI</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">char</span>* result_ptr = (<span style="color: rgba(0, 0, 255, 1)">char</span>*)<span style="color: rgba(0, 0, 255, 1)">malloc</span>(strlen(javaChar_ptr) + strlen(jniChar) + <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">);
strcpy(result_ptr, javaChar_ptr);
strcat(result_ptr, jniChar);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> env -><span style="color: rgba(0, 0, 0, 1)"> NewStringUTF(result_ptr);
}</span></pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190628110502990-1411433286.png" alt=""></p>
<p>6.完善Android.mk和Application.mk</p>
<p>Android.mk:</p>
<div class="cnblogs_code">
<pre>LOCAL_PATH := $(call my-<span style="color: rgba(0, 0, 0, 1)">dir)
######## build hello.so start #########
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION :</span>=<span style="color: rgba(0, 0, 0, 1)"> .cpp
LOCAL_MODULE :</span>=<span style="color: rgba(0, 0, 0, 1)"> hello
LOCAL_SRC_FILES :</span>=<span style="color: rgba(0, 0, 0, 1)"> com_axu_ndkdemo_HelloWorld.cpp
#LOCAL_C_INCLUDES </span>+=<span style="color: rgba(0, 0, 0, 1)"> com_axu_ndkdemo_HelloWorld.h
LOCAL_CPPFLAGS:</span>= -frtti -fexceptions --std=c++11 -DANDROID -<span style="color: rgba(0, 0, 0, 1)">DNDEBUG
include $(BUILD_SHARED_LIBRARY)
######### build hello.so end ##########</span></pre>
</div>
<p>Application.mk:</p>
<div class="cnblogs_code">
<pre>#APP_OPTIM :=<span style="color: rgba(0, 0, 0, 1)"> release
APP_ABI :</span>= armeabi-v7a arm64-<span style="color: rgba(0, 0, 0, 1)">v8a x86 x86_64
APP_PLATFORM :</span>= android-16<span style="color: rgba(0, 0, 0, 1)">
#APP_STL :</span>= c++_static</pre>
</div>
<p>7.在jni目录执行ndk-build命令,生成对应CPU架构的so文件</p>
<p><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190628111842046-1407380039.png" alt="" width="243" height="229"></p>
<p>8.配置app gradle 将so加载进去,并编写调用程序</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">sourceSets {
main {
jniLibs.srcDirs </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">src/main/libs</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
jni.srcDirs </span>=<span style="color: rgba(0, 0, 0, 1)"> []
}
}</span></pre>
</div>
<div class="cnblogs_code"><img id="code_img_closed_411c4022-0fc8-4897-a938-98245ccf8f19" class="code_img_closed" src="https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" alt=""><img id="code_img_opened_411c4022-0fc8-4897-a938-98245ccf8f19" class="code_img_opened" style="display: none" src="https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" alt="">
<div id="cnblogs_code_open_411c4022-0fc8-4897-a938-98245ccf8f19" class="cnblogs_code_hide">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> MainActivity <span style="color: rgba(0, 0, 255, 1)">extends</span><span style="color: rgba(0, 0, 0, 1)"> Activity {
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Button getJni_btn;
</span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> HelloWorld helloWorld;
</span><span style="color: rgba(0, 0, 255, 1)">static</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">{
System.loadLibrary(</span>"hello"<span style="color: rgba(0, 0, 0, 1)">);
}</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex){
ex.printStackTrace();
}
}
@Override
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onCreate(Bundle savedInstanceState) {
</span><span style="color: rgba(0, 0, 255, 1)">super</span><span style="color: rgba(0, 0, 0, 1)">.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
getJni_btn </span>=<span style="color: rgba(0, 0, 0, 1)"> (Button)findViewById(R.id.get_jni_btn);
getJni_btn.setOnClickListener(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> View.OnClickListener() {
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View v) {
String result </span>= helloWorld.ToastHelloFromC(" 我来自JAVA "<span style="color: rgba(0, 0, 0, 1)">);
Log.e(</span>"MainActivity"<span style="color: rgba(0, 0, 0, 1)">,result);
}
});
}
</span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> init(){
helloWorld </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HelloWorld();
}
}</span></pre>
</div>
<span class="cnblogs_code_collapse">View Code</span></div>
<p>9.运行程序,Logcat输出:</p>
<p><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190628112642425-1332314614.png" alt=""></p>
<p>四、熟练掌握JNI层与Java层交互</p>
<p>1、Java调用C代码</p>
<p>这种情况一般是正向调用,应用场景为java层应用需要使用C/C++层中的方法,常见于调用设备硬件驱动,优化算法,要求计算效率等接口中。</p>
<p>常见的形式有数值运算,字符串运算、数组运算以及复杂对象运算,下面分别举例说明</p>
<p>1)数值运算</p>
<p>2)字符串运算</p>
<p>3)数组运算</p>
<p>4)对象运算</p>
<p><img src="https://img2018.cnblogs.com/blog/320199/201906/320199-20190628153950190-294108479.png" alt=""></p>
<p>2、C代码调用Java</p>
<p> 这种情况可称为反向调用,最常见的应用场景为C/C++层需要将处理信息或者执行结果异步回调给java层。</p>
<p> 1)保存全局JVM实例和回调函数的实例对象</p>
<p>2)从全局jvm中获取当前线程的JNIEnv实例</p>
<p>3)获取回调函数的实例对象字节码jclass</p>
<p>4)通过GetMethodId方法获得回调函数的方法id</p>
<p>5)通过CallVoidMethod调用java方法</p>
<pre><br>需要注意的地方:<br>如果需要在jni层回调函数内处理java对象,分两种情况:<br>1.如果该对象在回调函数中使用到(传参或返回值),这可以直接通过FindClass找到该对象的字节码<br>2.如果该对象在回调函数中没有被使用到,那么就需要提前注册该类型的对象,然后在回调函数中取出对象进行使用<br><br>3、复杂对象的数据互通<br>复杂对象指对象列表,对象中嵌套对象。原理一样,当C解析java传过来的对象时,使用FindClass找到对象然后解析字段<br>当C组装java对象并传给java层时,需要先找到对象的构造方法id,之后通过构造方法进行对象赋值,从而创建对象,之后直接抛到java层,java层使用相应的对象接收即可。<br><br><br></pre><br><br>
来源:https://www.cnblogs.com/Em-Xu/p/11093074.html
頁:
[1]