Android Native jni 編程入門

來源:互聯網
上載者:User

標籤:

在某些情況下,java編程已經不能滿足我們的需要,比如一個複雜的演算法處理,這時候就需要用到jni(java native interface)技術;

  • jni 其實就是java和c/cpp之間進行通訊的一個介面規範,java可以調用c/cpp裡面的函數,同樣,c/cpp也可以調用java類的方法;

jni開發工具ndk的安裝:
在最新的ndk版本中,安裝ndk很簡單,只需要裝ndk的路徑配置到系統內容變數中即可;
在編譯的時候,進入工程根目錄;執行命令  ndk-build  即可完成編譯;

 

下面就通過一個例子一步一步的來初步學習jni

一、HelloWorld

建立一個工程,你甚至不需要其它額外的設定,然後在工程中添加一個jni目錄,然後就可以開始了;

1.建立一個java類HelloWorld.java

package com.jni;public class HelloWorld {    static {        System.loadLibrary("helloworld");    }    public native String helloworld();}

在HelloWorld中,定義了一個方法helloworld(),只不過這個方法被申明成了native的,並沒有具體的實現,具體功能我們在接下來的cpp檔案中實現;

2.在jni目錄下添加一個helloworld.cpp

#include <jni.h>#include <android/log.h>#include <string.h>#ifndef _Included_com_jni_HelloWorld // 1#define _Included_com_jni_HelloWorld#ifdef __cplusplus // 2extern "C" {#endif // 2JNIEXPORT jstring JNICALL Java_com_jni_HelloWorld_helloworld(JNIEnv *, jobject);#ifdef __cplusplus // 3}#endif // 3#endif // 1JNIEXPORT jstring JNICALL Java_com_jni_HelloWorld_helloworld(JNIEnv * env,        jobject obj) {    return env->NewStringUTF("helloworld");}

從上面這個cpp檔案中可以很明白的看出,它有一個方法,具體包括方法申明和方法實現兩個部分;但是相信大家也都看出來了,方法的命令很怪異,怎麼這麼長的方法名?

我們在這裡先思考一個問題,java類中的方法是如何調用c++中的方法的呢?要解決這個問題,就得先來看這個長長的方法名;

其實這是jni的一個規範之一,用於映射java方法和c/c++中的方法對應;

再來看在cpp中定義的函數名:Java_com_jni_HelloWorld_helloworld

其實不難看出,java檔案與cpp檔案中函數名的配對定義方式為Java + 包名 + java類名 + 方法/函數名,中間用_分隔;其中兩個參數分別是:

    • env:當前該線程的內容,包含線程裡面全部內容;
    • obj:當前類的執行個體,指.java檔案的內容(在該例子中即是HelloWorld類);

這裡的helloworld方法,其實就只是返回了一個單詞"helloworld";

3.在jni目錄下添加一個Android.mk檔案

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE    := helloworldLOCAL_SRC_FILES := helloworld.cppinclude $(BUILD_SHARED_LIBRARY)

4.在命令列下進入工程目錄執行 ndk-build 命令,然後運行程式,調用HelloWorld執行個體的helloworld方法就可以得到它的返回字串了;

 

二、jni調用Java類的方法(1)

通過上面的helloworld練手之後,我們來看一下jni調用java類裡面的方法的實現;

1.建立設一個MethodCall.java檔案如下

public class MethodCall {    final String tag = "MethodCall";    static {        System.loadLibrary("methodcall");    }    public native String jniCallMethod1();    public native String jniCallMethod2();    public native String jniCallStaticMethod();    public void javaMethod1() {        Log.e(tag, "javaMethod1");    }    public String javaMethod2() {        Log.e(tag, "javaMethod2");        return "javaMethod2";    }    public static void javaStaticMethod(String input) {        Log.e("MethodCall", "" + input);    }}

該類有6個方法,其中有3個是java類的方法,另外3個是native方法,3個native方法分別去調用3個java方法;

2.添加三個native方法具體實現 methodcall.cpp

#include <jni.h>#include <android/log.h>#include <string.h>#ifndef _Included_com_jni_MethodCall#define _Included_com_jni_MethodCall#ifdef __cplusplusextern "C" {#endifJNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallMethod1(JNIEnv *,        jobject);JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallMethod2(JNIEnv *,        jobject);JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallStaticMethod(JNIEnv *,        jobject);#ifdef __cplusplus}#endif#endifJNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallMethod1(JNIEnv * env,        jobject obj) {    jmethodID mid; // 方法標識id    jclass cls = env->GetObjectClass(obj); // 類的對象執行個體    mid = env->GetMethodID(cls, "javaMethod1", "()V");    env->CallVoidMethod(obj, mid);    return env->NewStringUTF("jniCallMethod1");}JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallMethod2(JNIEnv * env,        jobject obj) {    jmethodID mid; // 方法標識id    jclass cls = env->GetObjectClass(obj); // 類的對象執行個體    mid = env->GetMethodID(cls, "javaMethod2", "()Ljava/lang/String;");    jstring js = (jstring) env->CallObjectMethod(obj, mid);    return js;}JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallStaticMethod(        JNIEnv * env, jobject obj) {    jmethodID mid; // 方法標識id    jclass cls = env->GetObjectClass(obj); // 類的對象執行個體    mid = env->GetStaticMethodID(cls, "javaStaticMethod",            "(Ljava/lang/String;)V");    jstring input = env->NewStringUTF("jniCallStaticMethod->>javaStaticMethod");    env->CallStaticVoidMethod(cls, mid, input);    return env->NewStringUTF("jniCallStaticMethod");}

該cpp檔案中有3個方法(我這裡把方法名都寫得很明白直觀,相信不需要注釋都知道是調用的哪一個java方法)

我們知道,在java編程中,對一個類的調用,其實是先建立一個類的對象執行個體,然後再調用它的方法(這裡指的是非static方法) ,那麼我們是如何在c/c++檔案中調用java方法的呢?

回到上面的HelloWorld,我們講方法名的時候,下邊有隨便提到的方法的參數,其中,第二個參數obj其實就是我們在java中使用的類的執行個體,到這裡,相信是如何調用java方法的大家都明白了吧;

在java中,每一個方法其實都有一個id,我們在c/c++中不能直接通過obj來調用一個java方法,我們要先擷取方法的id,通過GetMethodID()來擷取,需要傳入類的類型,方法名,方法的簽名(方法簽名在文章後面會講到簽名規則);然後再線上程裡面調用java方法,通過env->Call****Method();需要傳入對象執行個體,方法id,或者其它參數;(上面只展示了幾個這種方法,其它的方法如果大家有需要用到可以自行尋找資料解決);

3.編寫Android.mk檔案,在Android.mk檔案後面添加如下內容

include $(CLEAR_VARS)LOCAL_MODULE    := methodcallLOCAL_SRC_FILES := methodcall.cppinclude $(BUILD_SHARED_LIBRARY)

4.執行ndk-build 命令,下面是分別執行3個jniCall****方法的結果

 

三、jni調用Java類的方法(1)

上面是c++調用java方法的例子,下面再帖一個c調用java方法的例子

1.Java檔案 MethodCall1.java

package com.jni;public class MethodCall1 {    static {        System.loadLibrary("methodcall1");    }    public static int value = 0;    public static void javaMethod() {        value = 12;    }    public native int jniCalljavaMethod();}

2.methodcall.c

#include <string.h>#include <jni.h>jint Java_com_jni_MethodCall1_jniCalljavaMethod(JNIEnv* env, jobject thiz)//env:當前該線程的內容,包含線程全部的東西;thiz:當前類的執行個體,指.java檔案的內容{    jint si;    jfieldID fid; // 一個欄位,實際上對應java類裡面的一個欄位或屬性;    jclass cls = (*env)->GetObjectClass(env, thiz); // 類的對象執行個體    jmethodID mid = (*env)->GetStaticMethodID(env, cls, "javaMethod", "()V"); // 一個方法的id    //(I)V  (I)I    if (mid == NULL) {        return -1;    }    (*env)->CallStaticVoidMethod(env, cls, mid); //調用callback方法    fid = (*env)->GetStaticFieldID(env, cls, "value", "I"); //取出value欄位    if (fid == NULL) {        return -2;    }    si = (*env)->GetStaticIntField(env, cls, fid); //取出欄位對應的值(fid欄位對應的值)    return si;    //    return (*env)->NewStringUTF(env, "init success");}

3.完善Android.mk檔案,參照二裡面第四步;

4.運行代碼

MethodCall1 mc1 = new MethodCall1();Log.e(tag, MethodCall1.value + "->" + mc1.jniCalljavaMethod());

 

四、方法簽名規則 

JNI類型簽名規則
Java類型 類型簽名 Java類型 類型簽名
boolean Z long J
byte B float F
char C double D
short S L全限定類名;
int I 數組 [元素類型簽名

 

 

 

 

 

 

 

上面是各種資料類型對應的簽名字元

  • 基礎資料型別 (Elementary Data Type)的簽名很簡單,只是一個選定的字母;
  • 類的簽名規則是:"L" + 全限定類名+";"三部分組成,其中全限定類名以"/"分隔;

方法的簽名組成:"(參數簽名)" + "傳回值簽名"

例如Java方法long fun(int n, String str, int[] arr);
根據上面的簽名規則可以得到其簽名為:(ILjava/lang/String;[I)J
上面的簽名分為兩部分:括弧裡面為函數的參數,參數的內容分三部分"I","Ljava/lang/String;","[I",之間沒有空格;括弧外邊是函數的傳回型別簽名。需要注意的是如果函數傳回型別為void則其中傳回型別簽名為V;

 

五、動態註冊函數

前面二和三都是c/c++裡面方法的名稱來映射函數,其實jni還為我們提供了動態註冊函數的功能;

1.添加java檔案 DynamicRegisterMethod.java

package com.jni;public class DynamicRegisterMethod {    static {        System.loadLibrary("dynamicregistermethod");    }    public native String dynamicRegisterMethod();}

2.添加 c 檔案 

#include <string.h>#include <jni.h>#ifndef _Included_org_spring_SpringUtils#define _Included_org_spring_SpringUtilsjstring JNICALL java_dynamicRegisterMethod(JNIEnv * env, jobject obj) {    return (*env)->NewStringUTF(env, "dynamicRegisterMethod");}static JNINativeMethod gmethods[] = { { "dynamicRegisterMethod",        "()Ljava/lang/String;", (void*) java_dynamicRegisterMethod } };static int registerNativeMethods(JNIEnv * env, const char* className,        JNINativeMethod* gMethods, int numMethods) {    jclass clazz;    clazz = (*env)->FindClass(env, className);    if (clazz == NULL)        return JNI_FALSE;    if (((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0)) {        return JNI_FALSE;    }    return JNI_TRUE;}static int registerNatives(JNIEnv* env) {    if (!registerNativeMethods(env, "com/jni/DynamicRegisterMethod", gmethods,            sizeof(gmethods) / sizeof(gmethods[0]))) {        return JNI_FALSE;    }    return JNI_TRUE;}jint JNI_OnLoad(JavaVM* vm, void* reserved) {    jint result = -1;    JNIEnv* env = NULL;    if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4)) {        goto fail;    }    if (registerNatives(env) != JNI_TRUE) {        goto fail;    }    result = JNI_VERSION_1_4;    fail: return result;}#endif

3.在Android.mk檔案中進行編譯的配置(省略,參考前面的例子)

4.ndk-build編譯項目 

DynamicRegisterMethod drm = new DynamicRegisterMethod();Log.e(tag, drm.dynamicRegisterMethod());

執行結果:

可以看到通過動態註冊方法的方式,也是成功的調用了 native 方法;

 

六、加入連結庫

在程式開發過程中,會頻繁的用到調試,方式有很多種,下面要講的這一種是通過log列印資訊來列印程式運行時的一些狀態數值;

修改Android.mk檔案,添加一句代碼

include $(CLEAR_VARS)LOCAL_LDLIBS += -llog //LDLIBS:串連libs,後面跟的參數為需要連結的libs,-llog表示Android中的Log庫;include $(BUILD_SHARED_LIBRARY)

加入了log庫之後,即可通過c/c++檔案直接列印log資訊;

在c/c++中調用log列印輸出資訊:

#include <android/log.h>
__android_log_print(ANDROID_LOG_ERROR, "hello", "livingstone");    __android_log_print(ANDROID_LOG_DEBUG, "hello", "livingstone %d" ,23);

 

 

例子拖管地址:

https://github.com/a284628487/JniSample

Android Native jni 編程入門

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.