JNI 使用

JNI使用示例
2010-03-15 11:18
       JNI提供了一种扩展Android功能和移植已有软件的方式。本文将通过一个实例来讲述如何建立JNI库以及JNI库如何与android的JVM交互。


Java接口


       定义java类JNIExampleInterface, 该类提供了调用Native库中本地函数的接口。本地函数和对应的Java函数具有相互匹配的签名式(即,参数的类型和个数,以及返回值的类型)。获取本地库中对应的函数签名式的最简单的方法就是,首先写出对应的Java原型,然后使用javah工具生成对应的本地JNI头文件。可以copy/paste到C++文件中来实现对应的函数。


     本地函数支撑的对应的Java函数按照正常方式去声明,但需要加上native。我们还想演示如何在native代码中调用Java代码,因此我们的接口类定义如下:


package org.wooyd.android.JNIExample;


import android.os.Handler;
import android.os.Bundle;
import android.os.Message;
import org.wooyd.android.JNIExample.Data;


public class JNIExampleInterface {
    private Handler h;
    <Example constructors>
    <Example native functions>
    <Example callback>
}


为什么要定义Handler呢?


当本地库需要通过callback传递信息给Java进程,如果这个callback是由本地线程调用的,并且想修改应用的用户界面,就会产生exception。这是因为Android仅仅允许主线程更改用户界面。为了避免这个问题,我们使用Handler提供的消息传递接口将callback接收到的数据传递给主线程,让主线程去更改界面。


<Example constructors>
    public JNIExampleInterface(Handler h) {
        this.h = h;
    }


为了阐述不同的参数传递技术,我们定义了三个native函数:


callVoid(): 没有参数并且没有返回值 ;
getNewData(): 有两个参数,用来构造一个新的类的实例;
getDataString(): 用对象作为参数,从对象中抽取值。
<Example native functions>
public native void callVoid();
    public native Data getNewData(int i, String s);
    public native String getDataString(Data d);
callback接收一个string参数,并将其封装成Bundle后分发给Handler:
<Example callback>
    public static void callBack(String s) {
        Bundle b = new Bundle();
        b.putString("callback_string", s);
        Message m = Message.obtain();
        m.setData(b);
        m.setTarget(h);
        m.sendToTarget();
    }
另外我们定义一个Data dummy类
Data.java
package org.wooyd.android.JNIExample;


public class Data {
    public int i;
    public String s;
    public Data() {}
    public Data(int i, String s) {
        this.i = i;
        this.s = s;
    }
}


编译Data.java和JNIExampleInterface.java


$ javac org/wooyd/android/JNIExample/*.java


生成JNI头文件,包含与Java对应的本地函数的原型


$ javah -classpath . org.wooyd.android.JNIExample.JNIExampleInterface


本地库的实现
<JNIExample.cpp>
<JNI includes>
<Miscellaneous includes>
<Global variables>
#ifdef __cplusplus
extern "C" {
#endif
<callVoid implementation>
<getNewData implementation>
<getDataString implementation>
<initClassHelper implementation>
<JNIOnLoad implementation>
#ifdef __cplusplus
}
#endif
头文件和全局变量
下面的include包含了Android的JNI定义的函数:
 


<JNI includes>
#include <jni.h>
#include <JNIHelp.h>
#include <android_runtime/AndroidRuntime.h>
一些其他要用到的头文件:


 




<Miscellaneous includes>
#include <string.h>
#include <unistd.h>
#include <pthread.h>




<Global variables>
static JavaVM *gJavaVM;
static jobject gInterfaceObject, gDataObject;
const char *kInterfacePath = "org/wooyd/android/JNIExample/JNIExampleInterface";
const char *kDataPath = "org/wooyd/android/JNIExample/Data";


 


从本地线程和java线程调用java函数


callVoid函数是最简单的一个,因为他没有任何参数并且没有返回值。我们用它来说明通过调用Java的callBack函数,如何将数据传回给Java。


至此,我们有必要区分下面两种情况:


Java函数可能在Java线程或本地线程中被调用,JVM是无法得知的。对于前者,调用可以直接执行,而对于后者,我们必须首先将本地线程关联到JVM中。因此需要一个附加层,本地回调句柄(Native CallBack Handler),来正确处理这两种情况。我们还需要一个建立本地线程的函数,因此实现如下:


<callVoid implementation>=
<Callback handler>
<Thread start function>
<callVoid function>
Native callback handler获取JNI环境(如果需要,则关联本地线程),使用缓存的全局对象gInterfaceObject获取JNIExampleInterface类,获取callBack()函数的引用,并调用:


<Callback handler>
static void callback_handler(char *s) {
    int status;
    JNIEnv *env;
    bool isAttached = false;
   
    status = gJavaVM->GetEnv((void **) &env, JNI_VERSION_1_4);
    if(status < 0) {
        LOGE("callback_handler: failed to get JNI environment, "
             "assuming native thread");
        status = gJavaVM->AttachCurrentThread(&env, NULL);
        if(status < 0) {
            LOGE("callback_handler: failed to attach "
                 "current thread");
            return;
        }
        isAttached = true;
    }
    /* Construct a Java string */
    jstring js = env->NewStringUTF(s);
    jclass interfaceClass = env->GetObjectClass(gInterfaceObject);
    if(!interfaceClass) {
        LOGE("callback_handler: failed to get class reference");
        if(isAttached) gJavaVM->DetachCurrentThread();
        return;
    }
    /* Find the callBack method ID */
    jmethodID method = env->GetStaticMethodID(
        interfaceClass, "callBack", "(Ljava/lang/String;)V");
    if(!method) {
        LOGE("callback_handler: failed to get method ID");
        if(isAttached) gJavaVM->DetachCurrentThread();
        return;
    }
    env->CallStaticVoidMethod(interfaceClass, method, js);
    if(isAttached) gJavaVM->DetachCurrentThread();
}
说明:


1、JNI GetEnv()函数返回的JNI环境对每个线程来说是独特的,因此我们必须在每次进入函数时都要重新获取。然而JavaVM指针是属于每个进程的,因此我们可以将其缓存起来(在JNI_OnLoad()函数中),在多个线程间使用。




2、当我们关联本地线程时,其相关的Java环境是与类引导程序一起的,这就意味着即使我们要在函数中获取一个类的引用(通常使用JNI函数FindClass()),都将会引发一个exception。因此我们使用缓存的JNIExampleInterface对象去获取类的引用(有趣的是,我们不能缓存类引用本身,JVM认为这种引用在本地代码中是不可见的,因此任何试图使用它都会触发JVM产生exception)。




3、为了获取callBack()的函数ID,我们需要指定其名称和JNI签名式。这里的签名式指出该函数需要一个java.lang.String对象作为参数,返回值为空。关于函数签名式的更多信息请参阅JNI文档,你可以使用javap工具去查询非本地函数的签名式(本地函数的签名式信息已经包含在javah产生的头文件中了)。




 


为了测试从本地线程中调用函数,我们需要一个在另一个独立线程中运行的函数。它唯一的任务就是调用callback handler:


 


<Thread start function>=
void *native_thread_start(void *arg) {
    sleep(1);
    callback_handler((char *) "Called from native thread");
}
现在我们已经有了实现callVoid()函数的所有本地部分的代码:


 


<callVoid function>
/*
 * Class:     org_wooyd_android_JNIExample_JNIExampleInterface
 * Method:    callVoid
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_org_wooyd_android_JNIExample_JNIExampleInterface callVoid
  (JNIEnv *env, jclass cls) {
    pthread_t native_thread;


    callback_handler((char *) "Called from Java thread");
    if(pthread_create(&native_thread, NULL, native_thread_start, NULL)) {
        LOGE("callVoid: failed to create a native thread");
    }
}
 


 


 


实现其他本地函数


getNewData()函数描述了如何在本地库中建立一个新的Java对象,并返回给调用者。为了获取类并创建其实例,我们再次使用缓存的Data对象。


<getNewData implementation>=
/*
 * Class:     org_wooyd_android_JNIExample_JNIExampleInterface
 * Method:    getNewData
 * Signature: (ILjava/lang/String;)Lorg/wooyd/android/JNIExample/Data;
 */
JNIEXPORT jobject JNICALL Java_org_wooyd_android_JNIExample_JNIExampleInterface_getNewData
  (JNIEnv *env, jclass cls, jint i, jstring s) {
    jclass dataClass = env->GetObjectClass(gDataObject);
    if(!dataClass) {
        LOGE("getNewData: failed to get class reference");
        return NULL;
    }
    jmethodID dataConstructor = env->GetMethodID(
        dataClass, "<init>", "(ILjava/lang/String;)V");
    if(!dataConstructor) {
        LOGE("getNewData: failed to get method ID");
        return NULL;
    }
    jobject dataObject = env->NewObject(dataClass, dataConstructor, i, s);
    if(!dataObject) {
        LOGE("getNewData: failed to create an object");
        return NULL;
    }
    return dataObject;
}


getDataString()函数描述了如何在本地函数中获取对象属性值。


<getDataString implementation>=
/*
 * Class:     org_wooyd_android_JNIExample_JNIExampleInterface
 * Method:    getDataString
 * Signature: (Lorg/wooyd/android/JNIExample/Data;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_org_wooyd_android_JNIExample_JNIExampleInterface_getDataString
  (JNIEnv *env, jclass cls, jobject dataObject) {
    jclass dataClass = env->GetObjectClass(gDataObject);
    if(!dataClass) {
        LOGE("getDataString: failed to get class reference");
        return NULL;
    }
    jfieldID dataStringField = env->GetFieldID(
        dataClass, "s", "Ljava/lang/String;");
    if(!dataStringField) {
        LOGE("getDataString: failed to get field ID");
        return NULL;
    }
    jstring dataStringValue = (jstring) env->GetObjectField(
        dataObject, dataStringField);
    return dataStringValue;
}


 




JNI_OnLoad()函数的实现


为了让JNI可以和Android JVM一起工作,必须提供JNI_OnLoad()函数。在本地库被装入到JVM时会调用该函数。之前我们已经提及许多任务要在该函数中完成,如:缓存全局JavaVM指针和对象实例。另外,我们需要在Java中调用的任何本地函数都必须注册,否则Android JVM将无法解析它们。具体函数实现如下:


<JNIOnLoad implementation>=
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv *env;
gJavaVM = vm;
LOGI("JNI_OnLoad called");
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE("Failed to get the environment using GetEnv()");
return -1;
}
<Class instance caching>
<Native function registration>
return JNI_VERSION_1_4;
}


由于本地线程无法存取functional classloader,所以我们需要将类引用缓存起来。正如之前所述,我们不能缓存类引用本身,我们缓存这些类的实例,之后我们可以通过GetObjectClass()JNI函数来获取类引用。


我们要记住的一点就是我们必须使用NewGlobalRef()函数将这些对象保护起来,以免被GC回收,这样就可以在JVM的整个生命周期内的任何线程中使用。建立实例并将其保存到全局变量是函数initClassHelper()的工作:


<initClassHelper implementation>=
void initClassHelper(JNIEnv *env, const char *path, jobject *objptr) {
    jclass cls = env->FindClass(path);
    if(!cls) {
        LOGE("initClassHelper: failed to get %s class reference", path);
        return;
    }
    jmethodID constr = env->GetMethodID(cls, "<init>", "()V");
    if(!constr) {
        LOGE("initClassHelper: failed to get %s constructor", path);
        return;
    }
    jobject obj = env->NewObject(cls, constr);
    if(!obj) {
        LOGE("initClassHelper: failed to create a %s object", path);
        return;
    }
    (*objptr) = env->NewGlobalRef(obj);
}
定义了这个函数,缓存类实例就是小菜一碟了


<Class instance caching>
    initClassHelper(env, kInterfacePath, &gInterfaceObject);
    initClassHelper(env, kDataPath, &gDataObject);


为了注册本地函数,我们建立一个JNINativeMethod结构的数组,该结构包含函数名称、签名式(可以从javah产生的注释中拷贝)、实现函数的指针。然后将这个数组传递给Android的registerNativeMethods()函数:


 


<Native function registration>
    JNINativeMethod methods[] = {
        {
            "callVoid",
            "()V",
            (void *) Java_org_wooyd_android_JNIExample_JNIExampleInterface_callVoid
        },
        {
            "getNewData",
            "(ILjava/lang/String;)Lorg/wooyd/android/JNIExample/Data;",
            (void *) Java_org_wooyd_android_JNIExample_JNIExampleInterface_getNewData
        },
        {
            "getDataString",
            "(Lorg/wooyd/android/JNIExample/Data;)Ljava/lang/String;",
            (void *) Java_org_wooyd_android_JNIExample_JNIExampleInterface_getDataString
        }
    };
    if(android::AndroidRuntime::registerNativeMethods(
        env, kInterfacePath, methods, NELEM(methods)) != JNI_OK) {
        LOGE("Failed to register native methods");
        return -1;
    }




编译本地库
这里仅介绍使用Android.mk编译本地库的方法,你必须先现在Android的整个源码。
为你的本地库建立一个目录,如:/path/to/android/source/code/vendor/your/sample。将Native的源码文件放到该目录下,并建立Android.mk文件,内容如下:
# This makefile supplies the rules for building a library of JNI code for
# use by our example platform shared library.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
# This is the target being built.
LOCAL_MODULE:= libjniexample
# All of the source files that we will compile.
LOCAL_SRC_FILES:= \
             JNIExample.cpp
# All of the shared libraries we link against.
LOCAL_SHARED_LIBRARIES := \
             libandroid_runtime \
             libnativehelper \
             libcutils \
             libutils
# No static libraries.
LOCAL_STATIC_LIBRARIES :=
# Include C headers
#LOCAL_C_INCLUDES+= \
#            $(call include-path-for, dbus)
#LOCAL_C_INCLUDES +=\
#            external/freetype/include \
# Also need the JNI headers.
LOCAL_C_INCLUDES += \
             $(JNI_H_INCLUDE)
# No specia compiler flags.
LOCAL_CFLAGS +=
# Don't prelink this library.  For more efficient code, you may want
# to add this library to the prelink map and set this to true.
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
在Java代码中使用本地函数
我们将建立一个简单的activity,来使用JNI函数。我们唯一要做的就是在activity的onCreate()函数中load本地JNI库,使得其中定义的函数在Java中可用。整体结构如下:
 


<JNIExample.java>=
package org.wooyd.android.JNIExample;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import org.wooyd.android.JNIExample.JNIExampleInterface;
import org.wooyd.android.JNIExample.Data;
import android.util.Log;


public class JNIExample extends Activity
{
    TextView callVoidText, getNewDataText, getDataStringText;
    Button callVoidButton, getNewDataButton, getDataStringButton;
    Handler callbackHandler;
    JNIExampleInterface jniInterface;


    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
         <Load JNI library>
         <callVoid demo>
         <getNewData demo>
         <getDataString demo>
    }
}
首先使用System.loadLibrar安装库。
try {
         System.loadLibrary("libjniexample.so";
    } catch (Exception ex) {
         Log.e("JNIExample", "failed to install native library: " + ex);
    }


接下来就是调用本地库函数并显示结果。为了演示callVoid(),我们必须先初始化一个handler,并将其传给JNI接口类,以使得我们能接受到callback消息:


 


<callVoid demo>


callVoidText = (TextView) findViewById(R.id.callVoid_text);
    callbackHandler = new Handler() {
        public void handleMessage(Message msg) {
            Bundle b = msg.getData();
            callVoidText.setText(b.getString("callback_string"));
        }
    };
    jniInterface = new JNIExampleInterface(callbackHandler);


建立一个按钮,按下的时候调用callVoid:


 




<callVoid demo>
    callVoidButton = (Button) findViewById(R.id.callVoid_button);
    callVoidButton.setOnClickListener(new Button.OnClickListener() {
        public void onClick(View v) {
            jniInterface.callVoid();
            
        } 
    });


 




<getNewData demo>
    getNewDataText = (TextView) findViewById(R.id.getNewData_text);  
    getNewDataButton = (Button) findViewById(R.id.getNewData_button);
    getNewDataButton.setOnClickListener(new Button.OnClickListener() {
        public void onClick(View v) {
            Data d = jniInterface.getNewData(42, "foo");
            getNewDataText.setText(
                "getNewData(42, \"foo\") == Data(" + d.i + ", \"" + d.s + "\")");
        }
    });


<getDataString demo>




getDataStringText = (TextView) findViewById(R.id.getDataString_text);
getDataStringButton = (Button) findViewById(R.id.getDataString_button);
getDataStringButton.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
Data d = new Data(43, "bar");
String s = jniInterface.getDataString(d);
getDataStringText.setText(
"getDataString(Data(43, \"bar\")) == \"" + s + "\"");
}
});




references:
JNIExample
JNI references:
Sun's Java Native Interface guide
Java Native Interface: Programmer's Guide and Specification
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值