android app中使用JNI

JNI提供了一種機制,使得在Java 代碼中可使用 C/C++ 的本地層代碼,這種使用主要是指在 Java 代碼中調用 C/C++ 代碼。這種機制爲咱們開啓了一扇門,一扇將Java 代碼與廣闊的 C/C++ 本地層鏈接起來的門。java

基於 android-ndk-r8d 提供的sample 程序——android-ndk-r8d/samples/hello-jni 的代碼,咱們來看一下,如何在 android 應用開發中使用 JNI。android

首先來看在 Java 代碼中須要作些什麼事情:數組

package com.example.hellojni;

import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;

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

        /* Create a TextView and set its content.
         * the text is retrieved by calling a native
         * function.
         */
        TextView  tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);
    }

    /* A native method that is implemented by the
     * 'hello-jni' native library, which is packaged
     * with this application.
     */
    public native String  stringFromJNI();

    /* This is another native method declaration that is *not*
     * implemented by 'hello-jni'. This is simply to show that
     * you can declare as many native methods in your Java code
     * as you want, their implementation is searched in the
     * currently loaded native libraries only the first time
     * you call them.
     *
     * Trying to call this function will result in a
     * java.lang.UnsatisfiedLinkError exception !
     */
    public native String  unimplementedStringFromJNI();

    /* this is used to load the 'hello-jni' library on application
     * startup. The library has already been unpacked into
     * /data/data/com.example.hellojni/lib/libhello-jni.so at
     * installation time by the package manager.
     */
    static {
        System.loadLibrary("hello-jni");
    }
}

上面那些帶 native 修飾符的方法,就是在 Java 代碼中調用,實現卻在本地層的方法。native 修飾符告訴Java 編譯器,暫時不須要去連接這些方法。app

那Java的機制到底要如何去調用到那個在本地層實現的Java method的呢?能夠看到最後的那個static的block,裏面有調用到System.loadLibrary(),這個動做完成的事情,其實就是把實現了java方法的shared library加載進來。java的機制會在這個shared library中找到那個java方法的實現。ide

但是,JVM是怎麼樣知道java方法和本底層函數的對應關係的呢?或者說,在Java Code中,調用了那個native修飾的傢伙,要如何去找到它的實現呢?C/C++中可沒有java的string那種東西。創建Java方法到native方法的映射,也就是告訴Java層,它所調用的那些native方法在本底層對應於哪一個實際的C/C++方法,有兩種方法。一種是經過給C/C++的函數按照某個特定的規則來命名實現的。好比前面的那個public native String  stringFromJNI()方法,Java的機制是會知道它的實現應該是在C/C++中的Java_com_example_hellojni_HelloJni_stringFromJNI() 這個函數的。看看這個函數名,是有多麼的冗長啊,這個函數名中要包含對應的Java 方法的package name、class name及method name的信息嘛。能夠看到這個方法名的構成,Java開頭,而後是下劃線,緊接着是下劃線分割的package name,而後跟着的是下劃線和class name,最後是下劃線和方法名。函數

創建Java方法到native方法的映射還有另外的一種讓人感受更爲舒服的方法,那就是JNI_OnLoad機制。一個shared library,若是他裏面有一個JNI_OnLoad(JavaVM* vm, void*)函數,那麼當這個shared library被load起來的時候,它首先就會被調用一次。而後在這個JNI_OnLoad()函數中,就能夠經過JNI提供的一些函數,來顯式地註冊Java 方法到native方法的映射關係了。咱們修改ndk的sample code,來使用JNI_OnLoad這種機制(同時咱們想要加一個限制,那就是隻能基於"jni.h"提供的方法來實現,儘管在libandroid_android.so中已經存在一些很是好用的設施了,以使得咱們的JNI code能夠用ndk提供的設施輕鬆的build過)。以下是前面那個 stringFromJNI()的實現代碼:ui

 

#include <string.h>
#include <jni.h>
#include <stdlib.h>

#include <android/log.h>

#include "JniDebug.h"
#include "JniHelper.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:
 *
 *   apps/samples/hello-jni/project/src/com/example/hellojni/HelloJni.java
 */

//extern "C" {
//    JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv * env, jobject thiz );
//};
//
//JNIEXPORT jstring
//JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
//                                                  jobject thiz )
//{
//    const char *str = "Hello from JNI !";
//    JniDebug(str);
//    return env->NewStringUTF(str);
//}

static jstring HelloJni_stringFromJNI( JNIEnv* env, jobject thiz )
{
    JniDebug("from HelloJni_stringFromJNI");
    const char *str = "Hello from JNI !";
    JniDebug(str);
    return env->NewStringUTF(str);
}

static JNINativeMethod gMethods[] = {
        NATIVE_METHOD(HelloJni, stringFromJNI, "()Ljava/lang/String;"),
};

void register_com_example_hellojni(JNIEnv* env) {
    jniRegisterNativeMethods(env, "com/example/hellojni/HelloJni", gMethods, NELEM(gMethods));
}

int JNI_OnLoad(JavaVM* vm, void*) {
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        abort();
    }
    register_com_example_hellojni(env);
    JniDebug("JNI_OnLoad in hello-jni");
    return JNI_VERSION_1_6;
}

來看這段code,就創建Java方法到native方法的映射部分而言,主要作了兩件事情:this

  1. 創建了一個表,這個表描述了Java方法和native方法的映射關係。這個表中會包含Java層中這個方法的名稱,native層中相同方法的名稱,參數的類型及返回值的類型等信息。對應於上面那段code中定義的gMethods這個數組。怎麼沒有包含這個方法所在的java class的名稱信息呢?Java class name的信息固然是不可或缺的。且看這個部分完成第二件事情。
  2. 調用JNI的方法把第一步中創建的那個表註冊進系統,對應於上面那個對於jniRegisterNativeMethods()函數的調用。這個地方其實是須要提供Java class name信息的啦。

實際上在此處,無論是上面的第一步,仍是第二步,咱們都沒有直接使用"jni.h"中提供的那些函數或結構。而是參照android系統中libcore/luni/src/main/native/下面的那些實現,定義了一個宏來更加方便的創建Java 方法到native方法的映射表。同時參照android系統中libnativehelper的實現,對"jni.h"中直接提供的那些方法進行了一點點的加強。接下來來看這個部分的Code。首先是頭文件JniHelper.h:code

 

#include "jni.h"

#ifdef __cplusplus
extern "C" {
#endif


/*
 * Register one or more native methods with a particular class.
 * "className" looks like "java/lang/String". Aborts on failure.
 * TODO: fix all callers and change the return type to void.
 */
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);

#ifdef __cplusplus
}
#endif

/*
 * For C++ code, we provide inlines that map to the C functions.  g++ always
 * inlines these, even on non-optimized builds.
 */
#if defined(__cplusplus)
inline int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods) {
    return jniRegisterNativeMethods(&env->functions, className, gMethods, numMethods);
}
#endif

#define NATIVE_METHOD(className, functionName, signature) \
    { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }


# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

接下來是JniHelper.cpp 文件:orm

 

#include "JniDebug.h"
#include "JniHelper.h"

#include <stdlib.h>

/**
 * Equivalent to ScopedLocalRef, but for C_JNIEnv instead. (And slightly more powerful.)
 */
template<typename T>
class scoped_local_ref {
public:
    scoped_local_ref(C_JNIEnv* env, T localRef = NULL)
    : mEnv(env), mLocalRef(localRef)
    {
    }

    ~scoped_local_ref() {
        reset();
    }

    void reset(T localRef = NULL) {
        if (mLocalRef != NULL) {
            (*mEnv)->DeleteLocalRef(reinterpret_cast<JNIEnv*>(mEnv), mLocalRef);
            mLocalRef = localRef;
        }
    }

    T get() const {
        return mLocalRef;
    }

private:
    C_JNIEnv* mEnv;
    T mLocalRef;

    // Disallow copy and assignment.
    scoped_local_ref(const scoped_local_ref&);
    void operator=(const scoped_local_ref&);
};

static jclass findClass(C_JNIEnv* env, const char* className) {
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    return (*env)->FindClass(e, className);
}

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);

    JniDebug("Registering %s natives", className);

    scoped_local_ref<jclass> c(env, findClass(env, className));
    if (c.get() == NULL) {
        JniDebug("Native registration unable to find class '%s', aborting", className);
        abort();
    }

    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
        JniDebug("RegisterNatives failed for '%s', aborting", className);
        abort();
    }

    return 0;
}

在Java 方法的native實現的那個文件中,咱們看到有一些打log的函數。這個地方也順便看一下在作NDK開發時打log的方法,首先是頭文件:

 

void JniDebug(const char format[], ...);

這個頭文件,沒什麼可說的。接着來看這個打log的函數的實現:

 

#include "stdio.h"

static const size_t kBufferSize = 256;

#define LOG_TAG "hello-jni"
#include <android/log.h>

#include "JniDebug.h"

void JniDebug(const char format[], ...) {
    va_list args;
    va_start(args, format);
    __android_log_vprint(ANDROID_LOG_DEBUG, LOG_TAG, format, args);
    va_end(args);
}

看上去與咱們日常用的那些printf函數也真心沒有太大的區別。要打log,天然不能忘了要在jni的Android.mk文件中,創建對於liblog的依賴。像下面這樣:

 

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

ANDROID_SOURCE_TOP=$(HOME)/android/Data/android_src/JellyBean/

LOCAL_MODULE    := hello-jni
LOCAL_SRC_FILES := hello-jni.cpp \
                   JniHelper.cpp\
                   JniDebug.cpp

LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)

接下來再來看一看咱們上面的那個hello-jni程序運行的結果。它打出來的log:

 

03-02 08:44:23.362: D/hello-jni(922): Registering com/example/hellojni/HelloJni natives
03-02 08:44:23.362: D/hello-jni(922): JNI_OnLoad in hello-jni
03-02 08:44:23.512: D/hello-jni(922): from HelloJni_stringFromJNI
03-02 08:44:23.512: D/hello-jni(922): Hello from JNI !

結束。

相關文章
相關標籤/搜索