天天看點

android JNI調用機制

jni的出現使得開發者既可以利用java語言跨平台、類庫豐 富、開發便捷等特點,又可以利用native語言的高效。

android JNI調用機制

jni是jvm實作中的一部分,是以native語言和java代碼都運作在jvm的宿主環境。

jni是一個雙向的接口:開發者不僅可以通過jni在java代碼中通路native子產品,還可以在 native代碼中嵌入一個jvm,并通過jni通路運作于其中的java子產品。可見,jni擔任了一個橋梁的角色,它将jvm與native子產品聯系起 來,進而實作了java代碼與native代碼的互訪。在ophone上使用java虛拟機是為嵌入式裝置特别優化的dalvik虛拟機。每啟動一個應 用,系統會建立一個新的程序運作一個dalvik虛拟機,是以各應用實際上是運作在各自的vm中的。dalvik vm對jni的規範支援的較全面,對于從jdk

1.2到jdk 1.6補充的增強功能也基本都能支援。

缺點:由于native子產品的使用,java代碼會喪失其原有的跨平台性和類型安全等特性。此外,在jni應用中,java代碼與native代 碼運作于同一個程序空間内;對于跨程序甚至跨宿主環境的java與native間通信的需求,可以考慮采用socket、web service等ipc通信機制來實作。

互的類型可以分為在java代碼中調用native子產品和在native代碼中調用java子產品兩種。

java調用native子產品

hellojni.java

/* a native method that is implemented by the  

   * 'hello-jni' native library, which is packaged  

   * with this application.  

   */    

  public native string  stringfromjni();  

這個stringfromjni()函數就是要在java代碼中調用的native函數。

hello-jni.c

#include <string.h>    

#include <jni.h>    

jstring    

java_com_example_hellojni_hellojni_stringfromjni( jnienv* env,    

                                                 jobject thiz ) {    

        return (*env)->newstringutf(env, "hello from jni !");    

}    

這個native函數對應的正是我們在com.example.hellojni.hellojni這個中聲明的native函數string stringfromjni()的具體實作。

jni函數的命名規則: java代碼中的函數聲明需要添加native 關鍵 字;native的對應函數名要以“java_”開頭,後面依次跟上java的“package名”、“class名”、“函數名”,中間以下劃線“_” 分割,在package名中的“.”也要改為“_”。此外,關于函數的參數和傳回值也有相應的規則。對于java中的基本類型如int 、double 、char 等,在native端都有相對應的類型來表示,如jint 、jdouble 、jchar 等;其他的對象類型則統統由jobject 來表示(string 是個例外,由于其使用廣泛,故在native代碼中有jstring 這個類型來表示,正如在上例中傳回值string 對應到native代碼中的傳回值jstring )。而對于java中的數組,在native中由jarray 對應,具體到基本類型和一般對象類型的數組則有jintarray 等和jobjectarray 分别對應(string 數組在這裡沒有例外,同樣用jobjectarray 表示)。還有一點需要注意的是,在jni的native函數中,其前兩個參數jnienv *和jobject 是必需的——前者是一個jnienv 結構體的指針,這個結構體中定義了很多jni的接口函數指針,使開發者可以使用jni所定義的接口功能;後者指代的是調用這個jni函數的java對象,有點類似于c++中的this 指針。在上述兩個參數之後,還需要根據java端的函數聲明依次對應添加參數。在上例中,java中聲明的jni函數沒有參數,則native的對應函數隻有類型為jnienv *和jobject 的兩個參數。

,要使用jni函數,還需要先加載native代碼編譯出來的動态庫檔案(在windows上是.dll,在linux上則為.so)。這個動作是通過如下語句完成的:

static {  

    system.loadlibrary("hello-jni");  

}  

jni函數的使用方法和普通java函數一樣。在本例中,調用代碼如下:

textview tv = new textview(this);    

tv.settext( stringfromjni() );   

setcontentview(tv);    

native調用java子產品

package com.example.hellojni;    

public class sayhello {    

        public string sayhellofromjava(string nativemsg) {    

               string str = nativemsg + " but shown in java!";    

               return str;    

        }    

}   

從ophone的系統架構來看,jvm和native系統庫位于核心之上,構成ophone runtime;更多的系統功能則是通過在其上的application framework以java api的形式提供的。是以,如果希望在native庫中調用某些系統功能,就需要通過jni來通路application framework提供的api。

一般來說,要在native代碼中通路java對象,有如下幾個步驟:

1.         得到該java對象的類定義。jni定義了jclass 這個類型來表示java的類的定義,并提供了findclass接口,根據類的完整的包路徑即可得到其jclass 。

2.         根據jclass 建立相應的對象實體,即jobject 。在java中,建立一個新對象隻需要使用new 關鍵字即可,但在native代碼中建立一個對象則需要兩步:首先通過jni接口getmethodid得到該類的構造函數,然後利用newobject接口構造出該類的一個執行個體對象。

3.         通路jobject 中的成員變量或方法。通路對象的方法是先得到方法的method id,然後使用call<type>method 接口調用,這裡type對應相應方法的傳回值——傳回值為基本類型的都有相對應的接口,如callintmethod;其他的傳回值(包括string) 則為callobjectmethod。可以看出,建立對象實質上是調用對象的一個特殊方法,即構造函數。通路成員變量的步驟一樣:首先 getfieldid得到成員變量的id,然後get/set<type>field讀/寫變量值。

jstring hellofromjava( jnienv* env ) {    

       jstring str = null;    

       jclass clz = (*env)->findclass(env, "com/example/hellojni/sayhello");    

       jmethodid ctor = (*env)->getmethodid(env, clz, "<init>", "()v");    

       jobject obj = (*env)->newobject(env, clz, ctor);    

       jmethodid mid = (*env)->getmethodid(env, clz, "sayhellofromjava", "(ljava/lang/string;)ljava/lang/string;");    

       if (mid) {    

              jstring jmsg = (*env)->newstringutf(env, "i'm born in native.");    

              str = (*env)->callobjectmethod(env, obj, mid, jmsg);    

       }    

       return str;    

提一下程式設計時要注意的要點:1、findclass要寫明java類的完整包路徑,并将 “.”以“/”替換;2、getmethodid的第三個參數是方法名(對于構造函數一律用“<init>”表示),第四個參數是方法的“簽 名”,需要用一個字元串序清單示方法的參數(依聲明順序)和傳回值資訊。

如上這種使用newobject建立的對象執行個體被稱為“local reference”,它僅在建立它的native代碼作用域内有效,是以應避免在作用域外使用該執行個體及任何指向它的指針。如果希望建立的對象執行個體在作用 域外也能使用,則需要使用newglobalref接口将其提升為“global reference”——需要注意的是,當global

reference不再使用後,需要顯式的釋放,以便通知jvm進行垃圾收集。

native子產品的編譯與釋出

引用位址:http://mysuperbaby.iteye.com/blog/915425

二、

<span style="font-family: arial, helvetica, sans-serif;">原文位址:http://www.open-open.com/lib/view/open1324909652374.html</span>  

android jni是連接配接android java部分和c/c++部分的紐帶,完整使用jni需要java代碼和c/c++代碼。其中c/c++代碼用于生成庫檔案,java代碼用于引用c /c++庫檔案以及調用c/c++方法。

jnitest.java  

package com.hello.jnitest;  

import android.app.activity;  

import android.os.bundle;  

public class jnitest extends activity {  

    /** called when the activity is first created. */  

    @override  

    public void oncreate(bundle savedinstancestate) {  

        super.oncreate(savedinstancestate);  

        setcontentview(r.layout.main);  

        nadd test = new nadd();  

        settitle("the native add result is "+string.valueof(test.nadd(10, 20)));  

    }   

nadd.java  

public class nadd {  

    static {  

        system.loadlibrary("hello_jni");  

    }  

public native int nadd(int a, int b);  

java代碼說明:

1)jnitest.java是一個activity的類對象,在該類對象中生成調用jni函數的類對象,同時調用jni方法,最後将jni方法的結果顯示到标題欄上;

2)nadd.java是一個引用和聲明jni庫和函數的類,其中system.loadlibrary();函數用來引用jni庫,預設jni庫放在 android系統的/system/lib/目錄下;public nadd int nadd(int a, int b);為聲明需要在java程式中使用的jni庫中的函數;

jni中java部分的代碼到此就結束了,總結一下在java代碼中需要做兩件事:

1)使用system.loadlibrary()函數來引用jni庫;

2)聲明調用jni庫的函數且前面添加native關鍵字;

android c/c++部分代碼:

#define log_tag "hello-jni"  

#include <stdio.h>  

#include <stdlib.h>  

#include <unistd.h>  

#include <sys/types.h>  

#include <sys/stat.h>  

#include <fcntl.h>  

#include <assert.h>  

#include "jni.h"  

#include "jnihelp.h"  

#include "android_runtime/androidruntime.h"  

static jint com_hello_jnitest_jnitest_nadd(jnienv *env, jobject obj, jint a, jint b)  

{  

    return (a * b);  

static jninativemethod gmethods[] = {  

{"nadd", "(ii)i", (void *)com_hello_jnitest_jnitest_nadd},};  

static int register_android_test_hello(jnienv *env)  

    return android::androidruntime::registernativemethods(env, "com/hello/jnitest/nadd", gmethods, nelem(gmethods));  

jint jni_onload(javavm *vm, void *reserved)  

    jnienv *env = null;  

    if (vm->getenv((void **)&env, jni_version_1_4) != jni_ok) {  

        printf("error getenv\n");  

        return -1;  

    assert(env != null);  

    if (register_android_test_hello(env) < 0) {  

        printf("register_android_test_hello error.\n");  

    return jni_version_1_4;  

jni c/c++代碼說明:

1)jni_onload()函數。該函數在java程式調用system.loadlibrary()時,被調用執行,用于向javavm注冊jni函數等。在本例中首先通過參數javavm(java虛拟機指針)擷取目前應用程式所在的線程,即:jnienv。再通過調用 android::androidruntime::registernativemethods()注冊native實作的函數指針。

2)jni函數和java調用函數的映射關系。使用jninativemethod将java調用的函數名與jni實作的函數名聯系在一起;

3)jni函數實作;

android.mk代碼:

local_path := $(call my-dir)  

include $(clear_vars)  

local_prelink_module := false  

local_src_files := \  

com_hello_jnitest.cpp  

local_shared_libraries := \  

libandroid_runtime  

local_module := libhello_jni  

include $(build_shared_library)  

需要注意的是:

1)jni c/c++部分的代碼需要在android源代碼樹上進行編譯,編譯完成後我的做法是直接将生成的.so通過adb push方法上傳到android虛拟機的/system/lib/目錄下;

2)java代碼可以在eclipse下直接編譯且在虛拟機上執行;

編譯jni c/c++部分代碼(在android核心源代碼根目錄下):

#make libhello_jni

之後在out/target/product/generic/system/lib/目錄下生成libhello_jni.so

上傳libhello_jni.so到android虛拟機:

#adb push out/target/product/generic/system/lib/libhello_jni.so /system/lib

注意:此時有可能出現out of memory的錯誤提示。當出現如上錯誤提示時,需要使用#adb remount重新加載一下就可以了。

另外,也有可能直接使用eclipse啟動android虛拟機時出現上述錯誤且使用#adb remount也出現的情況,此時需要手動啟動android虛拟機,如:#emulator -avd xxx -partition-size 128,之後在使用#adb push就可以了。