Android NDK开发1——开发流程+依赖外部so+生成自实现so+静态注册JNI+动态注册JNI

背景:

在越来越卷的安卓生态中,一名安卓开发不仅要懂四大组件、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地址:

GitHub - mikelhm/MikelProjectDemo: Personal Android Demo

猜你喜欢

转载自blog.csdn.net/xiaobaaidaba123/article/details/122634191