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

前言

关于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地址

猜你喜欢

转载自blog.csdn.net/hjj378315764/article/details/79834352