首先咱們要明確幾個概念,jni,ndk,共享庫(.so)。java
jni是java native interface的縮寫,java 本地接口。它提供了若干的API實現了Java和其餘語言的通訊(主要是C/C++)。從Java1.1開始,jni標準成爲java平臺的一部分,它容許Java代碼和其餘語言寫的代碼進行交互。android
ndk:Android NDK 是在SDK前面又加上了「原生」二字,即Native Development Kit,所以又被Google稱爲「NDK」。c++
.so:共享函數庫,在可執行程序啓動的時候加載,全部程序從新運行時均可自動加載共享函數庫中的函數。app
爲什麼要使用ndk?框架
通俗來講,jni提供一套標準,包括定義了一些數據類型,引用類型,對應於Java中的數據類型,引用類型。還有一些轉換函數,這些都定義在jni.h中。 函數
例如,java傳入的String參數,在c文件中被jni轉換爲jstring的數據類型,在c文件中聲明char* test,而後test = (char*)(*env)->GetStringUTFChars(env, jstring, NULL);注意:test使用完後,須要釋放指針變量:(*env)->ReleaseStringUTFChars(env, jstring, test);將char* test轉換爲jstring 用 (*env)->NewStringUTF(env,const char* );(const 指啥意思?)gradle
Android 函數庫是用c/c++寫的,框架層不能直接調用它,而是經過jni調用的。咱們也能夠本身用jni調用native層。ui
實戰。命令行
1,配置NDK環境,須要下載NDK開發包並配置。3d
2,在app build.gradle裏面配置ndk屬性。
3,靜態加載動態庫,編寫naive方法,和普通java方法基本沒區別。
static { System.loadLibrary("JniTest"); } public native String getStringFromNative();
4,生成頭文件。在android studio 的命令行界面中,進入/app/src/main/java目錄下,執行命令:
javah -d ../jni com.example.shengchanglu.test.MainActivity
這樣就在src/main/目錄中新增了jni目錄,以及jni/com_example_shengchanglu_test_MainActivity.h頭文件。
com_example_shengchanglu_test_MainActivity.h頭文件內容以下:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_example_shengchanglu_test_MainActivity */ #ifndef _Included_com_example_shengchanglu_test_MainActivity #define _Included_com_example_shengchanglu_test_MainActivity #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_shengchanglu_test_MainActivity * Method: getStringFromNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_shengchanglu_test_MainActivity_getStringFromNative (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
看返回值jstring對應於java中得String。
5,在jni目錄中新增main.c文件,去實現com_example_shengchanglu_test_MainActivity.h頭文件中定義的方法。
// // Created by shengchang lu on 15/9/2. // /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> #include <android/log.h> #ifndef LOG_TAG #define LOG_TAG "ANDROID_LAB" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #endif #ifndef _Included_com_example_shengchanglu_test_MainActivity #define _Included_com_example_shengchanglu_test_MainActivity #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_shengchanglu_test_MainActivity * Method: getStringFromNative * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_shengchanglu_test_MainActivity_getStringFromNative(JNIEnv * env , jobject j) { LOGE("log string from ndk."); return (*env)->NewStringUTF(env,"Hello From JNI!"); } #ifdef __cplusplus } #endif #endif
6,Android中調用native方法。
7,編譯,運行。在app/build/intermediates/下出現ndk目錄,生成了動態庫so文件和mk文件。
上面講的只是ndk開發最基本的。Java不只能夠調用jni方法,jni也能夠調用Java中屬性(靜態和非靜態),方法(靜態和非靜態)。
void AppAction::setUndoRedoState(bool undo, bool redo) { JNIEnv* jniEnv = EnvManager::shareInstance()->getEnv(); if (jniEnv == NULL) { return; } jclass jclz = NULL; jclz = jniEnv->FindClass( "com/fotoable/fotoproedit/activity/ProEditLightPenActivity"); if (jniEnv->ExceptionCheck() == JNI_TRUE) { jniEnv->ExceptionClear(); jniEnv->DeleteLocalRef(jclz); return; } jmethodID checkUndoRedoState = jniEnv->GetStaticMethodID(jclz, "checkUndoRedoState", "(ZZ)V"); if (checkUndoRedoState == NULL) { jniEnv->DeleteLocalRef(jclz); return; } jniEnv->CallStaticVoidMethod(jclz, checkUndoRedoState, undo,redo); if (jniEnv->ExceptionCheck() == JNI_TRUE) { jniEnv->ExceptionClear(); } jniEnv->DeleteLocalRef(jclz); }
根據上面的jni代碼,咱們能夠反推出在當前項目中有且只有一個類中有這個方法。
類名:com.fotoable.fotoproedit.activity.ProEditLightPenActivity
方法:public void static checkUndoRedoState(boolean b1,boolean b1);
調用方法完畢後,指針變量應該釋放,要否則會引發內存泄露,程序崩潰。難點是異常捕獲,ExceptionCheck只能捕獲上一行代碼引起的異常,且不能向Android層拋出,因此必須多加當心。
關於保存jni環境: EnvManager::shareInstance():
/* * UtilManager.h * * Created on: 2013-12-31 * Author: Administrator */ #ifndef UTILMANAGER_H_ #define UTILMANAGER_H_ #include <jni.h> class EnvManager { public: EnvManager(); virtual ~EnvManager(); static EnvManager* shareInstance(); static void destroy(); JNIEnv* getEnv(); void setEnv(JNIEnv* jniEnv); private: JNIEnv* env; }; #endif /* UTILMANAGER_H_ */
/* * UtilManager.cpp * * Created on: 2013-12-31 * Author: Administrator */ #include <stdio.h> #include <stdlib.h> #include "EnvManager.h" static EnvManager * sEnvManager = NULL; EnvManager * EnvManager::shareInstance() { if (!sEnvManager) { sEnvManager = new EnvManager(); } return sEnvManager; } void EnvManager::destroy() { delete sEnvManager; sEnvManager = NULL; } JNIEnv* EnvManager::getEnv(){ return env; } void EnvManager::setEnv(JNIEnv* jniEnv){ env = jniEnv; } EnvManager::EnvManager() { env = NULL; } EnvManager::~EnvManager() { delete env; }
後續:跨平臺開發方面。譬如在圖片處理方面,Android 和 iOS底層可使用cocos2dx ,Android 經過 jni 與上層界面交互,iOS能夠調用Cocos2dx,這樣底層的代碼是同樣的,iOS和Android只用各寫本身的界面就行。開發速度就比較快。
若是底層有內存泄露引發崩潰,整個引用也會崩潰,不會拋出異常。因此應該儘可能少作底層和Android層的來回頻繁切換,避免崩潰。