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调用的情况:

android jrtplib 使用 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 .

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

android jrtplib 使用 android jni使用_android_02

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
            }

        });        

    }
}
  1. 包含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层实现的。

  1. 注册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如果成功则无任务返回回信息,如图:

android jrtplib 使用 android jni使用_android_03



  1. 之后在目录下便生成了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

等等…

  1. 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.mkView 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)

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