背景:
在越来越卷的安卓生态中,一名安卓开发不仅要懂四大组件、Handler、View绘制与事件分发、RecyclerView、动画、JetPack、组件化、插件化、热修复、性能优化、framework、各种开源框架OKhttp、Retrofit、Eventbus、MMKV等等。近年来开始卷到了Native层,NDK开发是安卓领域必备的技能。项目开发过程中,往往有需要在Native层开发的场景:隐私数据加解密、音视频编解码、人脸检测追踪等AI能力......
本文是个人学习NDK开发小结:NDK开发流程、如何依赖外部So以及对自实现的cpp文件生成so、和JNI接口静态注册+动态注册。
一、NDK开发环境搭建
1) 个人使用的Android studio是
2)先加载NDK和CMake
3)新建一个nativeLib Module,个人比较喜欢用单独的Module 来做Native层逻辑,后续便于做组件化,如果写在app层,后续耦合严重。
4)在nativelib/src下新建一个CMakeLists.txt文件。
#构建库文件所需要CMake最小版本
cmake_minimum_required(VERSION 3.4.1)
# 设置生成的so动态库输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/jniLibs/${ANDROID_ABI})
#添加自己的C/C++源文件
add_library( # Sets the name of the library.
native_test
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native_test.cpp)
#添加依赖的NDK 库
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 )
#添加外部依赖的so
add_library(
aesutil
SHARED
importED)
set_target_properties(
aesutil
PROPERTIES importED_LOCATION
${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libaesutil.so
)
include_directories(src/main/cpp/include)
#将目标库与NDK 中的库链接
target_link_libraries( # Specifies the target library.
native_test
# links the target library to the log library
# included in the NDK.
aesutil
${log-lib} )
5) nativelib module的build.gradle文件里需要声明和ndk相关配置
android {
compileSdkVersion 30
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
//1. android-defaultConfig {}下 externalNativeBuild cmake path
externalNativeBuild {
cmake {
cppFlags ""
arguments "-DANDROID_STL=c++_shared"
}
}
//2. android-defaultConfig {}下 选择生成的CPU 架构, android defaultConfig下定义
ndk{
abiFilters "armeabi-v7a", "arm64-v8a"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
//3. android{}下 externalNativeBuild cmake path
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
二、依赖外部so和编译自实现的so以及JNI接口调用
1)在nativelib/src/main/java/包名 下 新建一个类JNIHelper.java类 用于写JNI接口。
package com.mikel.nativelib;
public class JNIHelper {
static {
System.loadLibrary("native_test");
}
public static native String testJNI();
public static native String encryptJNI(String originStr);
public static native String decryptJNI(String enCodeStr);
public static native String dynamicMethodTest(int intValue, float floatValue, double doublevalue, byte btevalue,
String strValue, boolean boolValue,
int[] intArrayValue, float[] floatArrayValue, double[] doubleArrayValue,
byte[] byteArrayValue, boolean[] boolArrayValue);
}
2)在nativelib/src/main 下 新建一个cpp文件夹,并且新建目录include用于存放第三方依赖的cpp头文件,新建一个native_test.cpp用于实现JNI接口。
静态注册
优点:实现简单;缺点:JNI函数名很长,需要捆绑包名和类名,运行时进行匹配 影响性能
动态注册
优点:System.loadLibrary的时候调用JNI_onLoad进行注册,提高性能;
#include#include #include #include #include "aes.h" #include static const char *TAG = "native_test"; #define LOGI(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args) #define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args) #define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args) extern "C" { JNIEXPORT jstring JNICALL Java_com_mikel_nativelib_JNIHelper_testJNI( JNIEnv *env, jclass thiz) { char *helloworld = "hello world in C++"; return env->NewStringUTF(helloworld); } JNIEXPORT jstring JNICALL Java_com_mikel_nativelib_JNIHelper_encryptJNI(JNIEnv *env, jobject thiz, jstring origin) { const char *origin_str; const char *key_str = "123456789abcdef"; origin_str = env->GetStringUTFChars(origin, 0); char encrypt_str[1024] = {0}; AES aes_en((unsigned char *) key_str); aes_en.Cipher((char *) origin_str, encrypt_str); return env->NewStringUTF(encrypt_str); } JNIEXPORT jstring JNICALL Java_com_mikel_nativelib_JNIHelper_decryptJNI(JNIEnv *env, jobject thiz, jstring des) { const char *des_str; const char *key_str = "123456789abcdef"; des_str = env->GetStringUTFChars(des, 0); char decrypt_str[1024] = {0}; AES aes_de((unsigned char *) key_str); aes_de.InvCipher((char *) des_str, decrypt_str); return env->NewStringUTF(decrypt_str); } jstring dynamic_method_test(JNIEnv *env, jobject thiz, jint intValue, jfloat floatValue, jdouble doublevalue, jbyte bytevalue, jstring strValue, jboolean boolValue, jintArray intArrayValue, jfloatArray floatArrayValue, jdoubleArray doubleArrayValue, jbyteArray byteArrayValue, jbooleanArray boolArrayValue) { //转指针 const char* strBuf = env->GetStringUTFChars(strValue, nullptr); jint* intBuf = env->GetIntArrayElements(intArrayValue, nullptr); jfloat* floatBuf = env->GetFloatArrayElements(floatArrayValue, nullptr); jdouble* doubleBuf = env->GetDoubleArrayElements(doubleArrayValue, nullptr); jbyte* byteBuf = env->GetByteArrayElements(byteArrayValue, nullptr); jboolean* boolBuf = env->GetBooleanArrayElements(boolArrayValue, nullptr); //handle buf //释放指针 env->ReleaseStringUTFChars(strValue, strBuf); env->ReleaseIntArrayElements(intArrayValue, intBuf, 0); env->ReleaseFloatArrayElements(floatArrayValue, floatBuf, 0); env->ReleaseDoubleArrayElements(doubleArrayValue, doubleBuf, 0); env->ReleaseByteArrayElements(byteArrayValue, byteBuf, 0); env->ReleaseBooleanArrayElements(boolArrayValue, boolBuf, 0); return env->NewStringUTF(strBuf); } static JNINativeMethod dynamicMethods[] = { //public String dynamicMethodTest(int a, float b, double c, byte d, String e, boolean f, int[] g, // float[] h, double[] i, byte[] j, boolean[] l) {"dynamicMethodTest","(IFDBLjava/lang/String;Z[I[F[D[B[Z)Ljava/lang/String;",(void*)dynamic_method_test}, }; static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* dynamicMethods,int methodsNum){ jclass clazz; clazz = env->FindClass(className); if(clazz == NULL){ return JNI_FALSE; } //env->RegisterNatives 注册函数需要参数:java类,动态注册函数的数组,动态注册函数的个数 if(env->RegisterNatives(clazz, dynamicMethods, methodsNum) < 0){ return JNI_FALSE; } return JNI_TRUE; } //指定java层的类路径,然后动态注册函数 static int registerNatives(JNIEnv* env){ const char* className = "com/mikel/nativelib/JNIHelper"; return registerNativeMethods(env, className, dynamicMethods, sizeof(dynamicMethods)/ sizeof(dynamicMethods[0])); } JNIEXPORT jint JNICALL JNI_onLoad(JavaVM* vm, void* reserved){ JNIEnv* env = NULL; //通过Java虚拟机获取JNIEnv int result = vm->GetEnv(reinterpret_cast (&env), JNI_VERSION_1_6); if (result != JNI_OK) { return -1; } assert(env != NULL); if(!registerNatives(env)){ return -1; } //返回JNI版本 return JNI_VERSION_1_6; } }
3) 在nativelib/src/main 下新建一个文件夹jniLibs用于存放外部依赖的so。这里选择v7a和v8a的cpu架构,基本可以涵盖绝大部分机型。
4) build-make project 可以生成自己的so
三、效果:
参考:
Android中利用C语言进行AES加解密_水月洞天-CSDN博客
Android开发NDK调用三方so库 - 简书
Demo地址:
CODING | 一站式软件研发管理平台



