Android Native jni 編程入門

在某些狀況下,java編程已經不能知足咱們的須要,好比一個複雜的算法處理,這時候就須要用到jni(java native interface)技術;html

  • jni 其實就是java和c/cpp之間進行通訊的一個接口規範,java能夠調用c/cpp裏面的函數,一樣,c/cpp也能夠調用java類的方法;

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

 

下面就經過一個例子一步一步的來初步學習jniandroid

1、HelloWorldc++

新建一個工程,你甚至不須要其它額外的設置,而後在工程中添加一個jni目錄,而後就能夠開始了;git

1.新建一個java類HelloWorld.javagithub

package com.jni;

public class HelloWorld {
    static {
        System.loadLibrary("helloworld");
    }

    public native String helloworld();
}

在HelloWorld中,定義了一個方法helloworld(),只不過這個方法被申明成了native的,並無具體的實現,具體功能咱們在接下來的cpp文件中實現;算法

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

#include <jni.h>
#include <android/log.h>
#include <string.h>

#ifndef _Included_com_jni_HelloWorld // 1
#define _Included_com_jni_HelloWorld

#ifdef __cplusplus // 2
extern "C" {
#endif // 2
JNIEXPORT jstring JNICALL Java_com_jni_HelloWorld_helloworld(JNIEnv *, jobject);
#ifdef __cplusplus // 3
}
#endif // 3
#endif // 1

JNIEXPORT 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    := helloworld
LOCAL_SRC_FILES := helloworld.cpp
include $(BUILD_SHARED_LIBRARY)

4.在命令行下進入工程目錄執行 ndk-build 命令,而後運行程序,調用HelloWorld實例的helloworld方法就能夠獲得它的返回字符串了;

 

2、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 __cplusplus
extern "C" {
#endif
JNIEXPORT 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
#endif

JNIEXPORT 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    := methodcall
LOCAL_SRC_FILES := methodcall.cpp
include $(BUILD_SHARED_LIBRARY)

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

 

3、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());

 

4、方法簽名規則 

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

 

 

 

 

 

 

 

上面是各類數據類型對應的簽名字符

  • 基本數據類型的簽名很簡單,只是一個選定的字母;
  • 類的簽名規則是:"L" + 全限定類名+";"三部分組成,其中全限定類名以"/"分隔;

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

例如Java方法long fun(int n, String str, int[] arr);
根據上面的簽名規則能夠獲得其簽名爲:(ILjava/lang/String;[I)J
上面的簽名分爲兩部分:括號裏面爲函數的參數,參數的內容分三部分"I","Ljava/lang/String;","[I",之間沒有空格;括號外邊是函數的返回類型簽名。須要注意的是若是函數返回類型爲void則其中返回類型簽名爲V;

 

5、動態註冊函數

前面二和三都是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_SpringUtils

jstring 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 方法;

---------------------------------------下面是動態註冊的另外一種簡潔明瞭的寫法,但其實都是一個道理。---------------------------------------

jint JNI_OnLoad(JavaVM* pVm, void* reserved) {
    JNIEnv* env;
    if ((*pVm)->GetEnv(pVm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    JNINativeMethod nm[3];
    nm[0].name = "method0"; // int method0(String str); 
    nm[0].signature = "(Ljava/lang/String;)I";
    nm[0].fnPtr = (void*) method0;

    nm[1].name = "method1"; // void method1(SurfaceView sv); 
    nm[1].signature = "(Landroid/view/Surface;)V";
    nm[1].fnPtr = (void*) method1;

    nm[2].name = "method2"; // int[] method2();
    nm[2].signature = "()[I";
    nm[2].fnPtr = (void*) method2;

    jclass cls = (*env)->FindClass(env, "com/jni/DynamicRegisterMethod"); // 指定java類全類名 //Register methods with env->RegisterNatives.
    (*env)->RegisterNatives(env, cls, nm, 3);
    return JNI_VERSION_1_6;
}

其實就是申明一個JNINativeMethod數組,而後爲每個元素指定java方法的name、java方法對應到jni中的signature、native方法的函數指針fnPtr;

 

6、加入連接庫

在程序開發過程當中,會頻繁的用到調試,方式有不少種,下面要講的這一種是經過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);

 

2016-4-26 更新

http://www.open-open.com/lib/view/open1451917048573.html

http://www.jianshu.com/p/d8cde65cb4f7

例子拖管地址:

https://github.com/a284628487/JniSample

相關文章
相關標籤/搜索