2.7 jni應用層執行個體分析
2.2節講解了jni在應用架構層的使用,那麼應用層又是如何使用jni的呢?本節将通過一個執行個體來示範在應用層如何配合ndk開發基于jni的應用程式。
ndk給基于jni的應用開發帶來了極大的便利。隻需要以下三步:
步驟1 在eclipse中建立android工程,并在項目根目錄建立jni目錄,然後在jni目錄加入jni層的實作代碼和對應的android.mk檔案。
步驟2 将項目複制到ndk samples目錄,運作ndk-build指令。ndk會自動編譯出共享庫,并置于armeabi目錄下。
步驟3 将新生成的目錄和檔案從ndk中複制回eclipse。工程目錄如圖2-4所示。
接下來将分步實作應用層jni。
2.7.1 java層分析
在eclipse中建立android工程,工程名為appjni,并生成一個啟動activity: appjniactivity。内容如下:
package com.allongriver.jni;
import android.app.activity;
import android.os.bundle;
import android.util.log;
public class appjniactivity extends activity {
@override
public void oncreate(bundle savedinstancestate) {
}
//聲明一個native方法,需要在jni中實作
/jni中需要調用callback方法,用來示範在jni中如何操作java類。為了
private void callback(){
以上代碼實作在java層中對native 方法show()的調用,并在調用過程中,由jni函數回調java層callback方法。
2.7.2 jni層代碼和異常處理
接下來看如何在jni層實作這個native方法。代碼如下:
jstring java_com_allongriver_jni_appjniactivity_show( jnienv env,jobject thiz )
{
如果把以上代碼清除異常部分(如下所示)注釋掉,看看會出現什麼結果。
//(env)->exceptionclear(env);//清除異常
運作程式後,logcat中的日志資訊如下:
d/dalvikvm(4734): trying to load lib //加載共享庫
/data/data/com.allongriver.jni/lib/libapp_jni.so 0x4051b7f0
d/dalvikvm(4734): added shared lib
d/dalvikvm(4734): no jni_onload found in //執行共享庫中的第一個方法jni_onload
/data/data/com.allongriver.jni/lib/libapp_jni.so 0x4051b7f0, skipping init
d/appjniactivity(4734): call back from native //jni層發現異常
w/system.err(4734): java.lang.nullpointerexception
……
d/androidruntime(4734): shutting down vm
//異常被抛給java層,java層繼續列印出異常資訊
e/androidruntime(4734): fatal exception: main// 程序終止
在jni程式設計中,一定要處理好jni函數調用過程中可能出現的異常。
至此,讀者可能會發現,應用層jni程式設計跟應用架構層jni程式設計沒有多少相關性。難道jni提供兩套程式設計機制?
答案是肯定的。appjni例子示範的是傳統的jni程式設計方式,符合jni規範。但其缺點很明顯,具體有三個:
1)需要遵守繁瑣的jni實作方法的命名規則。比如要嚴格遵守show函數的命名規則,如果出錯,将無法調用到jni層的實作方法:
2)如果采用應用層的jni使用方式,就需要在架構的java層加入system.loadlibrary
("app_jni")這樣的加載共享庫的代碼。而應用架構層會頻繁調用,嚴重影響效率。
3)虛拟機在共享庫中搜尋定位jni實作方法效率也受影響。android應用架構層采用函數注冊的方法回避這些問題。
本章開頭log系統的例子就是采用函數注冊的方法。這是不是意味着在應用層就不能使用這種方法?答案顯然是否定的。接下來把appjni改造成函數注冊。隻需要借鑒log系統jni注冊流程,在原有的app_jni中添加如下代碼即可:
//這裡已經可以不用遵守jni的函數命名規則,因為我們已經做了函數映射
// 這部分代碼不需要任何改變
//下面都是為了完成函數注冊添加的代碼。這裡是java層方法和jni層方法的映射
static jninativemethod gmethods[] = {
(void)java_com_allongriver_jni_appjniactivity_show},
};
/
register several native methods for one class.
static int registernativemethods(jnienv env, const char classname,
register native methods for all classes we know about.
returns jni_true on success.
static int registernatives(jnienv env)
if (!registernativemethods(env, "com/allongriver/jni/appjniactivity",
return jni_false;
return jni_true;
/虛拟機執行system.loadlibrary("app_jni")後,進入libapp_jni.so後
會首先執行這個方法,是以我們在這裡做注冊的動作*/
jint jni_onload(javavm vm, void reserved)
fail:
return result;
至此讀者可能會問,為什麼同樣是以函數注冊方式調用jni,在log系統中沒有執行system.loadlibrary, 也沒有在jni_onload中執行注冊函數呢?
那是因為系統在啟動的過程中已經幫我們做了。android啟動篇會介紹這部分内容。如果應用架構層某些子產品不是在系統啟動過程中自動load并注冊,也需要上述jni_onload步驟。讀者可以參考android_media_mediaplayer.cpp的例子。
2.8 本章小結
本章以log系統的jni執行個體為引線,貫穿了jni技術的主要方面,讓讀者對jni有足夠的認識,具備深入學習架構層代碼的基礎。
本章首先概括了jni在應用層和架構層中的地位;然後以log系統為例,介紹了架構層jni調用和注冊流程,深入分析了jnienv的設計和實作;然後詳細介紹了jni資料類型轉換、方法簽名、對象操作、域和方法操作、全局引用、局部引用、弱引用以及異常處理等jni核心内容;最後以一個應用層jni執行個體示範了ndk的使用和異常處理流程。