JNI实例演示

作者: 码上述Andy | 来源:发表于2019-07-06 17:11 被阅读10次

    1.前言

    为了更好的理解并应用前面所分享的内容,下面将实例演示JNI开发步骤。

    2.Native工程准备

    2.1SDK Manager 安装相应组件组件

    AS菜单栏中选择 Tools >SDK Manager,点击 SDK Tools 选项卡,勾选 LLDB,CMake 和 NDK。如下图:


    image.png

    2.2新建工程

    打开Android Studio--->File菜单--->New--->New Project然后打开如下对话框



    选择Native C++然后Next指定Name,PackageName等Next



    c++Standard 标准的C++,默认就好,最后Finish即可创建Native工程。

    3.开发步骤

    3.1JNI函数注册

    3.1.1静态注册方式生成对应的.h头文件
    java层编写native方法
      
    
        /**
         * A native method that is implemented by the 'native-lib' native library,
         * which is packaged with this application.
         */
        public native String stringFromJNI();
        public native String stringFromJNI2();
        public native String fromJaveToNative(String s);
        public native String signture(String sig);
        public native int add(int a, int b);
        public native void throwException(String msg);
        public String getPackageN() {
            return getPackageName();
        }
        public void fromNativeJNI() {
            Logger.getLogger("MainActivity").severe("fromNativeJNI str>>>>>>>");
        }
        public void fromNativeJNI2(String str) {
            Logger.getLogger("MainActivity").severe("fromNativeJNI str=" + str);
        }
        public static void fromNativeJNI3(String str) {
            Logger.getLogger("MainActivity").severe("fromNativeJNI str=" + str);
        }
        public static void fromNativeJNI4() {
            Logger.getLogger("MainActivity").severe("fromNativeJNI str4444444");
        }
        public int getValue() {
            Logger.getLogger("MainActivity").severe("fromNativeJNI getValue");
            return i;
        }
        public void modifyValue(int j) {
            Logger.getLogger("MainActivity").severe("fromNativeJNI str55555>>j=" + j);
        }
    

    通过javah生成jni对应的函数格式

    /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/javah -classpath . -jni -d /Users/zhouwen/work/NativeApp/app/src/main/jni jni.chowen.com.nativeapp.MainActivity
    

    执行完命令之后,在jni目录下会自动生成jni_chowen_com_nativeapp_MainActivity.h文件

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class jni_chowen_com_nativeapp_MainActivity */
    
    #ifndef _Included_jni_chowen_com_nativeapp_MainActivity
    #define _Included_jni_chowen_com_nativeapp_MainActivity
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     jni_chowen_com_nativeapp_MainActivity
     * Method:    passBitmap
     * Signature: (Ljava/lang/Object;)V
     */
    JNIEXPORT void JNICALL Java_jni_chowen_com_nativeapp_MainActivity_passBitmap
      (JNIEnv *, jobject, jobject);
    
    /*
     * Class:     jni_chowen_com_nativeapp_MainActivity
     * Method:    add
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_jni_chowen_com_nativeapp_MainActivity_add
      (JNIEnv *, jobject, jint, jint);
    
    /*
     * Class:     jni_chowen_com_nativeapp_MainActivity
     * Method:    stringFromJNI
     * Signature: ()Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_stringFromJNI
      (JNIEnv *, jobject);
    
    /*
     * Class:     jni_chowen_com_nativeapp_MainActivity
     * Method:    stringFromJNI2
     * Signature: ()Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_stringFromJNI2
      (JNIEnv *, jobject);
    
    /*
     * Class:     jni_chowen_com_nativeapp_MainActivity
     * Method:    fromJaveToNative
     * Signature: (Ljava/lang/String;)Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_fromJaveToNative
      (JNIEnv *, jobject, jstring);
    
    /*
     * Class:     jni_chowen_com_nativeapp_MainActivity
     * Method:    signture
     * Signature: (Ljava/lang/String;)Ljava/lang/String;
     */
    JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_signture
      (JNIEnv *, jobject, jstring);
    
    /*
     * Class:     jni_chowen_com_nativeapp_MainActivity
     * Method:    throwException
     * Signature: (Ljava/lang/String;)V
     */
    JNIEXPORT void JNICALL Java_jni_chowen_com_nativeapp_MainActivity_throwException
      (JNIEnv *, jobject, jstring);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    
    JNI层实现

    新建native-lib.cpp c++文件,并实现javah生成的jni_chowen_com_nativeapp_MainActivity.h以上.h文件函数

    #include <jni.h>
    #include <string>
    #include "jni_chowen_com_nativeapp_MainActivity.h"
    #include "jni_chowen_com_nativeapp_CInterface.h"
    #include "MD5.h"
    
    #include <android/log.h>
    
    #define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "threaded_app", __VA_ARGS__))
    #define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "threaded_app", __VA_ARGS__))
    
    using namespace std;
    
    jstring getPackname(JNIEnv *env, jobject clazz, jobject obj);
    
    jstring getSignature(JNIEnv *env, jobject clazz, jobject jobject1);
    
    int RegisterNatives(JNIEnv *env);
    
    //静态注册
    //extern "C" JNIEXPORT jstring JNICALL
    //Java_jni_chowen_com_nativeapp_MainActivity_stringFromJNI(
    //        JNIEnv *env,
    //        jobject /* this */) {
    //    string hello = "Hello from C++";
    //    jint jint4 = env->GetVersion();
    //    LOGE("JNI version: %d", jint4);
    //
    //    return env->NewStringUTF(hello.c_str());
    //}
    
    //动态注册
    static jstring stringFromJNI(
            JNIEnv *env,
            jobject jobject1/* this */) {
        string hello = "Hello from C++";
        jint jint4 = env->GetVersion();
        LOGE("JNI version: %d", jint4);
    
        return env->NewStringUTF(hello.c_str());
    }
    
    jint add(JNIEnv *env, jclass clazz, jint a, jint b) {
        return a + b;
    }
    
    JNIEXPORT void JNICALL Java_jni_chowen_com_nativeapp_MainActivity_throwException
            (JNIEnv *env, jobject jobject1, jstring jstring1) {
        jclass  jclass1 = env->FindClass("java/io/IOException");
        const char* c = env->GetStringUTFChars(jstring1, 0);
    
    //    env->FatalError(c); // 抛出一个致命异常
    
        jthrowable jthrowable1 = env->ExceptionOccurred();
    
        if (env->ThrowNew(jclass1, c) == JNI_OK){
            if(jthrowable1){
                LOGE("JNI throw suc ExceptionOccurred");
            }
            env->ExceptionDescribe();//打印exception msg
            env->ExceptionClear();
    
            LOGE("JNI throw suc");
        } else {
            LOGE("JNI throw failed");
        }
    }
    
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
        JNIEnv *env = NULL;
        LOGE("JNI_OnLoad RegisterNatives JNI_OnLoad");
        if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
            return JNI_ERR;
        }
        jint result = RegisterNatives(env);
        LOGE("RegisterNatives result: %d", result);
        return JNI_VERSION_1_6;
    }
    
    
    int RegisterNatives(JNIEnv *env) {
        jclass clazz = env->FindClass("jni/chowen/com/nativeapp/MainActivity");
        if (clazz == NULL) {
            LOGE("con't find class: jni/chowen/com/nativeapp/MainActivity");
            return JNI_ERR;
        }
        JNINativeMethod methods_MainActivity[] = {
                {"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
                {"add",           "(II)I",                (void *) add}
        };
        LOGE("can find class: jni/chowen/com/nativeapp/MainActivity %d >>> %d", sizeof(methods_MainActivity), sizeof(methods_MainActivity[0]));
        // int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
        return env->RegisterNatives(clazz, methods_MainActivity,
                                    sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
    }
    
    JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_signture
            (JNIEnv *env, jobject jobject1, jstring jstring1) {
        //md5加密
        const char *jcstr = (env)->GetStringUTFChars(jstring1, 0);
        MD5 md5;
        md5.update(jcstr);
        string jstring2 = md5.toString();
    
        //package_name
        jstring packName = getPackname(env, jobject1, jobject1);
        const char *c = env->GetStringUTFChars(packName, 0);
        LOGE("getPackageName: %s", c);
    
        //signature
        jstring signatures = getSignature(env, jobject1, jobject1);
        const char *signaturesc = env->GetStringUTFChars(signatures, 0);
        LOGE("signatures: %s", signaturesc);
    
    //    jstring js3 = (jstring) "jni.chowen.com.nativeapp";
    //    const char* c33 = env->GetStringUTFChars(js3, 0);
    //    if (strcmp(c, c33)) {
    //        LOGE("getPackageName: %s", "is cmp!!!!");
    //    }
    
    
    //    return (*env)->NewStringUTF(env, jstring2.c_str());
    
        return env->NewStringUTF(jstring2.c_str());
    }
    
    jstring getPackname(JNIEnv *env, jobject clazz, jobject obj) {
        jclass native_class = env->GetObjectClass(obj);
        jmethodID mId = env->GetMethodID(native_class, "getPackageName", "()Ljava/lang/String;");
        jstring packName = static_cast<jstring>(env->CallObjectMethod(obj, mId));
    
        return packName;
    }
    
    
    jstring getSignature(JNIEnv *env, jobject clazz, jobject jobject1) {
        //PackageInfo packageInfo = getPackageManager().getPackageInfo(
    //        getPackageName(), PackageManager.GET_SIGNATURES);
    //Signature[] signs = packageInfo.signatures;
    //Signature sign = signs[0];
    
    
        jclass native_class = env->GetObjectClass(clazz);
        jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager",
                                           "()Landroid/content/pm/PackageManager;");
        jobject pm_obj = env->CallObjectMethod(clazz, pm_id);
        jclass pm_clazz = env->GetObjectClass(pm_obj);
    // 得到 getPackageInfo 方法的 ID
        jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo",
                                                     "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
        jstring pkg_str = getPackname(env, clazz, clazz);
    
    // 获得应用包的信息
        jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
    // 获得 PackageInfo 类
        jclass pi_clazz = env->GetObjectClass(pi_obj);
    // 获得签名数组属性的 ID
        jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures",
                                                      "[Landroid/content/pm/Signature;");
        jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
        jobjectArray signaturesArray = (jobjectArray) signatures_obj;
        jsize size = env->GetArrayLength(signaturesArray);
        jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
        jclass signature_clazz = env->GetObjectClass(signature_obj);
        jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString",
                                               "()Ljava/lang/String;");
        jstring str = static_cast<jstring>(env->CallObjectMethod(signature_obj, string_id));
        char *c_msg = (char *) env->GetStringUTFChars(str, 0);
        LOGI("signsture: %s", c_msg);
        return str;
    }
    
    //JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_stringFromJNI2
    //        (JNIEnv *env, jobject js) {
    //    LOGI("No data on command pipe!");
    //
    //    //抛java层异常
    ////    jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
    ////    env->ThrowNew(newExcCls, "throw from JNI");
    //
    //    return env->NewStringUTF("from native str");
    //}
    
    JNIEXPORT jstring JNICALL Java_jni_chowen_com_nativeapp_MainActivity_fromJaveToNative
            (JNIEnv *env, jobject jobject1, jstring jstring1) {
    
        //method 1
    //   jclass jclass1 = env->FindClass("jni/chowen/com/nativeapp/MainActivity");
    // method 2
        jclass jclass1 = env->GetObjectClass(jobject1);
    
        jmethodID jmethodID2 = env->GetMethodID(jclass1, "<init>", "()V");
        jobject jobject2 = env->NewObject(jclass1, jmethodID2);
    
        jmethodID jmethodID1 = env->GetMethodID(jclass1, "fromNativeJNI2", "(Ljava/lang/String;)V");
        jstring message = env->NewStringUTF("call instance method");
        env->CallVoidMethod(jobject2, jmethodID1, message);
    
        jmethodID jmethodID4 = env->GetMethodID(jclass1, "fromNativeJNI", "()V");
        env->CallVoidMethod(jobject2, jmethodID4);
    
    
        jstring message2 = env->NewStringUTF("call instance method33333");
        jmethodID jmethodID3 = env->GetStaticMethodID(jclass1, "fromNativeJNI3",
                                                      "(Ljava/lang/String;)V");
        env->CallStaticVoidMethod(jclass1, jmethodID3, message2);
    
        jmethodID jmethodID5 = env->GetStaticMethodID(jclass1, "fromNativeJNI4", "()V");
        env->CallStaticVoidMethod(jclass1, jmethodID5);
    
    
        jclass jclass2 = env->FindClass("jni/chowen/com/nativeapp/CInterface");
        jmethodID jmethodID6 = env->GetStaticMethodID(jclass2, "setAndGetValue", "(I)I");
        jint jint2 = env->CallStaticIntMethod(jclass2, jmethodID6, 2000);
        LOGE("AndroidBitmap_getInfo failed, jint2: %d", jint2);
    
    
    //    #修改field
        jfieldID jfieldID2 = env->GetFieldID(jclass1, "i", "I");
        env->SetIntField(jobject2, jfieldID2, 200);
    
        jfieldID jfieldID1 = env->GetFieldID(jclass1, "i", "I");
        jint jint1 = env->GetIntField(jobject2, jfieldID1);
        LOGE("AndroidBitmap_getInfo failed, result: %d", jint1);
    
    
        jmethodID jmethodID7 = env->GetMethodID(jclass1, "getValue", "()I");
        jint jint3 = env->CallIntMethod(jobject2, jmethodID7);
        LOGE("AndroidBitmap_getInfo failed, jint3: %d", jint3);
    
        env->DeleteLocalRef(jclass1);
        env->DeleteLocalRef(message);
        env->DeleteLocalRef(jobject2);
        env->DeleteLocalRef(message2);
    
        env->DeleteLocalRef(jclass2);
    
    //    jclass newExcCls = env->FindClass("java/lang/IllegalArgumentException");
    //    env->ThrowNew(newExcCls, "throw from JNI");
    
        return env->NewStringUTF("fromJaveToNative");
    }
    
    3.1.2函数动态注册方式

    直接在JNI_OnLoad函数里动态注册相关函数,之前讲过JNI_OnLoad的初始化时机,是在System.loadLibrary里加载的,具体请看JNI方法注册及加载原理分析
    下面以native-lib.cpp中两个函数为例:

    //动态注册
    static jstring stringFromJNI(
            JNIEnv *env,
            jobject jobject1/* this */) {
        string hello = "Hello from C++";
        jint jint4 = env->GetVersion();
        LOGE("JNI version: %d", jint4);
    
        return env->NewStringUTF(hello.c_str());
    }
    
    jint add(JNIEnv *env, jclass clazz, jint a, jint b) {
        return a + b;
    }
    //重载JNI_OnLoad函数,在其中可做初始化相关工作
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
        JNIEnv *env = NULL;
        LOGE("JNI_OnLoad RegisterNatives JNI_OnLoad");
        if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
            return JNI_ERR;
        }
        jint result = RegisterNatives(env);
        LOGE("RegisterNatives result: %d", result);
        return JNI_VERSION_1_6;
    }
    //具体注册函数
    int RegisterNatives(JNIEnv *env) {
        jclass clazz = env->FindClass("jni/chowen/com/nativeapp/MainActivity");
        if (clazz == NULL) {
            LOGE("con't find class: jni/chowen/com/nativeapp/MainActivity");
            return JNI_ERR;
        }
        JNINativeMethod methods_MainActivity[] = {
                {"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},
                {"add",           "(II)I",                (void *) add}
        };
        LOGE("can find class: jni/chowen/com/nativeapp/MainActivity %d >>> %d", sizeof(methods_MainActivity), sizeof(methods_MainActivity[0]));
        // int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);
       // 进行函数动态注册
        return env->RegisterNatives(clazz, methods_MainActivity,
                                    sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
    }
    

    Java层实现
    声明对应的native函数

    public native int add(int a, int b);
    public native String stringFromJNI();
    

    3.2CMake构建脚本文件编写

    CMake是一个跨平台的构建系统,会在接下来详细介绍它的语法及使用。
    在项目cpp文件夹中新建一个CMakeLists.txt文件,AS新建的时候会自动为大家新建一个,平时JNI开发会又子目录,可能需要新建很多CMakeLists.txt文件。

    # 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.4.1)
    
    # 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.
    
    #IF (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows")
    #    ADD_DEFINITIONS(-DWindows)
    #ELSE (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Linux")
    #    ADD_DEFINITIONS(-DLinux)
    #ENDIF ()
    # 搜索当前目录下的所有.cpp文件
    aux_source_directory(. SRC_LIST) 
    #将SRC_LIST下的cpp文件生成名为native-lib2的动态库
    add_library(native-lib2 SHARED ${SRC_LIST} MD5.cpp)
    
    #FILE(GLOB SRC_LIST_SUB "${PROJECT_SOURCE_DIR}/src/main/cpp/cppp/*.cpp")
    #FILE(GLOB SRC_LIST_SUB2 "${PROJECT_SOURCE_DIR}/src/main/cpp/*.cpp")
    #add_library(native-lib SHARED ${SRC_LIST} ${SRC_LIST_SUB})
    
    # 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.
    #查找指定的log库文件,并将路径存到log-lib中
    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.
    # 指定动态链接库,native-lib2, jnigraphics,log-lib三个库
    target_link_libraries( # Specifies the target library.
            native-lib2
            jnigraphics
            # Links the target library to the log library
            # included in the NDK.
            ${log-lib})
    
    简单说明,后续会详细讲解:

    指定最小版本
    cmake_minimum_required(VERSION 3.4.1)
    指定动态链接库,native-lib2, jnigraphics,log-lib三个库
    target_link_libraries( # Specifies the target library.
    native-lib2
    jnigraphics
    # Links the target library to the log library
    # included in the NDK.
    ${log-lib})
    查找指定的log库文件,并将路径存到log-lib中
    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)
    

    搜索当前目录下的所有.cpp文件
    aux_source_directory(. SRC_LIST)
    将SRC_LIST下的cpp文件生成名为native-lib2的动态库
    add_library(native-lib2 SHARED ${SRC_LIST} MD5.cpp)

    4.gradle打包脚本编写

    在根目录build.gradle文件中android里配置exteranlNativeBuild

    android {
        ...
           
            externalNativeBuild {
                cmake {
                    cppFlags ""
                    // 配置构建相应的cpu架构平台so库
                    abiFilters 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a'
                }
            }
        }
      
        externalNativeBuild {
           //指定CMakeLists.txt文件目录路径
            cmake {
                path "src/main/cpp/CMakeLists.txt"
            }
        }
    }
    

    5.通过CMake构建即可打出SO动态库文件

    编译完成后会在项目以下(build/intermediates/cmake/debug/obj/对应的cpu架构平台/native-lib.so)文件中出现一个so文件。
    ../NativeApp/app/build/intermediates/cmake/debug/obj/armeabi-v7a

    6.项目加载so库

    引用编译好的so库,拷贝到jniLibs目录下,在项目使用之前需要loadLibrary动态库。说明下编译出来的是libnative-lib2名称,在System.loadLibrary中需要去掉lib。

    // Used to load the 'native-lib' library on application startup.
        static {
            System.loadLibrary("native-lib2");
        }
    

    That's All

    相关文章

      网友评论

        本文标题:JNI实例演示

        本文链接:https://www.haomeiwen.com/subject/aaefcctx.html