您的位置:首页 > 其它

静态注册JNI和动态注册JNI

2015-05-14 14:18 316 查看
Android JNI静态注册实例

andriod的SDK 中没有包括 JNI 的支持,而且对如何支持 JNI 也没有任何文档说明。不过既然整个android平台 是开源的,我们可以通过 Google 发布的源代码来找到一些线索(比如 frameworks/base/media/jni/ 目录),依葫芦画瓢的实现上层 JAVA 程序通过 JNI 来调用 Native C 程序中的函数。

依照下面的步骤可以实现一个非常简单的 JNI 的实例程序:

1. 首先编写 C 模块,实现动态库。(关于如何在 Android 中编译 C 模块的更多细节,请参考《Android编译Native C 模块 》。)
在 development 目录下添加新目录 hellolib ,并添加 hellolib.c 和 Android.mk 文件。 hellolib.c 的内容如下:

[cpp] view plaincopy

#include <jni.h>

#define LOG_TAG "TestLib"
#undef LOG
#include <utils/Log.h>

JNIEXPORT void JNICALL Java_com_test_TestHelloLib_printHello(JNIEnv * env, jobject jobj)
{
LOGD("Hello LIB!/n");
}

注意这里的函数名需要按照 JNI 的规范(因此也可以用 javah -jni 工具来生成头文件,来保证函数名的正确性).
Java_com_test_TestHelloLib_printHello 的命名对应后面在 java 代码中, package 名字是 com.test ,类名是TestHelloLib , native 函数名是 printHello 。
另外, LOGD 及 #define LOG_TAG "TestLib" 等打印 log 的方式是采用了 Android 所提供的 LOG 机制,这样才能通过 Android 的 logcat 工具看到 log 。
用于编译 C 模块的 Android.mk 文件内容如下:

[cpp] view plaincopy

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_SRC_FILES:= /
hellolib.c

LOCAL_C_INCLUDES := /
$(JNI_H_INCLUDE)

LOCAL_SHARED_LIBRARIES := /
libutils

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE := libhello

include $(BUILD_SHARED_LIBRARY)

该文件中的一些变量分别对应的含义如下:
LOCAL_SRC_FILES - 编译的源文件
LOCAL_C_INCLUDES - 需要包含的头文件目录
LOCAL_SHARED_LIBRARIES - 链接时需要的外部库
LOCAL_PRELINK_MODULE - 是否需要 prelink 处理(参考 prelink 的详细介绍:《 动态库优化 ——Prelink(预连接)技术 》, Android 的 Toolchain, prelink 工具:《 Android
Toolchain与 Bionic Libc 》 )
LOCAL_MODULE - 编译的目标对象
BUILD_SHARED_LIBRARY - 指明要编译成动态库。

接下来回到 Android 顶层目录,并执行 make libhello 来编译:

[cpp] view plaincopy

# cd $(YOUR_ANDROID) && make libhello
target thumb C: libhello <= development/hellolib/hellolib.c
target SharedLib: libhello (out/target/product/generic/obj/SHARED_LIBRARIES/libhello_intermediates/LINKED/libhello.so)
target Non-prelinked: libhello (out/target/product/generic/symbols/system/lib/libhello.so)
target Strip: libhello (out/target/product/generic/obj/lib/libhello.so)
Install: out/target/product/generic/system/lib/libhello.so

编译结果可得到位于 out/target/product/generic/system/lib/ 目录的动态共享库 libhello.so

2 .编写 Java 模块,来通过 JNI 方式调用 C 接口。具体 Eclipse 环境的搭建请参考 Android SDK 文档中的详细说明,及 Hello Android 程序的创建过程,这里仅给出我们需要修改的 TestHelloLib.java 文件:

[c-sharp] view plaincopy

package com.test;

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

public class TestHelloLib extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
printHello();
}

static {
System.loadLibrary("hello");
}

private native void printHello();
}

注意上面代码部分: private native void printHello() 用来声明一个 native 接口;
static {
System.loadLibrary("hello");
}
用来加载上面步骤中生成 libhello.so (注意 loadLibrary 方法的参数不是 ”libhello.so” ,而是去掉前缀和后缀之后的”hello” ), onCreate() 方法中则调用了 printHello() 接口。
通过这一步骤可生成 Android 开发者所熟悉的 apk 文件: TestHelloLib.apk 。

3 .集成测试 TestHelloLib.apk 和 libhello.so 。先运行 emulator 并将 TestHelloLib.apk 和 libhello.so 上传至 emulator中。注意要将 libhello.so 上传到 emulator 的 /system/lib 目录,由于该目录是只读的,上传之前先要执行 adb
remount:

[cpp] view plaincopy

# adb remount
# adb push out/target/product/generic/system/lib/libhello.so /system/lib
# adb install TestHelloLib.apk

接下来在模拟器菜单中可以看到已经安装的 TestHelloLib 程序,运行即可。
由于 JNI 接口 printHello() 并没有作界面上的改动,要验证其效果需要用 Android 的 logcat 工具来查看。运行 ”adb
logcat” 可以找到下面的 log 片断:

[cpp] view plaincopy

I/ActivityManager( 48): Starting activity: Intent { action=android.intent.action.MAIN categories={android.intent.category.LAUNCHER} flags=0x10200000 comp={com.test/com.test.TestHelloLib} }
I/ActivityManager( 48): Start proc com.test for activity com.test/.TestHelloLib: pid=174 uid=10024 gids={}
D/dalvikvm( 174): Trying to load lib /system/lib/libhello.so 0x43481c58
D/dalvikvm( 174): Added shared lib /system/lib/libhello.so 0x43481c58
D/dalvikvm( 174): No JNI_OnLoad found in /system/lib/libhello.so 0x43481c58
D/dalvikvm( 174): +++ not scanning '/system/lib/libwebcore.so' for 'printHello' (wrong CL)
D/dalvikvm( 174): +++ not scanning '/system/lib/libmedia_jni.so' for 'printHello' (wrong CL)
D/TestLib ( 174): Hello LIB!
I/ActivityManager( 48): Displayed activity com.test/.TestHelloLib: 806 ms

这里包含了调用 printHello() 接口的 log 信息,其中 ” D/TestLib ( 174): Hello LIB!” 就是 printHello() 所打印的信息。至此成功完成 Android JNI 的实例验证。

动态注册实例

在纯java中使用JNI文章中可以看到,java的native方法与C/C++代码函数是通过Java_<包名>_<类名>_<方法名>这种方式对应的,即它是静态注册的。当需要使用现有的C/C++代码函数时,需要以这种形式定义包装函数,在包装函数中调用现有C/C++代码函数;而且这样的函数名也非常长,不适合管理。使用动态注册,可以不受上述命名的限制。

运行下面示例需要安装NDK及搭建环境,请看另一篇文章使用NDK与环境搭建

下面我将Android NDK中的samples\hello-jni示例,由原来的静态注册改为动态注册,只需要改JNI部分。

samples\hello-jni\jni\hello-jni.c的原代码如下

[cpp] view
plaincopy

#include <string.h>

#include <jni.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

*/

jstring

Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,

jobject thiz )

{

return (*env)->NewStringUTF(env, "Hello from JNI !");

}

将其改为

[cpp] view
plaincopy

#include <stdlib.h>

#include <string.h>

#include <stdio.h>

#include <jni.h>

#include <assert.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

*/

jstring native_hello(JNIEnv* env, jobject thiz)

{

return (*env)->NewStringUTF(env, "动态注册JNI");

}

/**

* 方法对应表

*/

static JNINativeMethod gMethods[] = {

{"stringFromJNI", "()Ljava/lang/String;", (void*)native_hello},//绑定

};

/*

* 为某一个类注册本地方法

*/

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) {

const char* kClassName = "com/example/hellojni/HelloJni";//指定要注册的类

return registerNativeMethods(env, kClassName, gMethods,

sizeof(gMethods) / sizeof(gMethods[0]));

}

/*

* System.loadLibrary("lib")时调用

* 如果成功返回JNI版本, 失败返回-1

*/

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {

JNIEnv* env = NULL;

jint result = -1;

if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {

return -1;

}

assert(env != NULL);

if (!registerNatives(env)) {//注册

return -1;

}

//成功

result = JNI_VERSION_1_4;

return result;

}

改成动态注册后,当调用HelloJni类的public native String stringFromJNI()方法时,会找到动态注册的native_hello函数。



上面的代码没什么难点,唯一看不明白的可能是方法对应表,下面来讲讲

JNINativeMethod是JNI机制定义的一个结构体

[cpp] view
plaincopy

typedef struct {

const char* name; //Java中函数的名字

const char* signature; //用字符串描述的函数的参数和返回值

void* fnPtr; //指向C函数的函数指针

} JNINativeMethod;

比较难以理解的是第二个参数,例如

"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

实际上这些字符是与函数的参数类型一一对应的。

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具体的每一个字符的对应关系如下

字符 Java类型 C类型

V void void

Z jboolean boolean

I jint int

J jlong long

D jdouble double

F jfloat float

B jbyte byte

C jchar char

S jshort short

数组则以"["开始,用两个字符表示

[I jintArray int[]

[F jfloatArray float[]

[B jbyteArray byte[]

[C jcharArray char[]

[S jshortArray short[]

[D jdoubleArray double[]

[J jlongArray long[]

[Z jbooleanArray boolean[]

上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring

Ljava/lang/String; String jstring

Ljava/net/Socket; Socket jobject

如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。

例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"

转自:http://blog.csdn.net/chenfeng0104/article/details/7088600
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: