«

静态注册JNI和动态注册JNI

时间:2024-3-2 17:22     作者:韩俊     分类: Html+Css


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中的sampleshello-jni示例,由原来的静态注册改为动态注册,只需要改JNI部分。

sampleshello-jnijnihello-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

标签: javascript html css

热门推荐