一、環境配置
1、NDK簡介
Android NDK(Native Development Kit),即Android本地開發工具包,是Google為了允許開發者使用C/C++編寫本地代碼並在Android設備上運行而提供的一份工具和支持庫。NDK提供了一系列系統頭文件、函數庫、調試器等工具,同時也提供了支持C++代碼的STL等庫文件。使用NDK可以加速CPU密集型任務和讓現有C/C++代碼可以在Android系統上運行。
2、安裝NDK
首先,要在Android Studio中下載NDK,打開新建工程中的Gradle Scripts->build.gradle(Project:為你的項目名),將「classpath ‘com.android.tools.build:gradle:x.x.x’」中的「x.x.x」修改成你的AS版本所需的gradle版本,然後再在app->build.gradle文件中添加以下內容:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
...
//指向 CMakeLists.txt 文件所在的外部編譯目錄的絕對路徑
path "src/main/cpp/CMakeLists.txt"
}
}
}
buildTypes {
...
externalNativeBuild {
cmake {
...
//指向 CMakeLists.txt 文件所在的外部編譯目錄的絕對路徑
path "src/main/cpp/CMakeLists.txt"
}
}
}
externalNativeBuild {
// 定義ndk-build編譯腳本的庫
ndkBuild {
//指向Android.mk文件所在的絕對路徑
path "src/main/jni/Android.mk"
}
}
}
這裡以使用CMake為編譯系統為例,在app->build.gradle文件中添加以下內容:
android {
...
defaultConfig {
...
// 指定支持abi
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
}
externalNativeBuild {
cmake {
//指向 CMakeLists.txt 文件所在的外部編譯目錄的絕對路徑
path file('src/main/cpp/CMakeLists.txt')
}
}
}
3、配置CMakeLists.txt
CMake是一個跨平台的編譯系統,Android NDK支持使用CMake_build原生代碼。打開Android項目中的app目錄,在cpp文件夾下創建一個新的CMakeLists.txt文件。以下是最簡單的示例CMakeLists.txt:
cmake_minimum_required(VERSION 3.4.1)
# 創建一個動態庫
add_library(
hello # 庫名稱
SHARED # 庫類型:SHARED表示動態庫
hello.c) # 庫源文件
# 導入log庫
find_library(
log-lib # 定義log庫名稱
log) # 指定庫名稱在系統中的實際文件名稱
# 將log庫鏈接到hello庫中
target_link_libraries(
hello # 目標庫名稱
${log-lib}) # 鏈接庫名稱
二、JNI開發
1、JNI簡介
Java Native Interface (JNI) 是一個Java平台編程接口,允許Java代碼和其他語言如C/C++程序進行交互。Java虛擬機提供一個標準的JNI接口,它可以被C/C++編譯器所使用,可以將Java垃圾回收機制與C/C++語言靈活地融合在一起。
2、JNI基礎
在Java中使用JNI調用本地代碼,需要在Java中定義native函數,並生成頭文件。以下是一個Java類中定義native函數的示例:
public class NdkJniUtils {
static {
System.loadLibrary("ndkJniLib");
}
public native String sayHello(String name);
}
使用以下命令生成頭文件:
javah -d jni -classpath {Java類的.class文件路徑} {Java全限定類名(包括包名)}
3、JNI實踐
以調用hello.c函數為例,先編寫.h文件:
#include <jni.h>
JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_MainActivity_hello(JNIEnv *env, jobject instance){
return (*env)->NewStringUTF(env, (const char *)"Hello NDK!");
}
在.c文件中實現hello.c函數:
#include <stdio.h>
#include <jni.h>
JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_MainActivity_hello(JNIEnv *env, jobject instance){
return (*env)->NewStringUTF(env, "Hello NDK!");
}
4、編譯
使用以下命令編譯:
gcc -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux hello.c -o libhello.so
將生成的libhello.so庫文件拷貝到app/src/main/jniLibs/armeabi-v7a/目錄下,通過System.loadLibrary()方法加載即可。
三、混合編程
1、混合編程簡介
將Java和C++混合編程的方式,稱之為混合編程。混合編程可以讓我們在Java層面上方便的使用C++的特性,也可以讓我們利用Java的強大庫來進行快速開發。
2、JNI編程注意點
在JNI編程中,有幾個重要的規則需要遵循:
- 盡量不要執行耗時任務
- 盡量避免崩潰或錯誤
- 避免資源泄露
- 鎖定Global Reference和Local Reference
3、示例代碼
代碼示例中將展示如何從Java層面調用C++庫。在Java文件中聲明native方法:
public static native int add(int a, int b);
在C++文件中實現add()函數,並將其包裝在JNICaller類中:
#include <jni.h>
using namespace std;
class JNICaller
{
public:
static void init(JNIEnv *env);//初始化
static int add(int a,int b); //導出給Java層面的native方法
};
void JNICaller::init(JNIEnv *env)
{
jclass jniCaller_clazz=env->FindClass("com/example/ndkdemo/JNICaller");//獲取Java層面的類的引用
jmethodID construct=jniEnv->GetMethodID(jniCaller_clazz,<init>,"()V");//獲取Java層面的構造函數的引用
//註冊native方法
JNINativeMethod methods[] =
{
{"add", "(II)I", (void *) JNICaller::add}
};
jniEnv->RegisterNatives(jniCaller_clazz, methods, sizeof(methods) / sizeof(methods[0]));
printf("init JNI caller success.\n");
}
int JNICaller::add(int a,int b)
{
return a+b;
}
在Java層面中調用add()方法:
JNICaller.init();//初始化JNICaller
int result = JNICaller.add(a, b);//在Java層面中調用JNICaller的add()方法
四、NDK調試
1、NDK調試簡介
NDK調試可以讓我們在本地開發環境中調試C++層代碼,可以極大的提高開發效率。NDK調試可以幫助我們在開發過程中及時發現bug並進行修復。NDK調試分為兩種類型:GDB和LLDB
2、NDK調試步驟
將以下代碼添加到app->build.gradle文件中:
externalNativeBuild {
cmake {
cmake {
// -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=libs
// 指定編譯生成文件的目錄
arguments "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${project.buildDir}/libs/${outputPath}"
// -DCMAKE_BUILD_TYPE=Debug
// 指定編譯類型為Debug模式
arguments "-DCMAKE_BUILD_TYPE=Debug"
// -DANDROID_TOOLCHAIN=clang
// 指定編譯器為Clang編譯器
arguments "-DANDROID_TOOLCHAIN=clang"
// -DANDROID_STL=c++_static
// 指定STL庫為c++_static
arguments "-DANDROID_STL=c++_static"
}
}
ndkBuild {
//同上
}
}
在Android Studio中打開「Edit Configurations」菜單,在「Debugger」選項卡中設置:
- Debugger Type為Auto
- Debug Type為Native
點擊「Run」菜單中的「Debug 'app'」進行調試
五、NDK壓縮
在開發完成之後,需要將so庫打包成apk文件。程序中使用的Shared Library(.so文件)原則上都應當打包進APK里。這樣的做法直接導致.apk文件的大小增加,不過現在主流的壓縮工具如upx等均對.so文件有壓縮能力。毫無疑問,對於 .so 文件而言,應該利用好這樣的能力來縮減APK包大小。
1、UPX
UPX是一個免費的、便攜式的、可擴展的、高性能的文件壓縮器,它是一個通用性的可執行文件壓縮器。UPX支持Linux、MacOS X和近年出現的Windows安裝包壓縮,UPX完全符合ELF, PE, MZ, COFF, NLM和OMF格式標準。在NDK中,我們使用UPX來對.so庫進行壓縮。
2、NDK壓縮步驟
在app/build.gradle文件中添加以下配置:
splits {
abi {
enable true
//This property should be set only to true for publishing part of your app as APK.
reset()
include "armeabi-v7a", "x86"
universalApk true
}
}
apply from: "../../tools/JniLibsCompression.gradle"
為了使用UPX,需要拷貝tools目錄中的JniLibsCompression.gradle,然後添加以下代碼到app/build.gradle文件:
apply from: "../../tools/JniLibsCompression.gradle"
project.ext.upx_enabled = true
project.ext.upx_options = "best --lzma"
android {
...
}
最後執行gradlew clean assembleRelease命令打包APK文件,生成的APK文件中的so庫已經被壓縮。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/301976.html