淺析JNI


layout: post title: "淺析JNI" date: 2013-06-26 22:31 comments: truejava

追求閱讀體驗請到:http://whbzju.github.io/blog/2013/06/01/android-jni-config/

內容概述

本文從如下4個部分進行:android

  1. Java層,聲明、使用native方法
  2. Java與Native如何關聯,即註冊的方式與實現
  3. Java與Native方法通訊,即如何互相調用
  4. Java與Native的數據結構對應關係

我相信,若是你弄懂了以上的問題,可使用jni技術進行基本的開發。本文經過實現一個簡單的demo,對以上的問題的進行解答。git

Java層---聲明、使用native方法

先看MediaScanner.java的代碼程序員

{% codeblock MediaScanner lang:java %}github

public class jniActivity extends Activity {web

private TextView tv;

/**
 * Called when the activity is first created.
 */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    TextView  tv = new TextView(this);
    tv.setText( stringFromJNI() );
    setContentView(tv);
}

// 這個方法採用靜態註冊,參考ndk自帶的例子hello-jni的實現
public native String  stringFromJNI();

// 修改爲動態註冊
public native String DynamicStringFromJNI();

// 提供方法,讓native層調用
public void testMethodForNativeCallJava(){
    Toast.makeText(getApplicationContext(), "Call from Native", Toast.LENGTH_SHORT).show();
}

// 加載jni庫
static{
   System.loadLibrary("learnJni");
}

}數據結構

{% endcodeblock %}app

能夠大體猜到,static塊load了jni的庫文件,有好幾個帶native聲明的方法,這些方法都沒有具體的實現,其實現應該在native層也就是c層。該方法對java來說使用起來沒什麼不一樣。Java層須要作的事情就結束了,祕密應該就在這兩個咱們不熟悉的部分。總結下Java層的工做:eclipse

  • 經過static塊來加載對應的jni庫
  • 聲明由關鍵字native修飾的方法

看來Jni對Java程序員仍是很友好的,使用起來很方便。ide

Java與Native如何關聯---註冊JNI方法

首先,咱們看下最簡單的native方法實現,修改自ndk自帶的sample:hello-jni的hellojni.c

{% codeblock learnjni lang:java %}

include <string.h>

include <jni.h>

/ This is a trivial JNI example where we use a native method * to return a new VM String. See the corresponding Java source * file located at: * *
/ jstring Java_com_example_learnjni_LearnJni_stringFromJNI( JNIEnv env, jobject thiz ) { return (env)->NewStringUTF(env, "Hello from JNI !"); } {% endcodeblock %}

明顯,這裏的函數名好奇怪,一大串。其實若是你有jni的基礎,你會發現這個函數名和你知道的靜態註冊和動態註冊都不同,這裏並無使用包含方法簽名的頭文件,而是使用虛擬機默認的函數調用方式。

註冊這個概念不難理解,打個比方,java和c是兩個世界的東西,若要將他們鏈接在一塊兒,必需要有一個統一的溝通標準,註冊就是將雙方的方法函數用一個標準描述,通知對方。JNI裏面有兩種註冊方式,分別是靜態註冊和動態註冊。下面咱們來詳細介紹下這兩種註冊方式:

靜態註冊

靜態註冊比較簡單,其步驟有二:

  1. 編譯聲明瞭native函數的java類,對每一個生成的class用javah生成一個頭文件,包含了native方法的簽名。操做以下:javah -o output package.classname,這樣會生成一個output.h的jni頭文件,package.classname是java編譯好的class文件。
  2. 在native層包含這個頭文件,實現裏面的函數聲明。好比生成learnjni頭文件的步驟以下:
    1. 先生成class文件,因爲learnjni是一個android工程,沒法直接用javac生成class文件。此時有兩種方式處理:一是經過android源碼編譯環境生成class;二是藉助ice生成class,好比eclipse或者intellij idea,找到它們的輸出路徑。好比個人intellij idea輸出路徑是IdeaProject/*/out
    2. cd IdeaProject/*/out
    3. javah -o output -classpath ~/IdeaProjects/Learn/learn-jni/src/ com.example.learn_jni.jniActivity
    4. 生成的output頭文件以下: {% codeblock output lang:java %} / DO NOT EDIT THIS FILE - it is machine generated /

      include <jni.h>

      / Header for class com_example_learn_jni_jniActivity /

ifndef _Included_com_example_learn_jni_jniActivity

define _Included_com_example_learn_jni_jniActivity

ifdef __cplusplus

extern "C" {

endif

/ * Class: com_example_learn_jni_jniActivity * Method: stringFromJNI * Signature: ()Ljava/lang/String; / JNIEXPORT jstring JNICALL Java_com_example_learn_1jni_jniActivity_stringFromJNI (JNIEnv *, jobject);

/ * Class: com_example_learn_jni_jniActivity * Method: DynamicStringFromJNI * Signature: ()Ljava/lang/String; / JNIEXPORT jstring JNICALL Java_com_example_learn_1jni_jniActivity_DynamicStringFromJNI (JNIEnv *, jobject);

ifdef __cplusplus

}

endif

endif

{% endcodeblock %}

須要解釋下靜態方法中native方法是如何和jni函數對應上。當Java層調用native方法,好比stringFromJNI時,它會從對應的JNI庫中尋找 Java_com_example_learn_1jni_jniActivity_stringFromJNI,若是找不到就報錯。若是找到,則爲兩者創建一個映射關係,實際上是保存了jni層函數的函數指針。固然,這項工做由虛擬機完成。

小結

能夠明顯的看出,靜態註冊方法有很多弊端。

  1. 須要用javah對java類生成頭文件,並且生成的jni層函數名字特別長,不方便書寫。
  2. 如果未來函數名有改動,或者native函數數量有變化,都須要從新生成頭文件,不易維護
  3. 初次調用native函數要根據函數名字搜索對應的jni層函數來創建關聯關係,影響效率。

未完待續

相關文章
相關標籤/搜索