Android筆記之JNI學習
交叉編譯
- 在一個平臺上去編譯另一個平臺上可以執行的本地代碼
- cpu平臺 arm x86 mips
- 操作系統平臺 windows linux mac os
- 原理 模擬不同平臺的特性去編譯代碼
jni開發工具
- ndk native develop kit
- ndk目錄
- docs 幫助文檔
- platforms 好多平臺版本文件夾 選擇時選擇項目支持的最小版本號對應的文件夾
- 每一個版本號的文件夾中放了 不同cpu架構的資源文件
- include文件夾 jni開發中常用的 .h頭文件
- lib 文件夾 google打包好的 提供給開發者使用的 .so文件
- samples google官方提供的樣例工程 可以參考進行開發
- android-ndk-r9d\build\tools linux系統下的批處理文件 在交叉編譯時會自動調用
- ndk-build 交叉編譯的命令
- cdt eclipse的插件 高亮C代碼 C的代碼提示
jnihelloworld
-
jni開發的步驟
-
①寫java代碼 聲明本地方法 用到native關鍵字 本地方法不用去實現
-
②項目根目錄下創建jni文件夾
-
③在jni文件夾下創建.c文件
- 本地函數命名規則: Java_包名_類名_本地方法名
- JNIENV* env JNIEnv 是JniNativeInterface這個結構體的一級指針
- JniNativeInterface這個結構體定義了大量的函數指針
- env 就是結構體JniNativeInterface這個結構體的二級指針
- (*env)->調用結構體中的函數指針
- 第二個參數jobject 調用本地函數的java對象就是這個jobject
-
④ 導入<jni.h>
-
⑤ 創建Android.mk makefile 告訴編譯器.c的源文件在什麼地方,要生成的編譯對象的名字是什麼
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello #指定了生成的動態鏈接庫的名字
LOCAL_SRC_FILES := hello.c #指定了C的源文件叫什麼名字include $(BUILD_SHARED_LIBRARY)
-
⑥ 調用ndk-build編譯c代碼生成動態鏈接庫.so文件 文件的位置 lib->armeabi->.so
-
⑦ 在java代碼中加載動態鏈接庫 System.loadlibrary(“動態鏈接庫的名字”); Android.mkLOCAL_MODULE所指定的名字
jni開發中的常見錯誤
- java.lang.UnsatisfiedLinkError: Native method not found: 本地方法沒有找到
- 本地函數名寫錯
- 忘記加載.so文件 沒有調用System.loadlibrary
- findLibrary returned null
- System.loadLibrary(“libhello”); 加載動態鏈接庫時 動態鏈接庫名字寫錯
- 平臺類型錯誤 把只支持arm平臺的.so文件部署到了 x86cpu的設備上
- 在jni目錄下創建 Application.mk 在裏面指定
- APP_ABI := armeabi
APP_PLATFORM := android-14
- javah
- jdk 1.7 項目 src目錄下運行javah
- jdk 1.6 項目 bin目錄下 classes文件夾
- javah native方法聲明的java類的全類名
jni簡便開發流程
- ① 寫java代碼 native 聲明本地方法
- ② 添加本地支持 右鍵單擊項目->andorid tools->add native surport
- 如果發現 finish不能點擊需要給工作空間配置ndk目錄的位置
- window->preferences->左側選擇android->ndk 把ndk解壓的目錄指定進來
- ③ 如果寫的是.c的文件 先修改一下生成的.cpp文件的擴展名 不要忘了 相應修改Android.mk文件中LOCAL_SRC_FILES的值
- ④ javah生成頭文件 在生成的頭文件中拷貝c的函數名到.c的文件
- ⑤ 解決CDT插件報錯的問題
- 右鍵單擊項目選擇 properties 選測 c/c++ general->paths and symbols->include選項卡下->點擊add…->file system 選擇ndk目錄下 platforms文件夾 對應平臺下(項目支持的最小版本)
usr 目錄下 arch-arm -> include 確定後 會解決代碼提示和報錯的問題 - ⑥編寫C函數 如果需要單獨編譯一下c代碼就在c/c++視圖中找到小錘子
- 如果想直接運行到模擬器上 就不用錘子了
- ⑦ java代碼中不要忘了 system.loadlibrary();
C代碼中向logcat輸出內容
Android.mk文件增加以下內容
LOCAL_LDLIBS += -llog
C代碼中增加以下內容
#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__)
- define C的宏定義 起別名 #define LOG_TAG “System.out” 給"System.out"起別名LOG_TAG
- #define LOGI(…) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, VA_ARGS)
- 給 __android_log_print函數起別名 寫死了前兩個參數 第一個參數 優先級 第二個參數TAG
- VA_ARGS 可變參數的固定寫法
- LOGI(…)在調用的時候 用法跟printf()一樣
C代碼回調java方法
- ① 找到字節碼對象
- //jclass (FindClass)(JNIEnv, const char*);
- //第二個參數 要回調的java方法所在的類的路徑 “com/itheima/callbackjava/JNI”
- ② 通過字節碼對象找到方法對象
- //jmethodID (GetMethodID)(JNIEnv, jclass, const char*, const char*);
- 第二個參數 字節碼對象 第三個參數 要反射調用的java方法名 第四個參數 要反射調用的java方法簽名
- javap -s 要獲取方法簽名的類的全類名 項目/bin/classes 運行javap
- ③ 通過字節碼創建 java對象(可選) 如果本地方法和要回調的java方法在同一個類裏可以直接用 jni傳過來的java對象 調用創建的Method
- jobject obj =(*env)->AllocObject(env,claz);
- 當回調的方法跟本地方法不在一個類裏 需要通過剛創建的字節碼對象手動創建一個java對象
- 再通過這個對象來回調java的方法
- 需要注意的是 如果創建的是一個activity對象 回調的方法還包含上下文 這個方法行不通!!!回報空指針異常
- ④ 反射調用java方法
- //void (CallVoidMethod)(JNIEnv, jobject, jmethodID, …);
- 第二個參數 調用java方法的對象 第三個參數 要調用的jmethodID對象 可選的參數 調用方法時接收的參數