背景:
在越来越卷的安卓生态中,一名安卓开发不仅要懂四大组件、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");
}
/***
* 静态注册
* @return
*/
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 <jni.h>
#include <string.h>
#include <stdlib.h>
#include <android/log.h>
#include "aes.h"
#include <cassert>
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<void**>(&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 | 一站式软件研发管理平台
|