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++代码的步骤流程如下:
- 配置好CMakeLists.txt文件和build.gradle文件指明库文件信息
- 编写C/C++文件,根据包名和类名编写相应函数
- 在Java文件中通过System.loadLibrary()方法加载动态链接库,并声明对应的Native方法对接C/C++函数
- 调用Native方法即可实现Java调用C/C++函数
下面结合实例分析Java调用C/C++函数
- 新建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方法
- 新建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库的应用
- 在build.gradle文件中配置abiFilter CPU类型
并在CMakeLists.txt文件中指明编译生成的so库文件位置,这里指定为项目根目录的jniLibs目录下
- 按build->Make Module即可在项目根目录下的jniLibs文件夹下生成so库文件
注意 默认的so文件生成路径在module的build/intermediates/cmake/debug/obj目录下- 新建jnismodel module, 在src的main目录下新建jniLibs目录,将上面编译生成的so文件给拷贝过来
- 要通过JNI调用so库,需要新建和so库的包名一样的类JNI,并加装so动态链接库。在src/main目录下,新建so库对应的包名路径,并在该路径新建JNI类
- 在JNI类中加装动态链接库,并声明native方法
- 新建jnismodel module, 在src的main目录下新建jniLibs目录,将上面编译生成的so文件给拷贝过来
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
}
...
}
- 新建AS工程,在对应module的src/main路径下新建jniLibs目录用于存放从美图秀秀中获的的so文件(注:由于JNI类的native方法是不能混淆打包的,所以这里可以获取对应的JNI类文件)
- 在module的src\main\java目录下新建美图秀秀apk里面的JNI对应包名一致的包,并将JNI文件考别到该包下
- 在静态代码块中加载动态链接库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地址