# [TOC] [Android開發中的NDK到底是什麼?(詳細解析+案例) - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/415536928) # NDK介紹 **(1)簡介** **定義:**`Native Development Kit`,是 ` ...
目錄
Android開發中的NDK到底是什麼?(詳細解析+案例) - 知乎 (zhihu.com)
NDK介紹
(1)簡介
定義:Native Development Kit
,是 Android
的一個工具開發包
- NDK是屬於
Android
的,與Java
並無直接關係
作用:快速開發C
、 C++
的動態庫,並自動將so
和應用一起打包成 APK
- 即可通過
NDK
在Android
中 使用JNI
與本地代碼(如C、C++)交互
應用場景:在Android的場景下 使用JNI
- 即
Android
開發的功能需要本地代碼(C/C++)實現
java調用c的步驟
-
package com.fs.test; public class HelloWorld { //本地方法 private native void show(); public static void main(String[] args) { System.loadL ibrary("wgr"); new HelloWorld().show(); } }
1.System.loadL ibrary("wgr");載入動態庫
2.private native void show();聲明一個動態庫對應的本地方法,調用libwgr.so中的函數
*void Java_ com_ fs_ test HelloWorld_ show(JNIEnv env, jobject thiz)3.直接調用show函數(動態庫中與show對應的函數)
一、NDK/JNI
NDK
NDK(Native Development Kit)-原生開發工具包,使得能夠在Android上
去使用C/C++代碼;
JNI
JNI即Java Native Interface,Java和Native介面,就是Java和C/C++之間通訊的橋梁; 為什麼要有JNI,因為Java和C/C++之間是無法直接相互調用的,也就是無法直接通訊,就和Java和JS之間也不能直接相互調用,中間需要翻譯者來處理,JNI就是為了實現Java和C/C++之間這兩種不同語言之間的通訊的;
JNI並不是Android中的技術,它是Java本身就具有的功能,是由JVM支持JNI功能
-那麼為什麼需要在Android上去使用C/C++代碼?
因為我們知道開發Android我們是使用Java/Kotlin語言進行開發的,那麼不論是Java還是Kotlin,最終都是編譯成位元組碼文件.dex文件,然而位元組碼文件是Java/Kotlin經過一定語義編譯之後的產物,並不是完全可以運行的機器碼指令;所以運行應用時還是需要進一步的編譯成機器碼才能夠執行;
而C/C++是完全的編譯型語言,編譯之後是機器碼文件,運行應用時,可以直接執行的,所以效率肯定是要比Java/Kotlin要高的;所以一些對一些比較耗性能的操作,如音視頻編解碼,圖像處理等操作,最好還是交由C/C++代碼中去執行,這樣能夠大大提高應用的執行效率;
NDK開發
新建一個Native項目
Android Studio 新建一個項目,新建項目時,項目模板選擇Native C++
新建項目成功後,我們可以看一下項目的目錄結構;
在src/main目錄下 有一個cpp目錄,與java目錄同級,cpp目錄下有一個 CMakeLists.txt文件和native-lib.cpp
CMkeLists.txt就是CMake腳本文件
native-lib.cpp 是C++源文件
app Module build.gradle中會有一些NDk相關的配置:
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.nativec'
compileSdk 33
defaultConfig {
applicationId "com.example.nativec"
minSdk 24
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.22.1'
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
設置調試模式
在Run/Edit Configuration/Debug Configurations中 Debugger 調試有四個選項
Auto、 自動檢測
Java、 只調試Java代碼
Native、 只調試Native代碼
Dual、 Java+Native兩種都用
註意不要選擇Java和Native,選擇Auto或者Dual
編寫編譯腳本文件CMakeLists.txt
CMake是一個跨平臺的C++編譯工具,所謂編譯就是把C++代碼編譯成靜態庫或者動態庫
CMakeLists.txt就是CMake的編譯腳本文件,
CMake編譯C++是通過CMakeLists.txt編譯腳本來進行編譯的;
CMakeLists.txt常用編譯腳本
(1)include_directories(dir_path)
//設置頭文件的查找目錄,可以使用多次include_directories設置多個頭文件
//查找目錄
舉例: include_directories(${CMAKE_CURRENT_SOURCE_DIR}/giflib)
(2)add_library(編譯生成的庫名,
編譯生成庫的類型(STATIC/SHARED),
編譯的庫所使用的C/C++源文件(.c/.cpp文件)(可能會有很多)
.
.
.
) //指定編譯生成庫
舉例:add_library(
native-lib
SHARED
native-lib.cpp)
上面這個add_library就指定了編譯生成的庫的名字為native-lib,庫的類型為
動態庫,庫所使用或者依賴的所以C/C++源文件只有一個native-lib.cpp
這裡編譯native-lib庫只使用了一個C/C++源文件native-lib.cpp,但是大多情況下,
編譯庫,可能會使用依賴到很多C/C++源文件;
如:
add_library(
native-lib
SHARED
native-lib.cpp,
a.cpp,
b.cpp
....
)
這樣把所有使用到的C/C++源文件一個個寫進來是可以的,但是當非常多的時候,就很容易寫漏,
而且也很麻煩; 可以使用file() 這個腳本,來設置C/C++的查找路徑的別名,最終使用這個查找路徑
別名,就可以替代寫這個路徑下的所有C/C++源文件了
(3) file(GLOB 別名 某路徑下某種類型文件) 查找某路徑下所有某種類型文件
舉例:
#設置查找GifLib庫實現源文件(.c)文件目錄
file(GLOB giflib_C_DIR ${CMAKE_CURRENT_SOURCE_DIR}/giflib/*.c)
#設置自己編寫的C++實現源文件(.CPP)查找目錄
file(GLOB CSUTOM_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
然後上面說到的add_library()設置編譯生成庫依賴很多C/C++源文件的是偶,就可以使用
file()設置C/C++源文件查找別名
add_library(
native-lib
SHARED
${CSUTOM_CPP_DIR}
${giflib_C_DIR}
)
(4) target_link_libraries(生成庫的名字,
生成庫所鏈接的或者說是使用到的其他動態庫或者靜態庫
) //設置生成庫使用到的所有其他動態庫或者靜態庫
舉例:
target_link_libraries(
native-lib
a
b
....
)
生成庫使用到了哪些其他動態庫或者靜態庫都要寫進來,如果很多的情況下一個個寫就很麻煩了
和add_library()寫使用到的所有C/C++源文件一樣,也可以藉助file()來設置使用到的動態庫或者
靜態庫文件查找路徑別名;
#查找所有so庫
file(GLOB SO_DIR ../../../libs/${CMAKE_ANDROID_ARCH_ABI}/*.so)
target_link_libraries( # Specifies the target library.
native-lib
${SO_DIR}
)
(5) set(變數名 變數值) //用於定義一個變數指代後面的變數值
比如但我們需要使用一個目錄路徑的時候,這個目錄路徑很長,並且在CMakeLists.txt中
多個地方使用到,我們可以通過set()來定義一個變數指代這個目錄路徑,後面在使用到這個
路徑的時候,使用這個簡單的變數來替代;
set(LINK_DIR ../../../../libs/${CMAKE_ANDROID_ARCH_ABI})
file(GLOB SO_DIR ${LINK_DIR}/*.so)
(6)find_library(庫的別名 庫名) //從系統查找依賴庫
find_library(
log-lib
log)
find_library(
jnigraphics-lib
jnigraphics
)
target_link_libraries( # Specifies the target library.
native-lib
${SO_DIR}
${log-lib}
${jnigraphics}
)
log和jnigraphics都是NDK中自帶的動態鏈接庫
新建Native項目後,CMakeLists.txt預設已經有了一些一些基本配置,可以讓我們正常編譯使用native-lib.cpp了;
# 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.22.1)
# Declares and names the project.
project("nativec")
# 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.
nativec
# 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.
nativec
# Links the target library to the log library
# included in the NDK.
${log-lib})
載入C/C++庫
Android 系統 API提供了兩種載入C/C++庫的方式:
一種是直接載入APK中的C/C++庫文件;
第二種是通過文件地址來載入C/C++庫文件;
(1)載入APK中的C/C++庫文件
庫文件可能是動態庫so庫,或者是靜態庫.a,或者是C/C++源文件
載入方式:
static {
System.loadLibrary("native-lib"); //註意不要寫庫文件擴展名
}
(2)載入外部的C/C++ so庫
如SD卡下的C/C++庫 ,這裡要註意的是,如果是載入外部的庫文件,只能載入動態庫so庫,並且這個庫文件的位置只能在/system/lib目錄下,或者是在應用的安裝包目錄下/data/data/packagename/,
而且這兩個路勁又是有許可權保護的不能直接訪問,所以一般我們是將庫文件複製到我們應用自己的應用目錄下/data/data/packagename/com.xxx.xxx
System.load(" C/C++庫文件的絕對路徑(註意不需要寫庫文件擴展名)");
實現Java和C++互相調用
因為我們新建的項目模板是Native C++,所以項目中已經自動為我們創建了一個native-lib.cpp,C++源代碼文件,MainActivtiy中也聲明瞭一個native方法與native-lib.cpp中對應的native方法對應,並且在MainActivtiy中調用了native方法;
來演示Java中調用C/C++方法
下麵我們來分析一下native方法:
Native方法中的包名要和Java方法中的包名對應上,Native層中JNI方法命名格式
為Java_包名類名方法名 之間全部是下劃線分隔 :
Java_com_nado_jniproject_MainActivity_stringFromJNI
參數:JNIEnv *env和jobject thiz
這個jobject類型的thiz就是對應外層的Java對象 jobject就是Java中的Object
Java類型 C類型 以及 JNI別名 對應如下:
Java 類型 JNI 別名 C 類型
boolean jboolean unsigned char
byte jbyte signed char
char jchar unsigned short
short jshort short
int jint int
long jlong long
float jfloat float
double jdouble double
String jstring char*
Class jclass /
Object jobject /
由於Java和C語言之間是無法直接調用的,兩種語言的基本類型是不一樣的,例如Java中有boolean類型,而在C中就沒有這種類型,但是C語言還是有if-else判斷的,那怎麼判斷true或者false? C中使用char類型,當char的值是0就是false非0就是true;基於這種情況,JNI重新定義了一些類型,以便C和Java對應上;
運行應用,我們可以在MainActivity中成功的顯示從調用C/C++方法獲取到的字元串
上面我們學習了Java中調用C/C++方法,為了清楚的瞭解Java和C之間的互相調用,還需要學習一下Native中調用Java方法
生成so文件
build一下,在這裡找到生成的so文件
參考
https://blog.csdn.net/mq2856992713/article/details/118090046