一天掌握Android JNI本地程式設計 快速入門
阿新 • • 發佈:2019-01-12
一、JNI(Java Native Interface)
1、什麼是JNI: JNI(Java Native Interface):java本地開發介面 JNI是一個協議,這個協議用來溝通java程式碼和外部的原生代碼(c/c++)-
private native String getStringFromC();
3、在jni中建立一個C檔案,定義一個函式實現本地方法,函式名必須用使用 本地方法的全類名,點改為下劃線。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <jni.h> 4 //方法名必須為本地方法的全類名點改為下劃線,穿入的兩個引數必須這樣寫, 5 //第一個引數為Java虛擬機器的記憶體地址的二級指標,用於本地方法與java虛擬機器在記憶體中互動 6 //第二個引數為一個java物件,即是哪個物件呼叫了這個 c方法 7 jstring Java_com_mwp_jnihelloworld_MainActivity_getStringFromC(JNIEnv* env, 8 jobject obj){ 9 //定義一個C語言字串 10 char* cstr = "hello form c"; 11 //返回值是java字串,所以要將C語言的字串轉換成java的字串 12 //在jni.h 中定義了字串轉換函式的函式指標 13 //jstring (*NewStringUTF)(JNIEnv*, const char*); 14 //第一種方法:很少用 15 jstring jstr1 = (*(*env)).NewStringUTF(env, cstr); 16 //第二種方法,推薦 17 jstring jstr2 = (*env) -> NewStringUTF(env, cstr); 18 return jstr2; 19 }
4、在jni中建立 Android.mk檔案,用於配置 本地方法
-
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) #編譯生成的檔案的類庫叫什麼名字 LOCAL_MODULE := hello #要編譯的c檔案 LOCAL_SRC_FILES := Hello.c include $(BUILD_SHARED_LIBRARY)
5、在jni目錄下執行 ndk-build.cmd指令,編譯c檔案
static{ //載入打包完畢的 so類庫 System.loadLibrary("hello"); }
7、jni打包的C語言類庫預設僅支援 arm架構,需要在jni目錄下建立 Android.mk 檔案新增如下程式碼可以支援x86架構
-
APP_ABI := armeabi armeabi-v7a x86
四、JNI常見錯誤
#include <jni.h> #include <string.h> //將java字串轉換為c語言字串(工具方法) char* Jstring2CStr(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = (*env)->FindClass(env,"java/lang/String"); jstring strencode = (*env)->NewStringUTF(env,"GB2312"); jmethodID mid = (*env)->GetMethodID(env,clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env,jstr,mid,strencode); // String .getByte("GB2312"); jsize alen = (*env)->GetArrayLength(env,barr); jbyte* ba = (*env)->GetByteArrayElements(env,barr,JNI_FALSE); if(alen > 0) { rtn = (char*)malloc(alen+1); //"\0" memcpy(rtn,ba,alen); rtn[alen]=0; } (*env)->ReleaseByteArrayElements(env,barr,ba,0); // return rtn; } JNIEXPORT jstring JNICALL Java_com_mwp_encodeanddecode_MainActivity_encode (JNIEnv * env, jobject obj, jstring text, jint length){ char* cstr = Jstring2CStr(env, text); int i; for(i = 0;i<length;i++){ *(cstr+i) += 1; //加密演算法,將字串每個字元加1 } return (*env)->NewStringUTF(env,cstr); } JNIEXPORT jstring JNICALL Java_com_mwp_encodeanddecode_MainActivity_decode (JNIEnv * env, jobject obj, jstring text, jint length){ char* cstr = Jstring2CStr(env, text); int i; for(i = 0;i<length;i++){ *(cstr+i) -= 1; } return (*env)->NewStringUTF(env, cstr); }
七、JNI操作一個數組(引用傳遞) 傳遞陣列其實是傳遞一個堆記憶體的陣列首地址的引用過去,所以實際操作的是同一塊記憶體, 當呼叫完方法,不需要返回值,實際上引數內容已經改變, Android中很多操作硬體的方法都是這種C語言的傳引用的思路
1 public class MainActivity extends Activity { 2 3 static{ 4 System.loadLibrary("encode"); 5 } 6 int[] array = {1,2,3,4,5}; 7 @Override 8 protected void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); 10 setContentView(R.layout.activity_main); 11 } 12 13 public void click(View v){ 14 encodeArray(array); 15 //不需要返回值,實際操作的是同一塊記憶體,內容已經發生了改變 16 for (int i : array) { 17 System.out.println(i); 18 } 19 } 20 21 //傳遞陣列其實是傳遞一個堆記憶體的陣列首地址的引用過去,所以實際操作的是同一塊記憶體, 22 //當呼叫完方法,不需要返回值,實際上引數內容已經改變, 23 //Android中很多操作硬體的方法都是這種C語言的傳引用的思路,要非常熟練 24 private native void encodeArray(int[] arr); 25 }
1 #include <jni.h> 2 /* 3 * Class: com_mwp_jniarray_MainActivity 4 * Method: encodeArray 5 * Signature: ([I)V 6 */ 7 JNIEXPORT void JNICALL Java_com_mwp_jniarray_MainActivity_encodeArray 8 (JNIEnv * env, jobject obj, jintArray arr){ 9 //拿到整型陣列的長度以及第0個元素的地址 10 //jsize (*GetArrayLength)(JNIEnv*, jarray); 11 int length = (*env)->GetArrayLength(env, arr); 12 // jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); 13 int* arrp = (*env)->GetIntArrayElements(env, arr, 0); 14 int i; 15 for(i = 0;i<length;i++){ 16 *(arrp + i) += 10; //將陣列中的每個元素加10 17 } 18 }
八、偷用美圖秀秀的C語言本地類庫加深JNI的理解 專案中不需要有c程式碼,只需要有一個編譯過後的類庫供Java呼叫就可以了。 將美圖秀秀的apk檔案解壓縮,將lib目錄下C類庫匯入自己的專案, 反編譯美圖秀秀的apk檔案,將其本地方法類 JNI.java複製到自己的專案 根據本地方法名和引數猜函式的作用及如何使用, 下例呼叫了美圖的一個LOMO美化效果
1 public class MainActivity extends Activity { 2 3 static{ 4 //載入美圖秀秀的類庫 5 System.loadLibrary("mtimage-jni"); 6 } 7 private ImageView iv; 8 private Bitmap bitmap; 9 @Override 10 protected void onCreate(Bundle savedInstanceState) { 11 super.onCreate(savedInstanceState); 12 setContentView(R.layout.activity_main); 13 14 iv = (ImageView) findViewById(R.id.iv); 15 16 bitmap = BitmapFactory.decodeFile("sdcard/aneiyi.jpg"); 17 iv.setImageBitmap(bitmap); 18 } 19 20 public void click(View v){ 21 22 int width = bitmap.getWidth(); 23 int height = bitmap.getHeight(); 24 25 //用於儲存所有畫素資訊的陣列 26 int[] pixels = new int[width*height]; 27 //獲取圖片的畫素顏色資訊,儲存至pixels 28 bitmap.getPixels(pixels, 0, width, 0, 0, width, height); 29 30 JNI jni = new JNI(); 31 //呼叫美圖秀秀本地庫中的美圖方法,靠猜 32 //arg0:儲存了所有畫素顏色資訊的陣列 33 //arg1:圖片的寬 34 //arg2:圖片的高 35 //此方法是通過改變pixels的畫素顏色值來實現美化效果,傳遞一個數組引數是不需要返回值的 36 jni.StyleLomoB(pixels, width, height); 37 38 Bitmap bmNew = Bitmap.createBitmap(pixels, width, height, bitmap.getConfig()); 39 iv.setImageBitmap(bmNew); 40 } 41 }
九、在C語言中呼叫java方法(反射) 1、有時需要在C語言中呼叫java的方法,如重新整理UI顯示載入資源進度 在本地方法C語言程式碼中列印 Android的Logcat日誌輸出,Google已經幫我們封裝好了方法,只需要呼叫一下就可以 如果要輸出中文的話,必須將C語言的檔案編碼改成 utf-8,否則亂碼 在C語言中呼叫java的方法需要用到反射,C語言的反射需要一個方法簽名,使用javap能夠生成方法簽名,很熟練的話也可以自己寫方法簽名 在bin/classes目錄下執行 javap -s 全類名
1 public class MainActivity extends Activity { 2 static{ 3 System.loadLibrary("hello"); 4 } 5 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.activity_main); 10 } 11 12 public void click(View v){ 13 cLog(); 14 } 15 16 public native void cLog(); 17 18 public void show(String message){ 19 Builder builder = new Builder(this); 20 builder.setTitle("標題"); 21 builder.setMessage(message); 22 builder.show(); 23 } 24 25 }
#include <jni.h> #include <android/log.h> #define LOG_TAG "System.out" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) JNIEXPORT void JNICALL Java_com_mwp_ccalljava2_MainActivity_cLog (JNIEnv * env, jobject obj){ //列印log輸出 LOGD("我是C語言列印的debug日誌"); LOGI("我是C語言列印的info日誌"); //通過反射來呼叫java的方法,需要知道方法簽名,使用javap得到方法簽名 //在bin/classes目錄下執行 javap -s 全類名 //1、得到類的位元組碼物件 //jclass (*FindClass)(JNIEnv*, const char*); jclass clazz = (*env)->FindClass(env, "com/mwp/ccalljava2/MainActivity"); //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); jmethodID methodID = (*env)->GetMethodID(env, clazz, "show", "(Ljava/lang/String;)V"); //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); (*env)->CallVoidMethod(env,obj,methodID, (*env)->NewStringUTF(env, "這是彈窗的內容")); }
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_LDLIBS += -llog LOCAL_MODULE := hello LOCAL_SRC_FILES := log.c include $(BUILD_SHARED_LIBRARY)
十、模擬監測壓力感測器
1 public class MainActivity extends Activity { 2 static{ 3 System.loadLibrary("monitor"); 4 } 5 private MyProgressBar mpb; 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.activity_main); 10 11 mpb = (MyProgressBar) findViewById(R.id.mpb); 12 mpb.setMax(100); 13 } 14 15 public void start(View v){ 16 new Thread(){ 17 public void run() { 18 startMonitor(); 19 }; 20 }.start(); 21 } 22 23 public void stop(View v){ 24 stopMonitor(); 25 } 26 27 public native void startMonitor(); 28 public native void stopMonitor(); 29 30 //供本地方法呼叫重新整理UI 31 public void show(int pressure){ 32 mpb.setPressure(pressure); 33 } 34 }
#include <jni.h> #include <stdio.h> #include <stdlib.h> //模擬壓力感測其傳遞資料 int getPressure(){ return rand()%101; } //用於控制迴圈的開關 int monitor; JNIEXPORT void JNICALL Java_com_mwp_monitor_MainActivity_startMonitor (JNIEnv * env, jobject obj){ monitor = 1; int pressure; jclass clazz; jmethodID methodid; while(monitor){ //本地方法獲取感測器資料 pressure= getPressure(); //使用反射呼叫java方法重新整理介面顯示 //jclass (*FindClass)(JNIEnv*, const char*); clazz= (*env)->FindClass(env, "com/mwp/monitor/MainActivity"); //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); methodid= (*env)->GetMethodID(env, clazz, "show","(I)V"); // void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); (*env)->CallVoidMethod(env, obj, methodid, pressure); sleep(1); } } JNIEXPORT void JNICALL Java_com_mwp_monitor_MainActivity_stopMonitor (JNIEnv * env, jobject obj){ //結束迴圈 monitor = 0; }
十一、使用C++程式碼實現本地方法 1、把c檔案字尾名換成cpp 2、Android.mk檔案中的hello.c也要換成hello.cpp 3、c++的使用的環境變數結構體中,訪問了c使用的結構體的函式指標,函式名全部都是一樣的,只是引數去掉了結構體指標 4、訪問函式指標時,把env前面的*號去掉,因為此時env已經是一級指標 5、clean,清除之前編譯的殘留檔案 6、把宣告函式的h檔案放入jni資料夾中,include該h文
#include <jni.h> #include "com_mwp_cplusplus_MainActivity.h" JNIEXPORT jstring JNICALL Java_com_mwp_cplusplus_MainActivity_helloC (JNIEnv * env, jobject obj){ char* cstr = "hello from c"; //return (*env)->NewStringUTF(env, cstr); return env->NewStringUTF(cstr); }