Android JNI使用和原理分析

JNI原理分析
用法很比较简单。1.编写java文件,使用关键字native 2.编写头文件。3实现C++代码。4编译出SO 5.集成调用。

1.编写JAVA
创建Java文件:

package com.zx.testjni;

public class JNITest {

static{
	System.loadLibrary("testjni");
}

public  static native int add (int a,int b);

public  static native String sayHello (String name);

}

2.编译头文件

到此目录中 按住shift 右击鼠标 选择 在此处打开窗口命令 m

输入javah 生成头文件

3.编写C/C++文件
新建jni目录

拷贝 头文件,新建 Android.mk 和Application.mk文件 和c文件

Android.mk内容:
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := testjni
LOCAL_SRC_FILES := com_zx_testjni_JNITest.c

include $(BUILD_SHARED_LIBRARY)

Application.mk内容
APP_PLATFORM := android-17

头文件内容:
/* DO NOT EDIT THIS FILE - it is machine generated /
#include
/
Header for class com_zx_testjni_JNITest */

#ifndef _Included_com_zx_testjni_JNITest
#define _Included_com_zx_testjni_JNITest
#ifdef __cplusplus
extern “C” {
#endif
/*

  • Class: com_zx_testjni_JNITest
  • Method: add
  • Signature: (II)I
    */
    JNIEXPORT jint JNICALL Java_com_zx_testjni_JNITest_add
    (JNIEnv *, jclass, jint, jint);

/*

  • Class: com_zx_testjni_JNITest
  • Method: sayHello
  • Signature: (Ljava/lang/String;)V
    */
    JNIEXPORT jstring JNICALL Java_com_zx_testjni_JNITest_sayHello
    (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

C文件内容:
#include “com_zx_testjni_JNITest.h”
#include
#include

JNIEXPORT jint JNICALL Java_com_zx_testjni_JNITest_add
(JNIEnv *env, jclass object, jint a, jint b){
return a+b;
}

JNIEXPORT jstring JNICALL Java_com_zx_testjni_JNITest_sayHello
(JNIEnv *env, jclass object, jstring name){
// char str1[6] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘\0’};
// char str3[22];
// strcat( str3, str1);
// strcat( str3, name);
return (*env)->NewStringUTF(env,“hello i am jni”);
}

4编译SO文件

找到jni文件的目录,打开 cmd 输入 ndk-build ,如果没配置path 就如上图 输入全路径。

′f5刷新一下项目,会看到生成的文件

5调用

结果:

主要其实是C文件的编写是重点。 还是调用的原理是重点。

JNI是java native interface 。可以让Java代码调用到 native层方法。一般是C和C++。
生成头文件
比如我们有个源文件在:E:\com\zx\TestNative.java

第一种:直接cd到E:\目录 按住shift 点击右键 打开CMD 然后使用:javah com.zx.TestNative,其中javah后面的是需要生成头文件类的全路径(包名+类名),在当前目录就会生成com_zx_TestNative.h 头文件。
第二种:任意目录 打开CMD,使用如下命令:javah -classpath E:\ com.zx.TestNative 注意有空格。
其中-classpath后跟当前程序在磁盘上的位置,该位置只写到package com.zx;前一级目录就行了,后面是需要生成头文件类的全路径。就会在当前的目录生成com_zx_TestNative.h头文件。

头文件详解:

/* DO NOT EDIT THIS FILE - it is machine generated /
#include
/
Header for class com_zx_astudyandroidclient_TestNative */

#ifndef _Included_com_zx_astudyandroidclient_TestNative
#define _Included_com_zx_astudyandroidclient_TestNative
#ifdef __cplusplus
extern “C” {
#endif
/*

  • Class: com_zx_astudyandroidclient_TestNative
  • Method: add
  • Signature: (II)I
    */
    JNIEXPORT jint JNICALL Java_com_zx_astudyandroidclient_TestNative_add
    (JNIEnv *, jclass, jint, jint);

/*

  • Class: com_zx_astudyandroidclient_TestNative
  • Method: reduce
  • Signature: (II)I
    */
    JNIEXPORT jint JNICALL Java_com_zx_astudyandroidclient_TestNative_reduce
    (JNIEnv *, jclass, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

编写 C++代码:

/* DO NOT EDIT THIS FILE - it is machine generated /
#include “com_zx_TestNative.h”
#include
// 必须的头文件
#include
using namespace std;
/

  • Class: com_zx_TestNative
  • Method: add
  • Signature: (II)I
    */
    JNIEXPORT jint JNICALL Java_com_zx_TestNative_add
    (JNIEnv *env, jclass object, jint a, jint b);
    {
    cout << "Java_com_zx_TestNative_add : " << endl;
    return a+b;

}

/*

  • Class: com_zx_TestNative
  • Method: reduce
  • Signature: (II)I
    */
    JNIEXPORT jint JNICALL Java_com_zx_TestNative_reduce
    (JNIEnv *env, jclass object, jint a, jint b);
    {
    cout << "Java_com_zx_TestNative_reduce : " << endl;
    return a-b;

静态配置的缺点:
1.需要编译所有生命了native函数的Java类,每个所生成的class文件都得用javah生成一个头文件。
2.javah生成的JNI层函数名特别长,书写起来很不方便。
3.初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率。
动态注册:

1.java文件:
public class JNITest {

static{
	Log.d("ZX", "loadLibrary===");
	System.loadLibrary("testjni");
}

public  static native int add (int a,int b);

public  static native int reduce (int a,int b);

public  static native String sayHello (String name);

}

C++文件:

#include “com_zx_testjni_JNITest.h”

JNIEXPORT jint JNICALL add
(JNIEnv *env, jclass object, jint a, jint b){
LOGE(“JNICALL add\n”);
return a+b;
}

JNIEXPORT jint JNICALL reduce
(JNIEnv *env, jclass object, jint a, jint b){
LOGE(“JNICALL reduce\n”);
return a-b;
}

JNIEXPORT jstring JNICALL sayHello
(JNIEnv *env, jclass object, jstring name){
LOGE(“JNICALL sayHello\n”);
return env->NewStringUTF(“I am for C++”);
}

static JNINativeMethod gMethods[] = {
      {"add",       "(II)I",        (void *)add},
      {"reduce",       "(II)I",        (void *)reduce},
      {"sayHello",   "(Ljava/lang/String;)Ljava/lang/String;",   (void *)sayHello},
  };
//此函数通过调用RegisterNatives方法来注册我们的函数
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods,int methodsNum){

	LOGE("registerNativeMethods %d",methodsNum);
	jclass clazz;
    //找到声明native方法的类
    clazz = env->FindClass(className);
    if(clazz == NULL){

    	LOGE("find class err\n");

        return JNI_FALSE;
    }
    LOGE("start RegisterNatives\n");
   //注册函数 参数:java类 所要注册的函数数组 注册函数的个数

    int result=env->RegisterNatives(clazz,gMethods,methodsNum);

    LOGI("RegisterNatives result %d",result);
    if(result < 0){
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

static int registerNatives(JNIEnv* env){

	 LOGE("registerNatives-----");

    const char* className  = "com/zx/testjni/JNITest";

    return registerNativeMethods(env,className,gMethods, sizeof(gMethods)/ sizeof(gMethods[0]));
}

//可以在该函数中进行动态注册JNI
JNIEXPORT jint JNI_OnLoad(JavaVM *vm,void *reserved){
LOGE(“jni_load JNI_OnLoad\n”);

    JNIEnv* env = NULL;//定义JNI Env
    jint result = -1;
    /*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
     */
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return result;
    }
    LOGI("loading . . .");
    LOGE("register--------");

    /*开始注册
     * 传入参数是JNI env
     * 以registerNatives(env)为例说明
     */
    if(registerNatives(env) != JNI_TRUE) {
        LOGE("can't load registerNatives");
        goto end;
    }

    result = JNI_VERSION_1_4;
    end:
    return result;

}

一定要注意:这里是有分号结尾的。

这里就是把Java的方法和native方法对应起来。

这个方法名要写对,否则找不到对应的 java 类中的方法。

由于增加了Android打印:所以需要再Android.mk文件增加引用
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_LDLIBS := -llog

LOCAL_MODULE := testjni
LOCAL_SRC_FILES := com_zx_testjni_JNITest.cpp

include $(BUILD_SHARED_LIBRARY)

原理分析

1首先分析头文件:

/* DO NOT EDIT THIS FILE - it is machine generated /
#include
/
Header for class com_zx_testjni_JNITest */

#ifndef _Included_com_zx_testjni_JNITest
#define _Included_com_zx_testjni_JNITest
#ifdef __cplusplus
extern “C” {
#endif
/*

  • Class: com_zx_testjni_JNITest
  • Method: add
  • Signature: (II)I
    */
    JNIEXPORT jint JNICALL Java_com_zx_testjni_JNITest_add
    (JNIEnv *, jclass, jint, jint);

/*

  • Class: com_zx_testjni_JNITest
  • Method: sayHello
  • Signature: (Ljava/lang/String;)V
    */
    JNIEXPORT jstring JNICALL Java_com_zx_testjni_JNITest_sayHello
    (JNIEnv *, jclass, jstring);

#ifdef __cplusplus
}
#endif
#endif

这里的JNIEXPORT和JNICALL都是JNI的关键字,表示此函数是被JNI调用的。这两个宏在不同的操作系统中,其定义是不一样的。
#define JNIIMPORT
#define JNIEXPORT attribute ((visibility (“default”)))
#define JNICALL

  1. JNIEnv类实际代表了Java环境,通过这个JNIEnv* 指针,就可以对Java端的代码进行操作。例如,创建Java类的对象,调用Java对象的方法,获取Java对象的属性等等,JNIEnv的指针会被JNI传入到本地方法的实现两数中來对Java端的代码进行操作。

  2. jclass : 为了能够在c/c++中使用java类JNI.h头文件中专门定义了jclass类型来表示java中的Class类

VM怎样使Native Method跑起来:
我们知道,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的list,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。
如果一个方法描述符内有native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件内,但是它们会被操作系统加载到java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用java.system.loadLibrary()实现的。

你可能感兴趣的:(Android体系)