Android JNI實現Java與C/C++互相呼叫,以及so庫的生成和呼叫(JNI方式呼叫美圖秀秀so)

YellowJacketHjj發表於2018-04-07

前言

關於Android Studio如何繼承JNI開發環境,請閱讀上一篇博文 Android CMake整合JNI開發環境本篇博文將結合例項分別講解Android中Java如何呼叫C/C++的方法,C/C++如何回撥Java方法以及如何將本地native庫打包成so檔案作為庫使用。專案程式碼Github地址 喜歡的給個star,謝謝

Java呼叫C/C++程式碼的步驟流程如下:

  1. 配置好CMakeLists.txt檔案和build.gradle檔案指明庫檔案資訊
  2. 編寫C/C++檔案,根據包名和類名編寫相應函式
  3. 在Java檔案中通過System.loadLibrary()方法載入動態連結庫,並宣告對應的Native方法對接C/C++函式
  4. 呼叫Native方法即可實現Java呼叫C/C++函式

下面結合例項分析Java呼叫C/C++函式

  1. 新建Module javacallc(File->New->New Module->Phone & Tablet Module),將Module命名為javacallc
    這裡寫圖片描述
    2.在javacallc的目錄下新建CMakeLists.txt檔案,並指明庫檔案資訊
    這裡寫圖片描述
    這裡寫圖片描述
    已經在CMakeLists.txt檔案做了註釋,這裡就不再多作說明了。關於庫檔案的說明,請看上一篇部落格Android CMake整合JNI開發環境

3.然後配置build.gradle檔案

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.administrator.hellowjni"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
            ndk {
                // Specifies the ABI configurations of your native
                // libraries Gradle should build and package with your APK.
                abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                        'arm64-v8a'
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.0.0-beta1'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:0.5'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
}

我們來看看build.gradle裡面,起早第一個cmake裡面可以配置一些需要的引數,這裡暫時就預設,不用更改;第二個cmake裡面匯入要構建的CMakeLists.txt指令碼,這個指令碼里面就是我們需要改寫的。如:新增自己的c檔案,系統庫以及第三方庫等。

4.接著編寫JNI類,在JNI類裡面載入動態連結庫,並編寫Native方法,如下程式碼所示定義4個native方法

public class JNI {
    static {
        System.loadLibrary("javacallc");
    }

    /**
     * 讓C程式碼做加法運算,把結果返回
     * @param x
     * @param y
     * @return
     */
    public native int add(int x, int y);

    /**
     * 從java傳入字串,C程式碼程式拼接
     *
     * @param s I am from java
     * @return  I am form java add I am from C
     */
    public native String sayHello(String s);

    /**
     * 讓C程式碼給每個元素都加上10
     * @param intArray
     * @return
     */
    public native int[] increaseArrayEles(int[] intArray);
    /*
     * 應用: 檢查密碼是否正確, 如果正確返回200, 否則返回400
     */
    public native int checkPwd(String pwd);
}

5.在src/main/cpp目錄下編寫javacall.cpp檔案,在C++檔案中實現JNI類定義的native方法

#include <jni.h>
#include <string>
#include <string.h>
#include <android/log.h>
#include <malloc.h>
#include <stdio.h>

#define LOG_TAG "javacallc"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

/**
 * jint:返回值
 * Java_全類名_方法名
 * JNIEnv *env:
 */
extern "C"
JNIEXPORT jint

JNICALL
Java_com_example_javacallc_JNI_add(JNIEnv*env, jobject jobj, jint ji, jint jj){
    int result = ji + jj;
    LOGE("result===%d\n",result);
    return result;
};

/**
 * 將一個jstring轉換成一個c語言的char* 型別.
 */
char* _JString2CStr(JNIEnv* env, jstring jstr) {
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("GB2312");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); // String .getByte("GB2312");
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if(alen > 0) {
        rtn = (char*)malloc(alen+1); //"\0"
        memcpy(rtn, ba, alen);
        rtn[alen]=0;
    }
    env->ReleaseByteArrayElements(barr, ba,0);
    return rtn;
}


/**
 *  從java傳入字串,C程式碼程式拼接
     *
     * @param java : I am from java
     *        c    : add I am from C
     * @return  I am form java add I am from C
 */
extern "C"
JNIEXPORT jstring

JNICALL
Java_com_example_javacallc_JNI_sayHello(JNIEnv * env, jobject job, jstring jstr){

    char* fromJava = _JString2CStr(env,jstr);//I am form java add I am from C   注意_JString2CStr函式要在呼叫之前宣告,這是C/C++的語法規則,和Java不一樣...
    //c:
    char* fromC = "add I am from C";
    //拼接函式strcat
    strcat(fromJava,fromC);//把拼接的結果放在第一引數裡面
    //jstring     (*NewStringUTF)(JNIEnv*, const char*);
    LOGE("fromJava===%s\n",fromJava);
    return  env->NewStringUTF(fromJava);
};


/*
 * Class:     com_example_javacallc_JNI
 * Method:    increaseArrayEles
 * Signature: ([I)[I
 * 給每個元素加上10
 */
extern "C"
JNIEXPORT jintArray JNICALL Java_com_example_javacallc_JNI_increaseArrayEles
        (JNIEnv * env, jobject jobject1, jintArray jarray){

    //1.得到陣列的長度
    //jsize       (*GetArrayLength)(JNIEnv*, jarray);
    jsize size = env->GetArrayLength(jarray);
    //2.得到陣列元素(方法是先獲得陣列的指標,通過指標修改)
    //jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
    jint*  intArray = env->GetIntArrayElements(jarray,JNI_FALSE);
    //3.遍歷陣列,給每個元素加上10
    int i;
    for(i =0;i<size;i++){
//        *(intArray+i) = *(intArray+i) + 10;
        *(intArray+i) +=  10;
        LOGE("intArray[%d]===%d\n",i,*(intArray+i));
    }

    //4.呼叫SetXXArrayRegion方法提交到Java陣列裡面(如果不提交的話,不會修改Java陣列元素的值)。http://blog.csdn.net/pz0605/article/details/53010556
    env->SetIntArrayRegion(jarray, 0,size,intArray);
    //5.返回結果
    return  jarray;
}


/*
 * Class:     com_example_javacallc_JNI
 * Method:    checkPwd
 * Signature: (Ljava/lang/String;)I
 */
extern "C"
JNIEXPORT jint JNICALL Java_com_example_javacallc_JNI_checkPwd
        (JNIEnv * env, jobject jobject1, jstring jstr){

    //伺服器的密碼是123456
    char* origin = "123456";
    char* fromUser = _JString2CStr(env,jstr);

    //函式比較字串是否相同
    int code =  strcmp(origin,fromUser);
    LOGE("code===%d\n",code);
    if(code==0){
        return 200;
    }else{
        return 400;
    }
}




其中,C++的函式名為Java_native方法所在類的全類名(以下斜槓劃分層次)_native方法名。最後在Activity中呼叫JNI裡面宣告的native方法,即可實現Java呼叫C/C++函式

public class MainActivity extends AppCompatActivity {

    private static String TAG = MainActivity.class.getSimpleName();
    private JNI jni;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        jni = new JNI();
    }


    public void add(View view){
        int result =jni.add(99, 1);
        Log.e(TAG,"result==="+result);
    }

    public void string(View view){
        String result =jni.sayHello("I am from java ");
        Log.e(TAG, "result===" + result);
    }

    public void array(View view){
        int array[] = {1,2,3,4,5};
        int result[] =jni.increaseArrayEles(array);
        for(int i=0;i<result.length;i++){
            Log.e(TAG,"array["+i+"]==="+result[i]);
        }

    }

    public void checkpw(View view){
        int result =jni.checkPwd("123456");
        Log.e(TAG, "result===" + result);
    }
}

執行結果如下:
這裡寫圖片描述
專案github地址:javacallc地址

下面結合例項分析C/C++函式呼叫Java方法

  1. 新建Module ccalljava(File->New->New Module->Phone & Tablet Module),將Module命名為ccalljava
    這裡寫圖片描述
    在calljava的目錄下新建CMakeLists.txt檔案,並指明庫檔案資訊
    這裡寫圖片描述
    CMake檔案如下:
# 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的最小版本
cmake_minimum_required(VERSION 3.4.1)

#C 的編譯選項是 CMAKE_C_FLAGS
# 指定編譯引數,可選
#SET(CMAKE_CXX_FLAGS "-Wno-error=format-security -Wno-error=pointer-sign")

#設定生成的so動態庫最後輸出的路徑
#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/jniLibs/${ANDROID_ABI})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

#設定標頭檔案搜尋路徑(和此txt同個路徑的標頭檔案無需設定),可選
#INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/common)

#指定用到的系統庫或者NDK庫或者第三方庫的搜尋路徑,可選。
#LINK_DIRECTORIES(/usr/local/lib)

# 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.

#新增第一個遠端連結庫
add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp
             src/main/cpp/MyNativeCodeWithLog.cpp
             src/main/cpp/CCallJava.cpp
             )
# 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.

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.

target_link_libraries( # Specifies the target library.
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

配置build.gradle檔案

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.administrator.hellowjni"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
            ndk {
                // Specifies the ABI configurations of your native
                // libraries Gradle should build and package with your APK.
                abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                        'arm64-v8a'
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.0.0-beta1'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:0.5'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
}

新建JNI類,載入動態連結庫,並實現native方法

public class JNI {

    private static String TAG = JNI.class.getSimpleName();//這裡TAG宣告為靜態變數,避免c/c++函式多次例項化MainActivity物件時候建立出多個TAG,這樣列印效果不好
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String myNativeJNIMethodWithLog();

    public native String sayHello();

    public native String stringFromJNI();

    /**
     * 當執行這個方法的時候,讓C程式碼呼叫
     * public int add(int x, int y)
     */
    public native void callbackAdd();

    public native void callbackPrintString();

    public native void callbackSayHello();


    public int add(int x, int y) {
        Log.e(TAG, "add() x=" + x + " y=" + y);
        return x + y;
    }

    public void printString(String s){
        Log.e(TAG, "printString="+s);
    }

    public static void sayHello(String s){
        Log.e(TAG, "sayHello: "+s);
    }
}

在src/main/cpp目錄下編寫CCallJava.cpp檔案,在C++檔案中實現JNI類定義的native方法。
C程式碼回撥Java方法的主要流程為:
(1).呼叫JNIEnv的FindClass方法找到Java方法對應類的位元組碼jclass,這裡傳入的是Java類的全類名,如下如:

//1.得到位元組碼
jclass jclazz = env->FindClass("com/example/administrator/hellowjni/JNI");

(2).呼叫JNIEnv的GetMethodID傳入得到位元組碼jclass找到要呼叫的方法methodID

/**
* 2.得到方法
* jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
* 最後一個引數是簽名
*/
jmethodID jmethodIDs = env->GetMethodID(jclazz,"add","(II)I");

若呼叫靜態方法則呼叫JNIEnv的GetStaticMethodID方法

//2.得到方法
//最後一個引數是方法簽名
jmethodID jmethodIDs= env->GetStaticMethodID(jclazz,"sayHello","(Ljava/lang/String;)V");

這裡需要傳入方法的簽名,其中GetMethodID的第三個引數”(II)I”為方法add的簽名。獲取方法簽名可通過如下方法:
<1>. 按Build->Make Selected Modules構建專案,生成class檔案
這裡寫圖片描述
<2>.然後找到build/intermediates/classs/debug/com/example/administator/hellowjni下的JNI.class對應的目錄,右鍵->copy path
這裡寫圖片描述
這裡寫圖片描述
然後在AS命令列控制皮膚中進入JNI類對應的路徑下,執行javap -s JNI.class命令即可看到JNI類的方法簽名
這裡寫圖片描述
<3>.呼叫JNIEnv的AllocObject方法例項化該Java類(若是靜態方法,則不需例項化)

//3.例項化該類
jobject jobject =env->AllocObject(jclazz);

<4>.呼叫JNIEnv的CallIntMethod方法實現Java方法的呼叫

//4.呼叫方法
jint value = env->CallIntMethod(jobject,jmethodIDs,99,5);

若是靜態方法,則呼叫JNIEnv的CallStaticVoidMethod方法

env->CallStaticVoidMethod(jclazz,jmethodIDs,jst);

完整CCallJava.cpp的程式碼如下:

/**
 * 讓C程式碼呼叫Java類中的方法
 */
#include <jni.h>
#include <string>

#include <android/log.h>
#define LOG_TAG "CCallJava"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

extern "C"


/**
 * 讓C程式碼呼叫Java中JNI類的 public int add(int x,int y)
 */
JNIEXPORT void

JNICALL
Java_com_example_administrator_hellowjni_JNI_callbackAdd(
        JNIEnv *env,
        jobject /* this */) {
    //1.得到位元組碼
    jclass jclazz = env->FindClass("com/example/administrator/hellowjni/JNI");
    /**
     * 2.得到方法
     * jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
     * 最後一個引數是簽名
     */
    jmethodID jmethodIDs = env->GetMethodID(jclazz,"add","(II)I");

    //3.例項化該類
    jobject jobject = env->AllocObject(jclazz);

    //4.呼叫方法
    jint value = env->CallIntMethod(jobject,jmethodIDs,99,5);
    //成功呼叫了public int add(int x,int y)
    printf("1.value===%d\n",value);
    LOGE("2.value===%d\n",value);
}

extern "C"
/**
 * 讓C程式碼呼叫void printString(String s)
 */
JNIEXPORT void JNICALL Java_com_example_administrator_hellowjni_JNI_callbackPrintString
        (JNIEnv * env, jobject job){
    //1.得到位元組碼
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jclazz = env->FindClass("com/example/administrator/hellowjni/JNI");
    //2.得到方法
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    //最後一個引數是方法簽名
    jmethodID jmethodIDs= env->GetMethodID(jclazz,"printString","(Ljava/lang/String;)V");
    //3.例項化該類
    // jobject     (*AllocObject)(JNIEnv*, jclass);
    jobject  jobject =env->AllocObject(jclazz);
    //4.呼叫方法
    //void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    //jstring     (*NewStringUTF)(JNIEnv*, const char*);
    jstring jst = env->NewStringUTF("I am afu!!!(*env)->");
    env->CallVoidMethod(jobject,jmethodIDs,jst);
    //成功呼叫了public void helloFromJava()
};


extern "C"
/**
 * 讓C程式碼靜態方法 static void sayHello(String s)
 */
JNIEXPORT void JNICALL Java_com_example_administrator_hellowjni_JNI_callbackSayHello
        (JNIEnv * env, jobject jobj){

    //1.得到位元組碼
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jclazz = env->FindClass("com/example/administrator/hellowjni/JNI");
    //2.得到方法
    //jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*);
    //最後一個引數是方法簽名
    jmethodID jmethodIDs= env->GetStaticMethodID(jclazz,"sayHello","(Ljava/lang/String;)V");
    jstring jst = env->NewStringUTF("I am android1223");
    env->CallStaticVoidMethod(jclazz,jmethodIDs,jst);
    //成功呼叫了static void sayHello(String s)

}


//extern "C"  //Toast.makeText(MainActivity.this,"showToast----------------",Toast.LENGTH_SHORT).show();會報控制針異常
///**
// * instance:誰呼叫了當前Java_com_atguigu_ccalljava_JNI_callBackShowToast對應的Java的介面
// * 就是誰的例項:當前是JNI.this-->MainActivity.this
// */
//JNIEXPORT void JNICALL Java_com_example_administrator_hellowjni_MainActivity_callBackShowToast(JNIEnv * env, jobject instance) {
//    //1.得到位元組碼
//    //jclass      (*FindClass)(JNIEnv*, const char*);
//    jclass jclazz = env->FindClass("com/example/administrator/hellowjni/MainActivity");
//    //2.得到方法
//    //最後一個引數是方法簽名
//    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
//    jmethodID jmethodIDs= env->GetMethodID(jclazz,"showToast","()V");
//    //3.例項化該類
//    //   jobject     (*AllocObject)(JNIEnv*, jclass);
//    jobject  jobject1 = env->AllocObject(jclazz);
//    //4.呼叫方法
//    //void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
//    env->CallVoidMethod(jobject1,jmethodIDs);
//    //成功呼叫了static void sayHello(String s)
//
//}



/**
 * instance:誰呼叫了當前Java_com_atguigu_ccalljava_JNI_callBackShowToast對應的Java的介面
 * 就是誰的例項:當前是JNI.this-->MainActivity.this
 */
extern "C"
JNIEXPORT void JNICALL Java_com_example_administrator_hellowjni_MainActivity_callBackShowToast(JNIEnv *env, jobject instance) {

    //1.得到位元組碼
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jclazz = env->FindClass("com/example/administrator/hellowjni/MainActivity");
    //2.得到方法
    //最後一個引數是方法簽名
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
    jmethodID jmethodIDs= env->GetMethodID(jclazz,"showToast","()V");
    //3.例項化該類
    //   jobject     (*AllocObject)(JNIEnv*, jclass);
    //jobject  jobject1 = (*env)->AllocObject(env,jclazz);
    //4.呼叫方法
    //void        (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
    env->CallVoidMethod(instance,jmethodIDs);
    //成功呼叫了static void sayHello(String s)

}

執行結果如下:

Github地址:ccalljava地址

最後,我們再來講解如何打包so庫檔案以及so庫的應用

  1. 在build.gradle檔案中配置abiFilter CPU型別
    這裡寫圖片描述
    並在CMakeLists.txt檔案中指明編譯生成的so庫檔案位置,這裡指定為專案根目錄的jniLibs目錄下
    這裡寫圖片描述
  2. 按build->Make Module即可在專案根目錄下的jniLibs資料夾下生成so庫檔案
    這裡寫圖片描述
    這裡寫圖片描述
    注意 預設的so檔案生成路徑在module的build/intermediates/cmake/debug/obj目錄下
    這裡寫圖片描述
    1. 新建jnismodel module, 在src的main目錄下新建jniLibs目錄,將上面編譯生成的so檔案給拷貝過來
      這裡寫圖片描述
    2. 要通過JNI呼叫so庫,需要新建和so庫的包名一樣的類JNI,並加裝so動態連結庫。在src/main目錄下,新建so庫對應的包名路徑,並在該路徑新建JNI類
      這裡寫圖片描述
    3. 在JNI類中加裝動態連結庫,並宣告native方法
public class JNI {

    private static String TAG = JNI.class.getSimpleName();//這裡TAG宣告為靜態變數,避免c/c++函式多次例項化MainActivity物件時候建立出多個TAG,這樣列印效果不好
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String myNativeJNIMethodWithLog();

    public native String sayHello();

    public native String stringFromJNI();

    /**
     * 當執行這個方法的時候,讓C程式碼呼叫
     * public int add(int x, int y)
     */
    public native void callbackAdd();

    public native void callbackPrintString();

    public native void callbackSayHello();


    public int add(int x, int y) {
        Log.e(TAG, "add() x=" + x + " y=" + y);
        return x + y;
    }

    public void printString(String s){
        Log.e(TAG, "printString="+s);
    }

    public static void sayHello(String s){
        Log.e(TAG, "sayHello: "+s);
    }
}

這時候再MainActivity中即可呼叫JNI類的native方法

public class MainActivity extends AppCompatActivity {

    private JNI jni;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        jni = new JNI();
        jni.stringFromJNI();
        jni.sayHello();

        jni.myNativeJNIMethodWithLog(); //在c++程式碼中實現帶Log.e列印日誌

        jni.callbackAdd();
        jni.callbackPrintString();
        jni.callbackSayHello();
    }
}

專案github地址:jnisomodel地址

若想載入編譯生成多個so庫檔案,可在CMakeLists.txt檔案中多新增幾個add_library()函式進行設定

這裡寫圖片描述
同時在Java程式碼中載入兩個動態連結庫,並宣告兩個native方法(分別來自不同的兩個native庫)

public class JNI {
    static {
        System.loadLibrary("one");
        System.loadLibrary("two");
    }

    public native String stringFromJNIOne();
    public native String stringFromJNITwo();
}

便可編譯生成兩個so庫
這裡寫圖片描述

Github地址: mutillibs地址

同理,我們也可以載入第三方so庫檔案,呼叫第三方的native方法,下面我們通過加入美圖秀秀的libmtimage-jni.so

新建module mtxx
1. 反編譯美圖秀秀apk得到對應的圖片處理so檔案,在src/main路徑下新建jniLibs,將美圖秀秀的so檔案拷貝到該目錄下。
2. 利用Small2JavaUI工具讀取apk檔案得到對應的JNI類,取得類裡面的方法和類包名,根據包名,在src/main路徑下新建美圖秀秀的包名路徑以及JNI類,然後載入動態連結庫和編寫JNI方法,動態連結庫的名稱為so檔案去掉字首lib和字尾名得到的字串即為動態連結庫名稱。如該so檔案為libmtimage-jni.so,則動態連結庫的名稱為mtimage-jni。

這裡寫圖片描述

public class JNI {
    {
        System.loadLibrary("mtimage-jni");  //注意api版本不能高(低於API 23),否則會因和編譯so檔案的版本對不上而報錯 http://blog.csdn.net/qq_17265737/article/details/54139325
    }
    ...
}
  1. 新建AS工程,在對應module的src/main路徑下新建jniLibs目錄用於存放從美圖秀秀中獲的的so檔案(注:由於JNI類的native方法是不能混淆打包的,所以這裡可以獲取對應的JNI類檔案)
    這裡寫圖片描述
  2. 在module的src\main\java目錄下新建美圖秀秀apk裡面的JNI對應包名一致的包,並將JNI檔案考別到該包下
    這裡寫圖片描述
  3. 在靜態程式碼塊中載入動態連結庫System.loadLibrary(“庫名稱”); 庫名稱為so檔案去掉lib字首和檔名(如:libnative-lib.so檔案對應的庫名稱為native-lib)
public class JNI {
{
  ...
  System.loadLibrary("mtimage-jni"); //注意api版本不能高(低於API 23),否則會因和編譯so檔案的版本對不上而報錯 
  ...
}

注意專案的api版本不能高於.so檔案的編譯版本,否則會出現:has text relocations的異常,一般都是編譯的版本對不上導致的,我剛開始的時候呼叫android 7.0,改成6.0還是一樣的報錯,再改成5.2的就好了

最後便可以在MainActivity中呼叫libmtimage-jni.so檔案提供的native方法了

public class MainActivity extends AppCompatActivity {

    private JNI jni;
    private ImageView iv_icon;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        iv_icon = (ImageView) findViewById(R.id.iv_icon);
        jni = new JNI();
    }

    public void lomoHDR(View view){

        //6.1,把圖片轉換成陣列
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.girl);
        //裝圖片的像數
        int[] pixels = new int[bitmap.getWidth()*bitmap.getHeight()];
        /**
         * 引數

         pixels       接收點陣圖顏色值的陣列

         offset      寫入到pixels[]中的第一個畫素索引值

         stride       pixels[]中的行間距個數值(必須大於等於點陣圖寬度)。可以為負數

         x             從點陣圖中讀取的第一個畫素的x座標值。

         y             從點陣圖中讀取的第一個畫素的y座標值

         width       從每一行中讀取的畫素寬度

         height   讀取的行數

           異常
         */
        bitmap.getPixels(pixels,0,bitmap.getWidth(),0,0,bitmap.getWidth(),bitmap.getHeight());
        //6.2,把陣列傳入給C程式碼處理
        jni.StyleLomoHDR(pixels,bitmap.getWidth(),bitmap.getHeight());
        // 6.3,把處理好的陣列重新生成圖片
        bitmap =  Bitmap.createBitmap(pixels,bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        // 6.4,把圖片像數
        iv_icon.setImageBitmap(bitmap);


    }

    public void lomoC(View view){

        //6.1,把圖片轉換成陣列
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.girl);
        //裝圖片的像數
        int[] pixels = new int[bitmap.getWidth()*bitmap.getHeight()];
        bitmap.getPixels(pixels,0,bitmap.getWidth(),0,0,bitmap.getWidth(),bitmap.getHeight());
        //6.2,把陣列傳入給C程式碼處理
        jni.StyleLomoC(pixels, bitmap.getWidth(), bitmap.getHeight());
        // 6.3,把處理好的陣列重新生成圖片
        bitmap =  Bitmap.createBitmap(pixels,bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        // 6.4,把圖片像數
        iv_icon.setImageBitmap(bitmap);

    }

    public void lomoB(View view){
        //6.1,把圖片轉換成陣列
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.girl);
        //裝圖片的像數
        int[] pixels = new int[bitmap.getWidth()*bitmap.getHeight()];
        bitmap.getPixels(pixels,0,bitmap.getWidth(),0,0,bitmap.getWidth(),bitmap.getHeight());
        //6.2,把陣列傳入給C程式碼處理
        jni.StyleLomoB(pixels,bitmap.getWidth(),bitmap.getHeight());
        // 6.3,把處理好的陣列重新生成圖片
        bitmap =  Bitmap.createBitmap(pixels,bitmap.getWidth(),bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        // 6.4,把圖片像數
        iv_icon.setImageBitmap(bitmap);
    }


    public void reset(View view){

        iv_icon.setImageResource(R.drawable.girl);
    }
}

執行效果如下:
圖片高亮效果
高亮圖片
圖片黑白效果
這裡寫圖片描述
圖片懷久效果
這裡寫圖片描述
圖片還原效果
這裡寫圖片描述
專案github地址:mtxx地址

相關文章