天天看點

《Android的設計與實作:卷I》——第2章 2.7JNI應用層執行個體分析

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所示。

《Android的設計與實作:卷I》——第2章 2.7JNI應用層執行個體分析

接下來将分步實作應用層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方法。代碼如下:

《Android的設計與實作:卷I》——第2章 2.7JNI應用層執行個體分析

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中添加如下代碼即可:

《Android的設計與實作:卷I》——第2章 2.7JNI應用層執行個體分析

//這裡已經可以不用遵守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的使用和異常處理流程。

繼續閱讀