zoukankan      html  css  js  c++  java
  • [转]Android与JNI(二)

    Android与JNI(二)

    软件版本:
      ubuntu10.04
      java version "1.6.0_30-ea"
      eclipse
      android-ndk-r5b

    目录:

      1. 简介
      2. JNI 组件的入口函数
      3. 使用 registerNativeMethods 方法
      4. 测试
      5. JNI 帮助方法
      6. 参考资料

    1. 简介

      已经简单介绍了如何在 android  环境下使用 JNI 了。但是遵循 JNI 开发的基本步骤似乎有点死板,而且得到的本地函数名太丑了。所以非常有必要在这里介绍另外一种实现方法。

    2. JNI 组件的入口函数

      前一篇文章说到 static {System.loadLibrary("HelloJNI");} 会在第一次使用该类的时候加载动态库 libHelloJNI.so 。当 Android 的 VM 执行到System.loadLibrary() 这个函数时,首先会执行 JNI_OnLoad() 这个函数。与此对应的是卸载时调用 JNI_OnUnLoad() 。

      首先调用 c 组件中的 JNI_OnLoad()  的意图包括:

      (1) 告诉 VM 此 c 组件使用那一个 JNI 版本。如果动态库中没有提供 JNI_OnLoad(),VM 默认该动态库使用最老的 JNI 1.1 版本。由于新版的 JNI 做了许多扩充,如果需要使用 JNI 的新版功能,例如 JNI 1.4的 java.nio.ByteBuffer,就必须藉由 JNI_OnLoad() 函数来告知 VM。
      (2) 另外一个作用就是初始化,例如预先申请资源等。

      现在我们先现实一个非常简单的 JNI_OnLoad(),看是不是在真的有调用到它。在 HelloJNI.c 中加入代码:

    1 jint JNI_OnLoad(JavaVM* vm, void *reserved)
    2 {
    3     LOGD("%s called.
    ", __FUNCTION__);
    4 
    5     return JNI_VERSION_1_4;  // 注意这里要返回 JNI 的版本,否则会出错喔。
    6 }

      编译:

    复制代码
    $ndk-build 
    Compile thumb  : HelloJNI <= HelloJNI.c
    SharedLibrary  : libHelloJNI.so
    /home/eddy/workspace/HelloJNI/obj/local/armeabi/objs/HelloJNI/HelloJNI.o: In function `JNI_OnLoad':
    /home/eddy/workspace/HelloJNI/jni/HelloJNI.c:35: undefined reference to `LOGD'
    collect2: ld returned 1 exit status
    make: *** [/home/eddy/workspace/HelloJNI/obj/local/armeabi/libHelloJNI.so] Error 1
    复制代码

      加入头文件 #include <utils/Log.h> 之后再编译:

    $ndk-build 
    Compile thumb  : HelloJNI <= HelloJNI.c
    /home/eddy/workspace/HelloJNI/jni/HelloJNI.c:20:23: error: utils/Log.h: No such file or directory
    make: *** [/home/eddy/workspace/HelloJNI/obj/local/armeabi/objs/HelloJNI/HelloJNI.o] Error 1

      提示找不到指定的头文件。由于我们没有把这个 jni 放到整个 android 的源码中编译,所以遇到这个错误是正常的,解决的方法是在 Android.mk 中加入源码中头文件的路径。

    LOCAL_CFLAGS += -Iyour_path/android4.0/include/frameworks/base/include
    LOCAL_CFLAGS += -Iyour_path/android4.0/include/system/core/include
    LOCAL_CFLAGS += -Iyour_path/android4.0/include/frameworks/base/opengl/include
    LOCAL_CFLAGS += -Iyour_path/android4.0/include/system/core/include

      再编译:

    复制代码
    $ndk-build 
    Compile thumb  : HelloJNI <= HelloJNI.c
    SharedLibrary  : libHelloJNI.so
    /home/eddy/workspace/HelloJNI/obj/local/armeabi/objs/HelloJNI/HelloJNI.o: In function `JNI_OnLoad':
    /home/eddy/workspace/HelloJNI/jni/HelloJNI.c:36: undefined reference to `__android_log_print'
    collect2: ld returned 1 exit status
    make: *** [/home/eddy/workspace/HelloJNI/obj/local/armeabi/libHelloJNI.so] Error 1
    复制代码

      好吧,又出错了。但已经不是编译出错了,是链接出错,这个很简单,只要链接 liblog.so 这个库就可以了。在 Android.mk 中添加:

    LOCAL_LDLIBS += -lc -lm -llog

      再编译:

    $ndk-build 
    Compile thumb  : HelloJNI <= HelloJNI.c
    SharedLibrary  : libHelloJNI.so
    Install        : libHelloJNI.so => libs/armeabi/libHelloJNI.so

      OK,大功告成。如无意外,运行后你会在 logcat 中找到这样的打印:

    /dalvikvm( 1956): Trying to load lib /data/data/com.example.hellojni/lib/libHelloJNI.so 0x41345548
    D/dalvikvm( 1956): Added shared lib /data/data/com.example.hellojni/lib/libHelloJNI.so 0x41345548
    D/        ( 1956): JNI_OnLoad called.

      这说明了在加载 JNI 的动态库时,确实调用了 JNI_OnLoad() 。调用了这个库又有什么用呢?下面为你揭晓。

    3. 使用 registerNativeMethods 方法

      说了这么多,终于来重点了。先把修改后的 HelloJNI.c 列出来,然后再慢慢分析。

    复制代码
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  *
     16  */
     17 #include <string.h>
     18 #include <jni.h>
     19 #include "com_example_hellojni_HelloJNI.h"
     20 #include <utils/Log.h>
     21 
     22 #ifdef __cplusplus
     23 extern "C" {
     24 #endif
     25 
     26 static const char* className = "com/example/hellojni/HelloJNI";
     27 
     28 /* This is a trivial JNI example where we use a native method
     29  * to return a new VM String. See the corresponding Java source
     30  * file located at:
     31  *
     32  *   apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java
     33  */
     34 //jstring Java_com_example_hellojni_HelloJNI_stringFromJNI(JNIEnv *env, jobject this)
     35 //{
     36 //    return (*env)->NewStringUTF(env, "Hello from JNI !");
     37 //    //return "Hello from Jni !";
     38 //}
     39 jstring stringFromJNI(JNIEnv* env, jobject this)
     40 {
     41     return (*env)->NewStringUTF(env, "Hello form JNI!");
     42 }
     43 
     44 static JNINativeMethod gMethods[] = {
     45     { "stringFromJNI", "()Ljava/lang/String;", (void *)stringFromJNI },
     46 };
     47 
     48 // This function only registers the native methods, and is called from JNI_OnLoad
     49 int register_location_methods(JNIEnv *env)
     50 {
     51     jclass clazz;
     52 
     53     /* look up the class */
     54     clazz = (*env)->FindClass(env, className );
     55     //clazz = env->FindClass(env, className);
     56     if (clazz == NULL) {
     57         LOGE("Can't find class %s
    ", className);
     58         return -1;
     59     }
     60 
     61     LOGD("register native methods");
     62 
     63     /* register all the methods */
     64     if ((*env)->RegisterNatives(env, clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
     65     //if (env->RegisterNatives(env, clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK)
     66     {
     67         LOGE("Failed registering methods for %s
    ", className);
     68         return -1;
     69     }
     70 
     71     /* fill out the rest of the ID cache */
     72     return 0;
     73 }
     74 
     75 jint JNI_OnLoad(JavaVM* vm, void *reserved)
     76 {
     77     JNIEnv* env = NULL;
     78     jint result = -1;
     79 
     80     LOGD("%s: +", __FUNCTION__);
     81 
     82     // for c
     83     if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
     84     // for c++
     85     //if( vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
     86         LOGE("ERROR: GetEnv failed.
    ");
     87         return result;
     88     }
     89 
     90     if( register_location_methods(env) < 0 )
     91     {
     92         LOGE("ERROR: register location methods failed.
    ");
     93         return result;
     94     }
     95 
     96     return JNI_VERSION_1_4;
     97 }
     98 
     99 
    100 
    101 void JNI_OnUnload(JavaVM* vm, void *reserved)
    102 {
    103     return;
    104 }
    105 
    106 #ifdef __cplusplus
    107 }
    108 #endif
    复制代码

      先说一说这段代码实现了什么,他与 [1] 的结果完全一样,实现了 JAVA 中声明的 stringFromJNI 本地方法,返回一个字符串。至于为什么不再需要以Java_com_example_hellojni_HelloJNI_stringFromJNI 命名,就要慢慢分析了。

      还是从 JNI_OnLoad() 这个函数开始说起。该函数的动作包括:获取 JNI 环境对象,登记本地方法,最后返回 JNI 版本。值得引起注意的是第83和85行,在 c 环境下编译,使用第83行,c++ 环境下,使用第85行,否则会编译出错。

      登记本地方法,作用是将 c/c++ 的函数映射到 JAVA 中,而在这里面起到关键作用的是结构体 JNINativeMethod 。他定义在 jni.h 中。

    1 typedef struct {   
    2    const char* name;     /* java 中声明的本地方法名称 */
    3    const char* signature;  /* 描述了函数的参数和返回值 */
    4    void*       fnPtr;    /* c/c++的函数指针 */
    5 } JNINativeMethod; 

      声明实例就是第44到46行。

    1 static JNINativeMethod gMethods[] = {
    2     { "stringFromJNI", "()Ljava/lang/String;", (void *)stringFromJNI },
    3 };

      参数分析:

      "stringFromJNI":Java 中声明的本地方法名;
      (void *)stringFromJNI:映射对象,本地 c/c++ 函数,名字可以与 Java 中声明的本地方法名不一致。
      "()Ljava/lang/String;":这个应该是最难理解的,也就是结构体中的 signature 。他描述了本地方法的参数和返回值。例如
      "()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 类,则以"L"开头,以";"结尾,中间是用"/"隔开包及类名,而其对应的 C 函数的参数则为 jobject,一个例外是 String 类,它对应的 c 类型为 jstring 。
      Ljava/lang/String;     String     jstring
      Ljava/net/Socket;      Socket    jobject
      如果 JAVA 函数位于一个嵌入类(也被称为内部类),则用$作为类名间的分隔符,例如:"Landroid/os /FileUtils$FileStatus;"。

      最终是通过第64行代码登记所有记录在 JNINativeMethod 结构体中的 JNI 本地方法。

      使用 registerNativeMethods 方法不仅仅是为了改变那丑陋的长方法名,最重要的是可以提高效率,因为当 Java 类透过 VM 呼叫到本地函数时,通常是依靠 VM 去动态寻找动态库中的本地函数(因此它们才需要特定规则的命名格式),如果某方法需要连续呼叫很多次,则每次都要寻找一遍,所以使用 RegisterNatives 将本地函数向 VM 进行登记,可以让其更有效率的找到函数。

      registerNativeMethods 方法的另一个重要用途是,运行时动态调整本地函数与 Java 函数值之间的映射关系,只需要多次调用 registerNativeMethods 方法,并传入不同的映射表参数即可。

    4. 测试

      为了对 registerNativeMethods有更好的理解,我在 Java 中再添加一个本地方法。

    复制代码
     1 package com.example.hellojni;
     2 
     3 import android.os.Bundle;
     4 import android.app.Activity;
     5 import android.util.Log;
     6 import android.view.Menu;
     7 import android.view.MenuItem;
     8 import android.widget.TextView;
     9 import android.support.v4.app.NavUtils;
    10 
    11 public class HelloJNI extends Activity {
    12 
    13     @Override
    14     public void onCreate(Bundle savedInstanceState) {
    15         super.onCreate(savedInstanceState);
    16         setContentView(R.layout.hello_jni);
    17         getActionBar().setDisplayHomeAsUpEnabled(true);
    18         
    19         /* Create a TextView and set its content.
    20          * the text is retrieved by calling a native
    21          * function.
    22          */
    23         TextView  tv = new TextView(this);
    24         tv.setText( stringFromJNI() );
    25         setContentView(tv);
    26         
    27         Log.d("JNI", "max = " + max(10, 100));
    28     }
    29 
    30     @Override
    31     public boolean onCreateOptionsMenu(Menu menu) {
    32         getMenuInflater().inflate(R.menu.hello_jni, menu);
    33         return true;
    34     }
    35 
    36     
    37     @Override
    38     public boolean onOptionsItemSelected(MenuItem item) {
    39         switch (item.getItemId()) {
    40             case android.R.id.home:
    41                 NavUtils.navigateUpFromSameTask(this);
    42                 return true;
    43         }
    44         return super.onOptionsItemSelected(item);
    45     }
    46 
    47     /* A native method that is implemented by the
    48      * 'HelloJNI' native library, which is packaged
    49      * with this application.
    50      */
    51     public native String  stringFromJNI();
    52 
    53     /* This is another native method declaration that is *not*
    54      * implemented by 'HelloJNI'. This is simply to show that
    55      * you can declare as many native methods in your Java code
    56      * as you want, their implementation is searched in the
    57      * currently loaded native libraries only the first time
    58      * you call them.
    59      *
    60      * Trying to call this function will result in a
    61      * java.lang.UnsatisfiedLinkError exception !
    62      */
    63     public native String  unimplementedStringFromJNI();
    64     
    65     public native int max(int a,int b);
    66 
    67     /* this is used to load the 'HelloJNI' library on application
    68      * startup. The library has already been unpacked into
    69      * /data/data/com.example.HelloJni/lib/libHelloJNI.so at
    70      * installation time by the package manager.
    71      */
    72     static {
    73         System.loadLibrary("HelloJNI");
    74     }
    75 }
    复制代码

      在 HellocJNI.c 中添加 max 的实现方法。

    复制代码
    1 int native_max(JNIEnv* env, jobject this, int a, int b)
    2 {
    3     return  (a > b ? a:b);
    4 }
    5 
    6 static JNINativeMethod gMethods[] = {
    7     { "stringFromJNI", "()Ljava/lang/String;", (void *)stringFromJNI },
    8     { "max", "(II)I", (void *)native_max },
    9 };
    复制代码

      用 ndk 编译生成动态库。

    $ndk-build 
    Compile thumb  : HelloJNI <= HelloJNI.c
    SharedLibrary  : libHelloJNI.so
    Install        : libHelloJNI.so => libs/armeabi/libHelloJNI.so

      在模拟器上 run as ---> Android Application 。可以看到打印:

    D/dalvikvm( 2174): Trying to load lib /data/data/com.example.hellojni/lib/libHelloJNI.so 0x413488a8
    D/dalvikvm( 2174): Added shared lib /data/data/com.example.hellojni/lib/libHelloJNI.so 0x413488a8
    D/        ( 2174): JNI_OnLoad: +
    D/        ( 2174): register native methods
    D/JNI     ( 2174): max = 100

      证明 max 调用成功。

      通过 registerNativeMethods 这种方法,我们可以看到操作的过程中,不需要再使用 javah -jni 生成 jni 头文件。c/c++ 的函数名也可以自由取名。

    5. JNI 帮助方法

      在 Android 源码中 your_path/dalvik/libnativehelper/include/nativehelper/JNIHelp.h 这个头文件提供了一些关于 JNI 的帮助函数,包括本地方法注册、异常处理等函数。但是使用这些函数需要链接动态库 libnativehelper.so 。还提供了一个计算方法映射表长度的宏定义。

    1 #ifndef NELEM
    2 # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
    3 #endif

      有了这个宏之后,我们就可以这样子写:

    1 (*env)->RegisterNatives(env, clazz, gMethods, NELEM(gMethods));

      6. 参考资料

      [1]. Android与JNI(一)
      [2]. Android JNI知识简介
      [3]. Android中JNI编程的那些事儿
      [4]. Android 动态注册JNI

  • 相关阅读:
    Bootstrap3入门
    Pi
    比Redis更快:Berkeley DB面面观
    搞定KMP匹配算法
    elasticsearch文档-analysis
    21本计算机数学相关的免费电子书
    [Android开发常见问题-12] Android开发中debug.keystore如何使用。
    (Java实现) 组合的输出
    (Java实现) 自然数的拆分
    (Java实现) 自然数的拆分
  • 原文地址:https://www.cnblogs.com/blackcatx/p/6001518.html
Copyright © 2011-2022 走看看