日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

一天掌握Android JNI本地编程 快速入门

發布時間:2023/12/9 Android 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一天掌握Android JNI本地编程 快速入门 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、JNI(Java Native Interface)

? ? ? ?1、什么是JNI: ??????????????JNI(Java Native Interface):java本地開發接口

? ? ? ? ? ? ? JNI是一個協議,這個協議用來溝通java代碼和外部的本地代碼(c/c++)

? ? ? ? ? ? ? 外部的c/c++代碼也可以調用java代碼 ? ? ? ?2、為什么使用JNI ? ? ? ? ? ? ? 效率上 C/C++是本地語言,比java更高效 ? ? ? ? ? ? ? 代碼移植,如果之前用C語言開發過模塊,可以復用已經存在的c代碼 ? ? ? ? ? ? ? java反編譯比C語言容易,一般加密算法都是用C語言編寫,不容易被反編譯 ? ? ? ?3、Java基本數據類型與C語言基本數據類型的對應 ? ? ? ? ? ? ?? ? ? ? ?3、引用類型對應 ? ? ? ? ? ? ?? ? ? ? ?4、堆內存和棧內存的概念 ? ? ? ? ? ? ? 棧內存:系統自動分配和釋放, ? ? ? ? ? ? ? ? ? ? ? 保存全局、靜態、局部變量, ? ? ? ? ? ? ? ? ? ? ? 在站上分配內存叫靜態分配, ? ? ? ? ? ? ? ? ? ? ? 大小一般是固定的 ? ? ? ? ? ? ? 堆內存:程序員手動分配(malloc/new)和釋放(free/java不用手動釋放,由GC回收), ? ? ? ? ? ? ? ? ? ? ? 在堆上分配內存叫動態分配, ? ? ? ? ? ? ? ? ? ? ? 一般硬件內存有多大堆內存就有多大 二、交叉編譯 ? ? ? ?1、交叉編譯的概念 ? ? ? ? ? 交叉編譯即在一個平臺,編譯出另一個平臺能夠執行的二進制代碼 ? ? ? ? ? 主流平臺有: Windows、 Mac os、 Linux ? ? ? ? ? 主流處理器: x86、 arm、 mips ? ? ? ?2、交叉編譯的原理 ? ? ? ? ? 即在一個平臺上,模擬其他平臺的特性 ? ? ? ? ? 編譯的流程: 源代碼-->編譯-->鏈接-->可執行程序 ? ? ? ?3、交叉編譯的工具鏈 ? ? ? ? ? 多個工具的集合,一個工具使用完后接著調用下一個工具 ? ? ? ?4、常見的交叉編譯工具 ? ? ? ? ? NDK(Native Development Kit): 開發JNI必備工具,就是模擬其他平臺特性類編譯代碼的工具 ? ? ? ? ? CDT(C/C++ Development Tools): 是Eclipse開發C語言的一個插件,高亮顯示C語言的語法 ? ? ? ? ? Cygwin: 一個Windows平臺的Unix模擬器(可以參考之前博客Cygwin簡介及使用 ? ? ? ?5、NDK的目錄結構(可以在Google官網下載NDK開發工具,需要FQ) ? ? ? ? ??docs: 幫助文檔 ? ? ? ? ? build/tools:linux的批處理文件 ? ? ? ? ? platforms:編譯c代碼需要使用的頭文件和類庫 ? ? ? ? ? prebuilt:預編譯使用的二進制可執行文件 ? ? ? ? ? sample:jni的使用例子 ? ? ? ? ? source:ndk的源碼 ? ? ? ? ? toolchains:工具鏈 ? ? ? ? ? ndk-build.cmd:編譯打包c代碼的一個指令,需要配置系統環境變量 三、JNI的第一個例子 ? ? ? ? ? 好了,準備知識已經完畢,下面開始我們的一個JNI例子。 ? ? ? ? 1、新建一個Android項目,在根目錄下創建 jni文件夾,用于存放 C源碼。 ? ? ? ? 2、在java代碼中,創建一個本地方法 getStringFromC 本地方法沒有方法體。
  • 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.cinclude $(BUILD_SHARED_LIBRARY)

    ?????????5、在jni目錄下執行 ndk-build.cmd指令,編譯c文件

  • ? ? ? ? ?6、在java代碼中加載編譯后生成的so類庫,調用本地方法,將項目部署到虛擬機上之后就會發現toast彈出的C代碼定義的字符串,第一個例子執行成功了。 static{//加載打包完畢的 so類庫System.loadLibrary("hello");}

    ?????????7、jni打包的C語言類庫默認僅支持 arm架構,需要在jni目錄下創建 Android.mk 文件添加如下代碼可以支持x86架構

  • APP_ABI := armeabi armeabi-v7a x86


    四、JNI常見錯誤

  • ? ? ? ? ?1、findLibrary returned null: ? ? ? ? ? ? ? ? CPU平臺不匹配 或者 在加載類庫時,類庫名字寫錯了 ? ? ? ? ?2、本地方法找不到: ? ? ? ? ? ? ? ? 忘記加載類庫了 或者 C代碼中方法名寫錯了 ? ? 五、javah工具與javap工具 ? ? ? ? ?1、javah: ?生成本地方法頭文件 ? ? ? ? ? ? 需要在C/C++模塊下才能生效 ? ? ? ? ? ? 在JDK1.7中,在src目錄下執行javah 全類名 ? ? ? ? ? ? 在JDK1.6中,在bin/classes目錄下執行 ? ? ? ? ?2、javap: ?打印方法簽名 ? ? ? ? ? ? 在C語言中調用java的方法需要用到反射,C語言的反射需要一個方法簽名,使用javap能夠生成方法簽名,很熟練的話也可以自己寫方法簽名 ? ? ? ? ? ? 在bin/classes目錄下執行 javap -s 全類名 ? ? ? ? ? ?? 六、使用本地方法加密字符串的一個小例子 ? ? ? C語言字符串與Java中的字符串類型不同,所以需要進行字符串類型轉換。 ? ? ? 一個重要的思想:C語言計算字符串的長度不方便,但是java很方便,只需要調用一個length()方法就可以,所以像這種需求,那個語言有優勢就用哪個語言算,算完當做參數傳遞給另一種語言就ok。 ? ? ? ? ? ? ? ? ? ? ? 混合語言編程這應該是一種非常有用的思想。 ? ? ?Java非常容易被反編譯,所以加密都是用 c語言寫的
    #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)


    十、模擬監測壓力傳感器

    ? ? ? ? 傳感器的原理是使用敏感電阻如(光敏電阻,熱敏電阻)等監測電流電壓的變化 ? ? ? ? Android程序只需要處理傳感器傳遞的數據,并將其顯示在界面上就可以。 ? ? ? ? 下面模擬一個壓力傳感器來練習JNI編程
    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); }

    總結

    以上是生活随笔為你收集整理的一天掌握Android JNI本地编程 快速入门的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。