Android 开发中JNI的使用总结

http://www.cnblogs.com/spring2010/archive/2012/03/26/2416415.html

  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. JNI的Native方法的注册
    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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值