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地址
相關文章
- java呼叫c++動態庫之jni呼叫JavaC++
- C++庫封裝JNI介面——實現java呼叫c++C++封裝Java
- 鴻蒙手機版JNI實戰(JNI開發、SO庫生成、SO庫使用)鴻蒙
- Android JNI開發系列之Java與C相互呼叫AndroidJava
- Android Studio NDK開發-JNI呼叫Java方法AndroidJava
- Java如何呼叫C語言程式,JNI技術JavaC語言
- lua——alien庫實現lua呼叫C動態連結庫(dll、so)
- JNI:Java程式碼呼叫原生程式碼Java
- Nginx+lua 實現呼叫.so檔案Nginx
- JNI呼叫c動態連結庫函式程式碼實踐函式
- python和C的如何實現互相呼叫?Python
- windows和linux gcc生成動態連結庫DLL和SO並用python呼叫WindowsLinuxGCPython
- JNI初步(五)jni ndk 一個.so檔案依賴另一個.so檔案的寫法
- java動態呼叫c++庫JavaC++
- Python呼叫C/C++方式PythonC++
- C++呼叫 c#生成的dllC++C#
- C# 呼叫 C++ 生成的 dll 關鍵實現部分C#C++
- Linux下編譯生成SO並進行呼叫執行Linux編譯
- c與c++的相互呼叫C++
- 監控Java層和JNI Native層Crash,分析.so庫報錯的堆疊資訊Java
- flutter中呼叫C++的庫FlutterC++
- JNI-Thread中start方法的呼叫與run方法的回撥分析thread
- VS2012生成C的dll並呼叫以及Python呼叫C的DLLPython
- 交叉編譯c++給android呼叫編譯C++Android
- C++呼叫LuaC++
- Unity——Js和Unity互相呼叫UnityJS
- Python與C/C++呼叫之ctypesPythonC++
- C++呼叫C介面C++
- python和c++的相互呼叫教程PythonC++
- C++呼叫C#的動態庫dllC++C#
- Windows 下 c++ 呼叫 Rust 庫的例子WindowsC++Rust
- 成品直播原始碼推薦,用JNI生成so檔案,加密解密需要的hascode生成程式碼原始碼加密解密
- C#呼叫 C++的DLLC#C++
- Linux C/C++呼叫mongDBLinuxC++
- C#呼叫C++動態連結庫C#C++
- JEP 419:JDK18將無需JNI呼叫JVM外部程式和資料JDKJVM
- 安卓除錯 .so 斷到 JNI_OnLoad 或者 .init段安卓除錯
- VS(visual studio) C++ 封裝dll,以及其隱式呼叫與顯式呼叫(靜態動態)C++封裝