Android NDK学习笔记1:基础
轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/zhaoyanjun6/article/details/119005718
本文出自【趙彥軍的博客】
文章目錄
- (一)什么是Android NDK
- (二)下載NDK
- (三)什么JNI?
- (四) JNI的三個(gè)角色
- (五) JNI的命名規(guī)則
- (六) 如何實(shí)現(xiàn)JNI
- (七)JNI原理
- JNIEnv是什么?
- JNIEnv和JavaVM的區(qū)別
- JNIEnv的作用
- JNIEnv與線程
- JNIEnv結(jié)構(gòu)
- 創(chuàng)建Java類中的String對象
- (八)ABI
- (九)JNI的引用
- 1、局部引用(Local Reference)
- 2、全局引用(Global Reference)
- 3、弱全局引用(Weak Global Reference)
- (十)示例
- (十一)查看Android 項(xiàng)目so
- (十二)CMakeLists.txt
- (十三)如何編譯多個(gè)平臺(tái)so
(一)什么是Android NDK
NDK 官網(wǎng):https://developer.android.google.cn/ndk/index.html
Android NDK 是一個(gè)工具集,可讓您使用 C 和 C++ 等語言以原生代碼實(shí)現(xiàn)應(yīng)用的各個(gè)部分。對于特定類型的應(yīng)用,這可以幫助您重復(fù)使用以這些語言編寫的代碼庫。
(二)下載NDK
1、下載 NDK ,下載完成后,下載的目錄在 android sdk 目錄里面
/Users/xmly/Library/Android/sdk/ndk
除此之外,你還可以自由下載其他版本的 ndk ,https://developer.android.com/ndk/downloads/revision_history
2、下載 CMake 工具
3、在 Android Studio local.properties 配置 ndk 目錄,示例如下:
sdk.dir=/Users/xmly/Library/Android/sdk ndk.dir=/Users/xmly/Library/Android/ndk-r19c(三)什么JNI?
Java調(diào)用C/C++在Java語言里面本來就有的,并非Android自創(chuàng)的,即JNI。JNI就是Java調(diào)用C++的規(guī)范。當(dāng)然,一般的Java程序使用的JNI標(biāo)準(zhǔn)可能和android不一樣,Android的JNI更簡單。
JNI,全稱為Java Native Interface,即Java本地接口,JNI是Java調(diào)用Native 語言的一種特性。通過JNI可以使得Java與C/C++機(jī)型交互。即可以在Java代碼中調(diào)用C/C++等語言的代碼或者在C/C++代碼中調(diào)用Java代碼。
由于JNI是JVM規(guī)范的一部分,因此可以將我們寫的JNI的程序在任何實(shí)現(xiàn)了JNI規(guī)范的Java虛擬機(jī)中運(yùn)行。
同時(shí),這個(gè)特性使我們可以復(fù)用以前用C/C++寫的大量代碼JNI是一種在Java虛擬機(jī)機(jī)制下的執(zhí)行代碼的標(biāo)準(zhǔn)機(jī)制。
代碼被編寫成匯編程序或者C/C++程序,并組裝為動(dòng)態(tài)庫。也就允許非靜態(tài)綁定用法。這提供了一個(gè)在Java平臺(tái)上調(diào)用C/C++的一種途徑,反之亦然。
PS:
開發(fā)JNI程序會(huì)受到系統(tǒng)環(huán)境限制,因?yàn)橛肅/C++ 語言寫出來的代碼或模塊,編譯過程當(dāng)中要依賴當(dāng)前操作系統(tǒng)環(huán)境所提供的一些庫函數(shù),并和本地庫鏈接在一起。而且編譯后生成的二進(jìn)制代碼只能在本地操作系統(tǒng)環(huán)境下運(yùn)行,因?yàn)椴煌牟僮飨到y(tǒng)環(huán)境,有自己的本地庫和CPU指令集,而且各個(gè)平臺(tái)對標(biāo)準(zhǔn)C/C++的規(guī)范和標(biāo)準(zhǔn)庫函數(shù)實(shí)現(xiàn)方式也有所區(qū)別。這就造成了各個(gè)平臺(tái)使用JNI接口的Java程序,不再像以前那樣自由的跨平臺(tái)。
如果要實(shí)現(xiàn)跨平臺(tái), 就必須將本地代碼在不同的操作系統(tǒng)平臺(tái)下編譯出相應(yīng)的動(dòng)態(tài)庫。比如在Android環(huán)境下,編譯成Android平臺(tái)的 .so 庫。在 Ios 環(huán)境下,需要編譯成 Ios 平臺(tái)的動(dòng)態(tài)庫。
(四) JNI的三個(gè)角色
三者的關(guān)系
JNI下一共涉及到三個(gè)角色:C/C++代碼、本地方法接口類、Java層中具體業(yè)務(wù)類。
JNI簡要流程
(五) JNI的命名規(guī)則
隨便舉例如下:
JNIExport jstring JNICALL Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz )- jstring 是返回值類型
- Java_com_example_hellojni 是包名
- MainActivity 是類名
- stringFromJNI 是方法名
其中JNIExport和JNICALL是不固定保留的關(guān)鍵字不要修改
定義 native 方法:
java 類
native String stringFromJNI();kotlin 類
external fun stringFromJNI(): Stringexternal 相當(dāng)于 native
(六) 如何實(shí)現(xiàn)JNI
JNI開發(fā)流程的步驟:
- 第1步:在Java中先聲明一個(gè)native方法
- 第2步:編譯Java源文件javac得到.class文件
- 第3步:通過javah -jni命令導(dǎo)出JNI的.h頭文件
- 第4步:使用Java需要交互的本地代碼,實(shí)現(xiàn)在Java中聲明的Native方法(如果Java需要與C++交互,那么就用C++實(shí)現(xiàn)Java的Native方法。)
- 第5步:將本地代碼編譯成動(dòng)態(tài)庫(Windows系統(tǒng)下是.dll文件,如果是Linux系統(tǒng)下是.so文件,如果是Mac系統(tǒng)下是.jnilib)
- 第6步:通過Java命令執(zhí)行Java程序,最終實(shí)現(xiàn)Java調(diào)用本地代碼。
(七)JNI原理
JNIEnv是什么?
JNIEnv是一個(gè)線程相關(guān)的結(jié)構(gòu)體,該結(jié)構(gòu)體代表了Java在本線程的執(zhí)行環(huán)境
JNIEnv和JavaVM的區(qū)別
- JavaVM:JavaVM是Java虛擬機(jī)在JNI層的代表,JNI全局僅僅有一個(gè)
- JNIEnv:JavaVM 在線程中的代碼,每個(gè)線程都有一個(gè),JNI可能有非常多個(gè)JNIEnv;
JNIEnv的作用
- 調(diào)用Java 函數(shù):JNIEnv代表了Java執(zhí)行環(huán)境,能夠使用JNIEnv調(diào)用Java中的代碼
- 操作Java代碼:Java對象傳入JNI層就是jobject對象,需要使用JNIEnv來操作這個(gè)Java對象
JNIEnv與線程
JNIEnv是線程相關(guān)的,即在每一個(gè)線程中都有一個(gè)JNIEnv指針,每個(gè)JNIEnv都是線程專有的,其他線程不能使用本線程中的JNIEnv,即線程A不能調(diào)用線程B的JNIEnv。所以JNIEnv不能跨線程。
- JNIEnv只在當(dāng)前線程有效:JNIEnv僅僅在當(dāng)前線程有效,JNIEnv不能在線程之間進(jìn)行傳遞,在同一個(gè)線程中,多次調(diào)用JNI層方便,傳入的JNIEnv是同樣的
- 本地方法匹配多個(gè)JNIEnv:在Java層定義的本地方法,能夠在不同的線程調(diào)用,因此能夠接受不同的JNIEnv
JNIEnv結(jié)構(gòu)
JNIEnv是一個(gè)指針,指向一個(gè)線程相關(guān)的結(jié)構(gòu),線程相關(guān)結(jié)構(gòu),線程相關(guān)結(jié)構(gòu)指向JNI函數(shù)指針數(shù)組,這個(gè)數(shù)組中存放了大量的JNI函數(shù)指針,這些指針指向了詳細(xì)的JNI函數(shù)。
創(chuàng)建Java類中的String對象
jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len):通過Unicode字符的數(shù)組來創(chuàng)建一個(gè)新的String對象。
env是JNI接口指針;unicodeChars是指向Unicode字符串的指針;len是Unicode字符串的長度。返回值是Java字符串對象,如果無法構(gòu)造該字符串,則為null。
那有沒有一個(gè)直接直接new一個(gè)utf-8的字符串的方法呢?答案是有的
就是jstring NewStringUTF(JNIEnv *env, const char *bytes)這個(gè)方法就是直接new一個(gè)編碼為utf-8的字符串。
其他方法:
- NewArray Routines 返回值:Array Type
- NewBooleanArray() 返回值:jbooleanArray
- NewByteArray() 返回值:jbyteArray
- NewCharArray() 返回值:jcharArray
- NewShortArray() 返回值:jshortArray
- NewIntArray() 返回值:jintArray
- NewLongArray() 返回值:jlongArray
- NewFloatArray() 返回值:jfloatArray
- NewDoubleArray() 返回值:jdoubleArray
(八)ABI
(九)JNI的引用
Java內(nèi)存管理這塊是完全透明的,new一個(gè)實(shí)例時(shí),只知道創(chuàng)建這個(gè)類的實(shí)例后,會(huì)返回這個(gè)實(shí)例的一個(gè)引用,然后拿著這個(gè)引用去訪問它的成員(屬性、方法),完全不用管JVM內(nèi)部是怎么實(shí)現(xiàn)的,如何為新建的對象申請內(nèi)存,使用完之后如何釋放內(nèi)存,只需要知道有個(gè)垃圾回收器在處理這些事情就行了,然而,從Java虛擬機(jī)創(chuàng)建的對象傳到C/C++代碼就會(huì)產(chǎn)生引用,根據(jù)Java的垃圾回收機(jī)制,只要有引用存在就不會(huì)觸發(fā)該該引用所指向Java對象的垃圾回收。
在JNI規(guī)范中定義了三種引用:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。區(qū)別如下:
1、局部引用(Local Reference)
局部引用,也成本地引用,通常是在函數(shù)中創(chuàng)建并使用。會(huì)阻止GC回收所有引用對象。
最常見的引用類型,基本上通過JNI返回來的引用都是局部引用,例如使用NewObject,就會(huì)返回創(chuàng)建出來的實(shí)例的局部引用,局部引用值在該native函數(shù)有效,所有在該函數(shù)中產(chǎn)生的局部引用,都會(huì)在函數(shù)返回的時(shí)候自動(dòng)釋放(freed),也可以使用DeleteLocalRef函數(shù)手動(dòng)釋放該應(yīng)用。
之所以使用DeleteLocalRef函數(shù):實(shí)際上局部引用存在,就會(huì)防止其指向?qū)ο蟊焕厥掌诨厥?#xff0c;尤其是當(dāng)一個(gè)局部變量引用指向一個(gè)很龐大的對象,或是在一個(gè)循環(huán)中生成一個(gè)局部引用,最好的做法就是在使用完該對象后,或在該循環(huán)尾部把這個(gè)引用是釋放掉,以確保在垃圾回收器被觸發(fā)的時(shí)候被回收。在局部引用的有效期中,可以傳遞到別的本地函數(shù)中,要強(qiáng)調(diào)的是它的有效期仍然只是在第一次的Java本地函數(shù)調(diào)用中,所以千萬不能用C++全部變量保存它或是把它定義為C++靜態(tài)局部變量。
2、全局引用(Global Reference)
全局引用可以跨方法、跨線程使用,直到被開發(fā)者顯式釋放。類似局部引用,一個(gè)全局引用在被釋放前保證引用對象不被GC回收。和局部應(yīng)用不同的是,沒有那么多函數(shù)能夠創(chuàng)建全局引用。能創(chuàng)建全部引用的函數(shù)只有NewGlobalRef,而釋放它需要使用ReleaseGlobalRef函數(shù)
3、弱全局引用(Weak Global Reference)
是JDK 1.2新增加的功能,與全局引用類似,創(chuàng)建跟刪除都需要由編程人員來進(jìn)行,這種引用與全局引用一樣可以在多個(gè)本地方法有效,不一樣的是,弱引用將不會(huì)阻止垃圾回收期回收這個(gè)引用所指向的對象,所以在使用時(shí)需要多加小心,它所引用的對象可能是不存在的或者已經(jīng)被回收。
引用比較
在給定兩個(gè)引用,不管是什么引用,我們只需要調(diào)用IsSameObject函數(shù)來判斷他們是否是指向相同的對象。代碼如下:
(*env)->IsSameObject(env, obj1, obj2)如果obj1和obj2指向相同的對象,則返回JNI_TRUE(或者1),否則返回JNI_FALSE(或者0)
(十)示例
Java代碼
package com.example.myapplication;public class JniBridge {native String stringFromJNI();native String stringFromJNI2(String message);native double doubleFromJNI(double value);native int sumFromJNI(int value); }native-lib.cpp
#include <jni.h> #include <string> #include <float.h>extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication_JniBridge_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str()); }extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication_JniBridge_stringFromJNI2(JNIEnv *env, jobject thiz,jstring _message) {//獲取 c 語言層面的字符串,會(huì)分配內(nèi)存const char * str = env->GetStringUTFChars(_message, JNI_FALSE);//釋放c風(fēng)格的對象內(nèi)存,一旦釋放,str將沒有用env->ReleaseStringUTFChars(_message,str);//獲取字符串的長度int length = env->GetStringLength(_message);//獲取一段字符串,類似于java的截取char buffer[128];env->GetStringUTFRegion(_message,0,length-1,buffer);//c風(fēng)格的字符串轉(zhuǎn)換成jstringreturn env->NewStringUTF(""); }extern "C" JNIEXPORT jdouble JNICALL Java_com_example_myapplication_JniBridge_doubleFromJNI(JNIEnv *env, jobject thiz, jdouble value) {return value * 3; }extern "C" JNIEXPORT jint JNICALL Java_com_example_myapplication_JniBridge_sumFromJNI(JNIEnv *env, jobject thiz, jint value) {return value * 2; }(十一)查看Android 項(xiàng)目so
如何查看編譯出來的 so 文件,有兩種方式
第一種在 build 文件夾中查看
第二種反編譯 .apk 文件
(十二)CMakeLists.txt
當(dāng)你創(chuàng)建了一個(gè) JNI Android 工程的時(shí)候,會(huì)自帶創(chuàng)建一個(gè) CMakeLists.txt 文件
# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.10.2)# Declares and names the project.project("myapplication")# Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).native-lib.cpp )# Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build.find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log )# Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library.native-lib# Links the target library to the log library# included in the NDK.${log-lib} )ADD_LIBRARY()語法
add_library(<name> [STATIC | SHARED | MODULE][EXCLUDE_FROM_ALL]source1 [source2 ...]) :庫的名字,直接寫名字即可。
[STATIC | SHARED | MODULE] :類型有三種。
當(dāng)一個(gè)類中方法被一個(gè) cpp類實(shí)現(xiàn),source 選項(xiàng)只寫一個(gè)就行。如果是被 兩個(gè) cpp 實(shí)現(xiàn),就需要添加多個(gè) source
舉例:
public class JniBridge {//native2.cpp 實(shí)現(xiàn)native String stringFromJNI();native String stringFromJNI2(String message);native double doubleFromJNI(double value);native int sumFromJNI(int value);//native2.cpp 實(shí)現(xiàn)native int sum2(int value); }添加多個(gè) source
add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).native-lib.cpp native2.cpp )(十三)如何編譯多個(gè)平臺(tái)so
defaultConfig {applicationId "com.example.myapplication"minSdkVersion 16targetSdkVersion 30versionCode 1versionName "1.0"externalNativeBuild {ndk {abiFilters 'armeabi-v7a', 'arm64-v8a'}}}總結(jié)
以上是生活随笔為你收集整理的Android NDK学习笔记1:基础的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android属性动画 实战-视差动画
- 下一篇: android sina oauth2.