欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)

程序员文章站 2022-05-26 17:39:03
...

前言

关于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
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    2.在javacallc的目录下新建CMakeLists.txt文件,并指明库文件信息
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    已经在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);
    }
}

运行结果如下:
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
项目github地址:javacallc地址

下面结合实例分析C/C++函数调用Java方法

  1. 新建Module ccalljava(File->New->New Module->Phone & Tablet Module),将Module命名为ccalljava
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    在calljava的目录下新建CMakeLists.txt文件,并指明库文件信息
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    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文件
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
<2>.然后找到build/intermediates/classs/debug/com/example/administator/hellowjni下的JNI.class对应的目录,右键->copy path
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
然后在AS命令行控制面板中进入JNI类对应的路径下,执行javap -s JNI.class命令即可看到JNI类的方法签名
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
<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类型
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    并在CMakeLists.txt文件中指明编译生成的so库文件位置,这里指定为项目根目录的jniLibs目录下
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
  2. 按build->Make Module即可在项目根目录下的jniLibs文件夹下生成so库文件
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    注意 默认的so文件生成路径在module的build/intermediates/cmake/debug/obj目录下
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    1. 新建jnismodel module, 在src的main目录下新建jniLibs目录,将上面编译生成的so文件给拷贝过来
      Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    2. 要通过JNI调用so库,需要新建和so库的包名一样的类JNI,并加装so动态链接库。在src/main目录下,新建so库对应的包名路径,并在该路径新建JNI类
      Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
    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()函数进行设置

Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
同时在Java代码中加载两个动态链接库,并声明两个native方法(分别来自不同的两个native库)

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

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

便可编译生成两个so库
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀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。

Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)

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类文件)
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
  2. 在module的src\main\java目录下新建美图秀秀apk里面的JNI对应包名一致的包,并将JNI文件考别到该包下
    Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
  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);
    }
}

运行效果如下:
图片高亮效果
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
图片黑白效果
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
图片怀久效果
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
图片还原效果
Android JNI实现Java与C/C++互相调用,以及so库的生成和调用(JNI方式调用美图秀秀so)
项目github地址:mtxx地址