静态注册JNI和动态注册JNI

来源:互联网 发布:软件测试验收大纲 编辑:程序博客网 时间:2024/05/16 07:38

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
  1. #include <jni.h>  
  2.    
  3. #define LOG_TAG "TestLib"  
  4. #undef LOG  
  5. #include <utils/Log.h>  
  6.    
  7.    
  8. JNIEXPORT void JNICALL Java_com_test_TestHelloLib_printHello(JNIEnv * env, jobject jobj)  
  9. {  
  10.     LOGD("Hello LIB!/n");  
  11. }  

注意这里的函数名需要按照 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
  1. LOCAL_PATH:= $(call my-dir)  
  2. include $(CLEAR_VARS)  
  3.    
  4. LOCAL_SRC_FILES:= /  
  5.     hellolib.c  
  6.    
  7. LOCAL_C_INCLUDES := /  
  8.     $(JNI_H_INCLUDE)  
  9.    
  10. LOCAL_SHARED_LIBRARIES := /  
  11.     libutils  
  12.    
  13. LOCAL_PRELINK_MODULE := false  
  14.    
  15. LOCAL_MODULE := libhello  
  16.    
  17. 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
  1. # cd $(YOUR_ANDROID) && make libhello  
  2. target thumb C: libhello <= development/hellolib/hellolib.c  
  3. target SharedLib: libhello (out/target/product/generic/obj/SHARED_LIBRARIES/libhello_intermediates/LINKED/libhello.so)  
  4. target Non-prelinked: libhello (out/target/product/generic/symbols/system/lib/libhello.so)  
  5. target Strip: libhello (out/target/product/generic/obj/lib/libhello.so)  
  6. 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
  1. package com.test;  
  2.    
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5.    
  6. public class TestHelloLib extends Activity {  
  7.     /** Called when the activity is first created. */  
  8.     @Override  
  9.     public void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         setContentView(R.layout.main);  
  12.         printHello();  
  13.     }  
  14.      
  15.     static {  
  16.     System.loadLibrary("hello");  
  17.     }  
  18.      
  19.     private native void printHello();  
  20. }  

注意上面代码部分: 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
  1. # adb remount  
  2. # adb push out/target/product/generic/system/lib/libhello.so /system/lib  
  3. # adb install TestHelloLib.apk  

       接下来在模拟器菜单中可以看到已经安装的 TestHelloLib 程序,运行即可。

       由于 JNI 接口 printHello() 并没有作界面上的改动,要验证其效果需要用 Android 的 logcat 工具来查看。运行 ”adb logcat” 可以找到下面的 log 片断:

[cpp] view plaincopy
  1. I/ActivityManager(   48): Starting activity: Intent { action=android.intent.action.MAIN categories={android.intent.category.LAUNCHER} flags=0x10200000 comp={com.test/com.test.TestHelloLib} }  
  2. I/ActivityManager(   48): Start proc com.test for activity com.test/.TestHelloLib: pid=174 uid=10024 gids={}  
  3. D/dalvikvm(  174): Trying to load lib /system/lib/libhello.so 0x43481c58  
  4. D/dalvikvm(  174): Added shared lib /system/lib/libhello.so 0x43481c58  
  5. D/dalvikvm(  174): No JNI_OnLoad found in /system/lib/libhello.so 0x43481c58  
  6. D/dalvikvm(  174): +++ not scanning '/system/lib/libwebcore.so' for 'printHello' (wrong CL)  
  7. D/dalvikvm(  174): +++ not scanning '/system/lib/libmedia_jni.so' for 'printHello' (wrong CL)  
  8. D/TestLib (  174): Hello LIB!  
  9. 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
  1. #include <string.h>  
  2. #include <jni.h>  
  3.   
  4. /* This is a trivial JNI example where we use a native method 
  5.  * to return a new VM String. See the corresponding Java source 
  6.  * file located at: 
  7.  * 
  8.  *   apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java 
  9.  */  
  10. jstring  
  11. Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,  
  12.                                                   jobject thiz )  
  13. {  
  14.     return (*env)->NewStringUTF(env, "Hello from JNI !");  
  15. }  


将其改为

[cpp] view plaincopy
  1. #include <stdlib.h>  
  2. #include <string.h>  
  3. #include <stdio.h>  
  4. #include <jni.h>  
  5. #include <assert.h>  
  6.   
  7. /* This is a trivial JNI example where we use a native method 
  8.  * to return a new VM String. See the corresponding Java source 
  9.  * file located at: 
  10.  * 
  11.  *   apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java 
  12.  */  
  13. jstring native_hello(JNIEnv* env, jobject thiz)  
  14. {  
  15.     return (*env)->NewStringUTF(env, "动态注册JNI");  
  16. }  
  17.   
  18. /** 
  19. * 方法对应表 
  20. */  
  21. static JNINativeMethod gMethods[] = {  
  22.     {"stringFromJNI""()Ljava/lang/String;", (void*)native_hello},//绑定  
  23. };  
  24.   
  25. /* 
  26. * 为某一个类注册本地方法 
  27. */  
  28. static int registerNativeMethods(JNIEnv* env  
  29.         , const char* className  
  30.         , JNINativeMethod* gMethods, int numMethods) {  
  31.     jclass clazz;  
  32.     clazz = (*env)->FindClass(env, className);  
  33.     if (clazz == NULL) {  
  34.         return JNI_FALSE;  
  35.     }  
  36.     if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {  
  37.         return JNI_FALSE;  
  38.     }  
  39.   
  40.     return JNI_TRUE;  
  41. }  
  42.   
  43.   
  44. /* 
  45. * 为所有类注册本地方法 
  46. */  
  47. static int registerNatives(JNIEnv* env) {  
  48.     const char* kClassName = "com/example/hellojni/HelloJni";//指定要注册的类  
  49.     return registerNativeMethods(env, kClassName, gMethods,  
  50.             sizeof(gMethods) / sizeof(gMethods[0]));  
  51. }  
  52.   
  53. /* 
  54. * System.loadLibrary("lib")时调用 
  55. * 如果成功返回JNI版本, 失败返回-1 
  56. */  
  57. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {  
  58.     JNIEnv* env = NULL;  
  59.     jint result = -1;  
  60.   
  61.     if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {  
  62.         return -1;  
  63.     }  
  64.     assert(env != NULL);  
  65.   
  66.     if (!registerNatives(env)) {//注册  
  67.         return -1;  
  68.     }  
  69.     //成功  
  70.     result = JNI_VERSION_1_4;  
  71.   
  72.     return result;  
  73. }  

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


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

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

[cpp] view plaincopy
  1. typedef struct {    
  2. const char* name;  //Java中函数的名字  
  3. const char* signature;  //用字符串描述的函数的参数和返回值  
  4. void* fnPtr;  //指向C函数的函数指针  
  5. } 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


0 0
原创粉丝点击