当前位置:文档之家› 利用JNI来实现android与SO文件的交互

利用JNI来实现android与SO文件的交互

利用JNI来实现android与SO文件的交互
利用JNI来实现android与SO文件的交互

利用JNI来实现android与SO文件的交互。

该文档主要演示了android中如何与so文件进行交互。即现在的问题需要硬件厂商重新开发动态库,并按照全网物联生成的.h文件进行开发动态库。

1、总体步骤为

A、写好native 本地java类

B、生成.h文件。

C、硬件厂商按.h文件开发动态库so

D、Android中采用system.loadLib(‘*.so’)。

E、调用本地native方法。全网物联研发人员需要用Handler独立进程去调用,不然会

有问题。

F、驱动硬件。

2、教程:

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;

}

为什么要定义Handler呢?

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

public JNIExampleInterface(Handler h) {

this.h = h;

}

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

callVoid(): 没有参数并且没有返回值;

getNewData(): 有两个参数,用来构造一个新的类的实例;

getDataString(): 用对象作为参数,从对象中抽取值。

public native void callVoid();

public native Data getNewData(int i, String s);

public native String getDataString(Data d);

callback接收一个string参数,并将其封装成Bundle后分发给Handler:

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

本地库的实现,这个地方就需要全网物联把这个.H文件给硬件厂商了,硬件厂商需要按照这个文件开发动态库。函数名必须一致。

#ifdef __cplusplus

extern "C" {

#endif

#ifdef __cplusplus

}

#endif

C中的头文件和全局变量

下面的include包含了Android的JNI定义的函数:

#include

#include

#include

一些其他要用到的头文件:

#include

#include

#include

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),来正确处理这两种情况。我们还需要一个建立本地线程的函数,因此实现如下:

=

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

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签名式。这里的签名式指出该函数需要一个https://www.doczj.com/doc/3915217126.html,ng.String对象作为参数,返回值为空。关于函数签名式的更多信息请参阅JNI文档,你可以使用javap工具去查询非本地函数的签名式(本地函数的签名式信息已经包含在javah产生的头文件中了)。

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

任务就是调用callback handler:

=

void *native_thread_start(void *arg) {

sleep(1);

callback_handler((char *) "Called from native thread");

}

现在我们已经有了实现callVoid()函数的所有本地部分的代码:

/*

* 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对象。

=

/*

* 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, "", "(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()函数描述了如何在本地函数中获取对象属性值。

=

/*

* 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将无法解析它们。具体函数实现如下:

=

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;

}

return JNI_VERSION_1_4;

}

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

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

=

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, "", "()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);

}

定义了这个函数,缓存类实例就是小菜一碟了

initClassHelper(env, kInterfacePath, &gInterfaceObject);

initClassHelper(env, kDataPath, &gDataObject);

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

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中可用。整体结构如下:

=

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(https://www.doczj.com/doc/3915217126.html,yout.main);

}

}

首先使用System.loadLibrar安装库。

try {

System.loadLibrary("libjniexample.so";

} catch (Exception ex) {

Log.e("JNIExample", "failed to install native library: " + ex);

}

接下来就是调用本地库函数并显示结果。为了演示callVoid(),我们必须先初始化一个handler,

并将其传给JNI接口类,以使得我们能接受到callback消息:

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:

callVoidButton = (Button) findViewById(R.id.callVoid_button);

callVoidButton.setOnClickListener(new Button.OnClickListener() {

public void onClick(View v) {

jniInterface.callVoid();

}

});

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 + "\")");

}

});

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

相关主题
文本预览
相关文档 最新文档