Android == JNI动态注册

    传统的关于android使用JNI调用C/C++程序,首先javah 生产头文件,然后拷贝头文件里面的方法到C文件中进行映射调用,也就是JNI的静态注册,上一篇文章已经讲了,由于这种方法生成的映射方法名不太规则也比较长,二呢是调用数据较慢;因此可以使用JNI动态注册方法的方式来解决这个问题。

    学习这个JNI动态注册的时候还是遇到了挺多的问题的,现在就讲这些问题做下记录。

准备知识

1、packet的运用

    首先是对Java的不了解,不懂Java中对于packet的运用,导致程序无法运行,找不到class,参考了一下下面这个做法:

java 源文件带有包名,往往容易出错
如:H:\code\Hello2.java
package com.example;
 
public class Hello2{
    public static void main(String[]args){
        System.out.println("Hello2");
    }
}
这代码看上去没什么问题,执行:
H:\code>javac Hello2.java
H:\code>java Hello2
错误: 找不到或无法加载主类 Hello2
 
解决办法:
+.删除包名  或者 
+.在code 下创建一个与包名相同的文件结构(H:\code\com\example\Hello2.java)
  编译:H:\code>javac com/example/Hello2.java
  运行:H:\code>java com.example.Hello2

通过这个例子,我知道了运行带packet的Java的class文件,需要补全前缀。

2、Java程序在Android运行

    遇到这个问题的时候,我想到了几个解决的思路,写一个apk然后调用库,或者写一个Java service调用库。但是,一时之间不太想去学这些,有没有什么其他的解决办法?有,Android上虽然不能直接运行Java程序,但是可以通过jar包来运行。详细的方式可以查看前一篇博客:Android == 在Android系统上运行JAVA程序

编写Java端程序

    有了准备工作之后,就开始动手吧!首先在Android工程下随便建一个文件夹jnitest,继续建四个文件夹 src -> com -> example -> myjni,注意:这几个文件夹是嵌套的。在myjni文件夹中新建两个java文件,jnitest_javaclass_a.java和jnitest_javaclass_b.java。然后编写这两个Java程序,格式其实和静态注册的是一样的:

/* jnitest_javaclass_a.java */
package com.pioneer.jnitest;

import java.lang.ref.WeakReference;
public class jnitest_javaclass_a
{
    static
    {
        System.loadLibrary("jnitest_nativelibrary");        //加载库
    }

    public native void jnitest_nativefunction_01();
    public native void jnitest_nativefunction_02();

    public void jnitest_javafunction()
    {
    	jnitest_nativefunction_01();
	jnitest_nativefunction_02();
    }

    public static void main(String args[])
    {
        jnitest_javaclass_a class_instance = new jnitest_javaclass_a();
        class_instance.jnitest_javafunction();
    }
}

/* jnitest_javaclass_b.java */
package com.pioneer.jnitest;

public class jnitest_javaclass_b
{
    static
    {
        System.loadLibrary("jnitest_nativelibrary");
    }
    public native void jnitest_nativefunction_withpara(int para);
    public void jnitest_javafunction()
    {
        jnitest_nativefunction_withpara(10);
    }
    public static void main(String args[])
    {
        jnitest_javaclass_b class_instance = new jnitest_javaclass_b();
        class_instance.jnitest_javafunction();
    }
}

这样,两个Java程序就编写好了,接下来就是编译,

javac jnitest_javaclass_a.java jnitest_javaclass_b.java

然后切换到com那一级目录,将两个class文件打包成jar包,

dx --dex --output=jnitest.jar com/pioneer/jnitest/jnitest_javaclass_a.class com/pioneer/jnitest/jnitest_javaclass_b.class

如此,便生成了jnitest.jar这个jar包。

编写CPP文件及生成so库

动态注册和静态注册的不同之处就是动态注册没有了生成.h文件,在Java中调用System.loadLibrary("jnitest_nativelibrary");这句话的时候会去so库里面,也就是cpp中寻找JNI_OnLoad这个函数,然后在JNI_OnLoad这个函数中会将Java中的接口和本文件中的接口进行绑定,如下:

static JNINativeMethod gMethods_class[] = {
    {"jni_nativefunction",       "()V",    (void *)android_jni_nativefunction},
};

通过这个结构体可以进行Java程序和c++程序之间绑定,在{}中间的参数,第一个是Java中调用的native接口名称,第二个是接口的参数和返回值,第三个参数是本文件函数的指针,用于回调。第二个参数中()中的字符表示参数,后面的则代表返回值,如“()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[]
[D jdoubleArray double[]
[Z jbooleanArray boolean[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[J jlongArray long[]

 

如果java函数的参数是class,则以“L”开头,以“;”结尾,中间是用“/”隔开的包及类名,对应的C函数名的参数则为jobject,一个例外是String类,其对应类为jstring。

 

 

字符 java类型 C类型
Ljava/lang/String String jstring
Ljava/net/Socket Socket jobject

以MediaPlayer为例,Java层定义了如下native函数:                                                                           

   private native void getParameter(int key, Parcel reply);    

其对应的签名信息为:"(ILandroid/os/Parcel;)V"                      

好了,有了JNI的签名信息对照,然后就能撸代码了:

#define LOG_TAG "JNITEST"

#include "utils/Log.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include "jni.h"
#include "JNIHelp.h"
#include "android_runtime/AndroidRuntime.h"
#include "utils/Errors.h"  // for status_t
#include "utils/KeyedVector.h"
#include "utils/String8.h"
	
using namespace android;

//Java jnitest_javaclass_a类接口jnitest_nativefunction_01的本地C++实现
void android_jnitest_nativefunction_01(JNIEnv* env, jobject thiz)
{
	LOGD("android_jnitest_nativefunction_01!\n");
}

//Java jnitest_javaclass_a类接口jnitest_nativefunction_02的本地C++实现
void android_jnitest_nativefunction_02(JNIEnv* env, jobject thiz)
{
	LOGD("android_jnitest_nativefunction_02!\n");
}

//Java jnitest_javaclass_b类接口jnitest_nativefunction_withpara的本地C++实现
void android_jnitest_nativefunction_withpara(JNIEnv* env, jobject thiz, jint para)
{
	LOGD("This the native fuction with parameter input!\n");
	LOGD("Input parameter is  %d!\n", para);
}

//结构体绑定jnitest_javaclass_a中的两个接口和本地两个实现
static JNINativeMethod gMethods_class_a[] = {
    {"jnitest_nativefunction_01",       "()V",    (void *)android_jnitest_nativefunction_01},
    {"jnitest_nativefunction_02",       "()V",    (void *)android_jnitest_nativefunction_02},
};

//结构体绑定jnitest_javaclass_b中的接口和本地实现
static JNINativeMethod gMethods_class_b[] = {
    {"jnitest_nativefunction_withpara",    "(I)V",        (void *)android_jnitest_nativefunction_withpara},
};

//开始注册注册jnitest_javaclass_a和结构体
static int register_android_jnitest_nativefunction_forclass_a(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "com/pioneer/jnitest/jnitest_javaclass_a", gMethods_class_a, NELEM(gMethods_class_a));
}

//开始注册注册jnitest_javaclass_b和结构体
static int register_android_jnitest_nativefunction_forclass_b(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                "com/pioneer/jnitest/jnitest_javaclass_b", gMethods_class_b, NELEM(gMethods_class_b));
}

//此函数在Java中调用System.loadLibrary("");时会被自动触发,在这个函数中将调用上面的两个注册函数,最终返回JNI版本号
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    if (register_android_jnitest_nativefunction_forclass_a(env) < 0) {
        LOGE("ERROR: jnitest_1nativefunction native registration failed\n");
        goto bail;
    }

    if (register_android_jnitest_nativefunction_forclass_b(env) < 0) {
        LOGE("ERROR: jnitest_1nativefunction native registration failed\n");
        goto bail;
    }
    
    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
}

总的来说,思路还是比较清晰的,我画了一个简单的思维导图来梳理一下思路:

Android == JNI动态注册_第1张图片

     这就是我对于这个JNI动态注册的理解吧,其实和静态的注册相比,最终要实现的效果是一样的,都是通过在Java程序中调用System.loadLibrary();这个接口来访问libxxx.so库文件,然后两者的区别就是怎么去实现这个.so文件,静态注册是通过javah先生成.h文件,然后根据.h文件中的函数声明来实现具体的接口,最终得到libxxx.so这个库。

    而动态注册则不然,动态注册的时候在System.loadLibrary();这个接口被调用的时候,会去c++文件中调用JNI_OnLoad这个函数,然后在这个函数实现所有逻辑,包括Javafunction和nativefunction的绑定以及注册等等。

    最后声明一下,静态注册可以在Ubuntu上面直接测试运行,但是动态注册我是在机器上测试的,将cpp生成的库拷贝到安卓系统中的/system/lib/目录下,然后采用dalvikvm -cp jnitest.jar jnitest_javaclass_a来在Android上执行Java程序,具体的问题之前已经解释过了。最后在机器上运行的结果如下:

Android == JNI动态注册_第2张图片

你可能感兴趣的:(Android学习)