spring2010

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

 

  1. 什么是JNI
         JNI是Java Native Interface 的缩写,意为java本地接口, 使用JNI技术可以使得java语言与其它开发语言(如 C、C++ 和汇编语言)编写的应用程序或库进行相互操作。Android系统中的JNI运行通常是在java语言开发的apk或其它组件中调用C/C++开发的底层 模块。

  2. JNI的调用
         Android系统中应用层的操作都是由java编写的,而这些java编写的类最后都 要通过编译成Dex型式的ByteCode,通过Dalvik虚拟机(VM: Virtual Machine)来执行。java的开发的应用是运行在VM中的,而C,C++语言是运行在另一个平台中的,两者是通过什么来连接进行沟通起来的?是通过 Dalvik虚拟机进行连接,采用JNI技术来进行沟通。Android系统 中JNI调用的情况:

    Java要调用Native的方法要通过加载动态库的方法来实现,调用 System.loadLIbrary就可以加载动态库了。如下面的例子:


    static{
    try{
    System.loadLibrary("samJintest");
    }catch(UnsatisfiedLinkError e){
    Log.w(TAG, "can't load the library:" + LIB_NAME);
    }catch(Exception e){
    Log.w(TAG, "can't load the library:" + LIB_NAME);
    }
    }

    通常做法是在类中的static块内进行加载,当执行System.loadLIbrary时,在android系统中就会去/system/lib目录下去查找相对应的so, 上面的例子就是要加载libsamJintest.so .

  3. JNINative方法的注册
    1. 示意代码 分布情况:



      testJniActivity.java代码如下:
      //testJniActivity.java

      //。。。。省略代码
      publicclass testJniActivity extends Activity {
      /** Called when the activity is first created. */
      //。。。。省略代码
      @Override
      publicvoid onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
      //。。。。省略代码
      mButAdd.setOnClickListener(new Button.OnClickListener(){
      @Override
      publicvoid onClick(View v) {
      // TODO Auto-generated method stub
      //。。。。省略代码
      int add = mHJni.add(first, second);//--调用JniNativeHelp.add
      }

      });

      mButSub.setOnClickListener(new Button.OnClickListener(){
      @Override
      publicvoid onClick(View v) {
      // TODO Auto-generated method stub
      //。。。。省略代码
      int sub = mHJni.sub(first, second); //--调用JniNativeHelp.sub
      }

      });

      }
      }

      包含Native方法的JniNativeHelp.java代码:
      //文件JniNativeHelp.java
      package com.android.sam.test;

      import android.util.Log;

      publicclass JniNativeHelp{
      publicfinalstatic String TAG="JniNativeHelp";
      publicfinalstatic String LIB_NAME = "samJintest";

      static{
      try{
      System.loadLibrary("samJintest");
      }catch(UnsatisfiedLinkError e){
      Log.w(TAG, "can't load the library:" + LIB_NAME);
      }catch(Exception e){
      Log.w(TAG, "can't load the library:" + LIB_NAME);
      }
      }

      public String getTagValue(){
      return TAG;
      }

      publicnativeint add(int a, int b);
      publicnativeint sub(int a,int b);
      }

      从上面的代码我们知道包括有两个Native方法add加法操作,sub减法操作。其前面都有一个关键修饰词Native. 也就是表明这两个方法是有JNI层实现的。

    2. 注册Native方法有二种方式:一种是静态注册,另一种是动态注册

      以下分别讲述这两种方法

      静态注册Native方法

          静态方法是根据JNI规定的函数命名规则来找到指定的Native实现函数。

      a)  testJniActivity.java和JniNativeHelp.java是在同个Android project中的,编译通过后通过java自带的工具javah可生成JNI层的头文件。通过shell(window平台是命令)切换到工程下的bin目录,执行:javah packagename.classname, 本例子是: javah com.android.sam.test.JniNativeHelp

      如果成功则无任务返回回信息,如图:

      之后在目录下便生成了com.android.sam.test.JniNativeHelp.h

      //文件 com.android.sam.test.JniNativeHelp.h

      /* DO NOT EDIT THIS FILE - it is machine generated */
      #include <jni.h>
      /* Header for class com_android_sam_test_JniNativeHelp */

      #ifndef _Included_com_android_sam_test_JniNativeHelp
      #define _Included_com_android_sam_test_JniNativeHelp
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
      * Class: com_android_sam_test_JniNativeHelp
      * Method: add
      * Signature: (II)I
      */

      JNIEXPORT jint JNICALL Java_com_android_sam_test_JniNativeHelp_add
      (JNIEnv *, jobject, jint a, jint b);

      /*
      * Class: com_android_sam_test_JniNativeHelp
      * Method: sub
      * Signature: (II)I
      */
      JNIEXPORT jint JNICALL Java_com_android_sam_test_JniNativeHelp_sub
      (JNIEnv *, jobject, jint a, jint b);

      #ifdef __cplusplus
      }
      #endif
      #endif

      a)      创建一个对应的实现文件名为com.android.sam.test.JniNativeHelp.cpp 实现具体的Native函数体(代码在附录中)


      动态注册Native方法
          静态注册的方法的缺点是:1.要通过javah来生成JNI层的头文件。2.Native的函数名称太长,如果packagename的名称长提话就更不得了。不方便阅读。3.第一次调用时,Native要根据函数名称规则来建议联系,效率会有所响应。

      首先建议一个文件名为:com.android.sam.test.JniNativeHelp.cpp (文件名可以是xxx.cpp)

      先看一下最终的代码:

      //文件com.android.sam.test.JniNativeHelp.cpp
      #define LOG_TAG "JniNativeHelp_lib"

      #include "stdio.h"
      #include "string.h"
      #include "stdlib.h"
      #include "jni.h"
      #include "JNIHelp.h"
      #include "cutils/log.h"


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


      /*
      * Class: com_android_sam_test_JniNativeHelp
      * Method: add
      * Signature: (II)I
      */
      int JniNativeHelp_add(JNIEnv *env, jobject thiz, jint a, jint b)
      {
      SLOGI("native-----add----");
      return a+b;
      }

      /*
      * Class: com_android_sam_test_JniNativeHelp
      * Method: sub
      * Signature: (II)I
      */
      int JniNativeHelp_sub(JNIEnv *env, jobject thiz, jint a, jint b)
      {
      SLOGI("native-----sub----");
      return a-b;
      }

      //本地实现方法列表,该gMethods变量定义必须放到所有注册的方法之后,否则编译时会提示有些方法没有声明
      static JNINativeMethod gMethods[] = {
      {"add", "(II)I", (void*)JniNativeHelp_add },
      {"sub", "(II)I", (void*)JniNativeHelp_sub },
      };


      //目标JAVA类路径
      staticconstchar *classPathName = "com/android/sam/test/JniNativeHelp";

      //为调用的某个JAVA类注册本地JNI函数
      int registerNativeMethods(JavaVM* vm)
      {
      SLOGI("registerNativeMethods");

      JNIEnv* env = NULL;
      if( vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK ){
      SLOGE("JNI_VERSION_1_4 != JNI_OK");
      return -1;
      }

      int numMethods = NELEM(gMethods);
      return jniRegisterNativeMethods(env,classPathName, gMethods, numMethods);

      }

      //为当前虚拟机平台注册本地JNI
      jint JNI_OnLoad(JavaVM* vm, void* reserved){
      SLOGI("--------JNI_OnLoad-------");

      if(registerNativeMethods(vm) < 0 ){
      SLOGE("error");
      return -1;
      }
      return JNI_VERSION_1_4; //如果注册成功,返回版本信息
      }

      动态注册通过JNINativeMethod结构来记录Native函数的对应关系。如下所示:

      static JNINativeMethod gMethods[] = {             

          {"add", "(II)I", (void*)JniNativeHelp_add },

          {"sub", "(II)I", (void*)JniNativeHelp_sub },

      };

      该数组有两组元素。每组元素由三个成员构成。

      拿第一组元素来说明一下:

      {"add",        //对应Java Native类中声明 的Native函数名称,在JniNativeHelp.java文件

      "(II)I",         //表示该方法的参数类型及个数和返回值类型,叫做函数签名信息

       (void*)JniNativeHelp_add   //JNI Native方法的具体实现的函数名称,在

       },

      重点说明一下第二元素”(II)I”是函数签名信息,格式为”(参数1类型标识参数2类型标识..参数n类型标识)返回值类型标识”

      以下是JNI与java的类型标识对应表:

      类型标识           Java类型

      Z                           boolean

      B                           byte

      C                           char

      S                           short

      I                            int

      J                            long

      等等…

      所以说”(II)I” 表示该函数有二个参数分别为 int , int ,返回值类型为int。

      注意地方:该数组static JNINativeMethod gMethods[]=变量的定义必须要在JNI Native函数(数组中现在注册的对应的函数是JniNativeHelp_add, JniNativeHelp_sub)的声明之后进行,要不然编译会出错。

      真正注册的操作是必须要实现jint JNI_OnLoad(JavaVM* vm, void* reserved)方法。

      注意的地方:

      在JNI_OnLoad方法中如果注册成功的话返值为JNI_VERSION_1_4 。

      函数jniRegisterNativeMethods的调用是进行注册操作,该方法是Android系统中实现,所以要加上头文件#include "JNIHelp.h"。

      我们再看一下Native函数的具体实现,一共有二个:

      int  JniNativeHelp_add(JNIEnv *env, jobject thiz, jint a, jint b)
      {
      SLOGI("native-----add----");
      return a+b;
      }

      int JniNativeHelp_sub(JNIEnv *env, jobject thiz, jint a, jint b)
      {
      SLOGI("native-----sub----");
      return a-b;
      }

      每个函数的前两个参数是必须的,具体是什么再找资料。之后的参数就是对应的参数类型,其实也有类型转换关系。如下:

      JNI Native类型      Java                           

      Jboolean                 boolean

      Jbyte                        byte

      Jchar                        char

      Jshort                      short

      Jint                           int

      Jlong                        long

      Jfloat                       float

      Jdouble                double

      等等…

  4.  Android中Makefile的改写

    要把JNI层Native的具体实现编译成动态库,就要用到makefile脚本.Android的整个系统代码是通过一套很有规则的makefile文件脚本进行编译的。只要大概熟悉一下,就可以改写成一个适合自己新加入模块的makefile文件。

    我把com.android.sam.test.JniNativeHelp.cpp,如果是静态注册还有文件头文件com.android.sam.test.JniNativeHelp.h放在android_source_code/framework/base/learn_jni_test/jni 目录下,同时也放入自己改写的makefile文件,内容如下:

    //文件 Android.mk

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

    LOCAL_MODULE_TAGS := optional
    LOCAL_MODULE_TAGS := eng
    LOCAL_PRELINK_MODULE := false
    LOCAL_SRC_FILES:= \
    com_android_sam_test_JniNativeHelp.cpp

    LOCAL_SHARED_LIBRARIES := \
    libnativehelper \
    libutils

    LOCAL_C_INCLUDES += \
    $(PV_INCLUDES) \
    $(JNI_H_INCLUDE) \
    $(call include-path-for, corecg graphics)

    LOCAL_CFLAGS +=
    LOCAL_LDLIBS := -lpthread

    LOCAL_MODULE:= libsamJintest

    include $(BUILD_SHARED_LIBRARY)

    以上编译是依赖原来编译完的一些库,具体没有去细究。

  5. 附录所有代码:
    android开发中JNI的使用_代码.7z








posted on 2012-03-26 22:07  spring2010  阅读(1894)  评论(2编辑  收藏  举报