AndroidL JNI技术

JNI是Java Native Interface的缩写,JNI技术实现了java与本地代码(c,c++)的相互调用。
JNI的必要性: 
    java运行的环境是虚拟机,虚拟机与具体平台相关,JNI层屏蔽不同平台之间的差异,使的java可以做到平台无关特性。
    从运行速度与效率的角度考虑,采用JNI技术是明智的。

1.JNI动态注册
// frameworks/base/media/java/android/media/MediaScanner.java

........
public class MediaScanner
{
    static {
        // 加载动态库media_jni
        // 实际加载时会将其拓展, Linux系统为libmedia_jni.so, Windows系统为media_jni.dll

        System.loadLibrary("media_jni"); 
        native_init(); // 调用JNI层代码
    }
    ........
    // 带native的为本地方法,具体实现在JNI层完成
    private static native final void native_init();
    ........
}

// frameworks/base/media/jni/android_media_MediaScanner.cpp
........
static const char* const kClassMediaScanner =
        "android/media/MediaScanner";
........
static void android_media_MediaScanner_native_init(JNIEnv *env) // native_init的具体实现过程
{
    ALOGV("native_init");
    jclass clazz = env->FindClass(kClassMediaScanner);
    if (clazz == NULL) {
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }
}
........
static JNINativeMethod gMethods[] = { // 定义JNINativeMethod类型的数组
    ........
    {
        "native_init", // java中native函数名
        "()V", // 签名信息
        (void *)android_media_MediaScanner_native_init // 对应的本地函数指针
    },// 将所有native函数与本地函数建立对应关系
    ........
}

int register_android_media_MediaScanner(JNIEnv *env)
{   // 注册JNINativeMethod类型的数组,第二个参数表明Java中的那个类
    return AndroidRuntime::registerNativeMethods(env,
                kClassMediaScanner, gMethods, NELEM(gMethods));
}

// libnativehelper/include/nativehelper/Jni.h
........
typedef struct {
    const char* name;        // java中native方法名
    const char* signature;  // Java函数签名信息,表示参数类型与返回类型
    void*       fnPtr;              // JNI层对应函数的函数指针
} JNINativeMethod;
........

// frameworks/base/core/jni/AndroidRuntime.cpp
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{   // 调用jniRegisterNativeMethods来完成注册
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

// libnativehelper/include/nativehelper/JNIHelp.h
........
int jniRegisterNativeMethods(C_JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);
........

// libnativehelper/JNIHelp.cpp
........
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env); 
    ALOGV("Registering %s's %d native methods...", className, numMethods);
    scoped_local_ref<jclass> c(env, findClass(env, className));
    ........
    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) { // 调用JNIEnv的RegisterNatives来注册
        ........
    }
    return 0;
}
........

// register_android_media_MediaScanner调用过程
// 在Java中调用System.loadLibrary加载完JNI动态库后,如果有JNI_OnLoad函数,就会调用JNI_OnLoad函数
// 如果要进行动态注册,就必须实现JNI_OnLoad函数。静态注册也可以在这个函数中做一些初始化工作
// frameworks/base/media/jni/android_media_MediaPlayer.cpp

........
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = NULL;
    jint result = -1;
    // 通过vm来初始化JNIEnv类型的指针env, 
    // vm为虚拟机对象,每个Java进程都只有一个虚拟机对象
    // JNIEvn是一个与线程相关的一个结构体,每个线程也只有一个

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);
    ........
    // 动态注册MediaScanner的JNI函数
    if (register_android_media_MediaScanner(env) < 0) {
        ALOGE("ERROR: MediaScanner native registration failed\n");
        goto bail;
    }
    ........
}
........

2.JNI静态注册
1.编译MediaScanner.java生成MediaScanner.class文件如
有文件Project/src/android/media/Test.java 

package android.media;

class Test{
static {
System.loadLibrary("media_jni");
native_init();
}
private static native final void native_init();
private native final void native_setup();
}

cd ../Project/src/
javac ./android/media/Test.java // 在./android/media/目录下生成Test.class文件


2.使用java的工具javah, 如
javah -classpath ./ -d ./jni android.media.Test
-classpath ./ 表示Test.class是从当前目录下的子目录中
-d ./jni 表示生成的 xxx.h文件所在的目录
android.media.Test 表示包名.类名
在../Project/src/jni/下生成android_media_Test.h文件

3.命名规则 
头文件xxx.h: 包名.包名xxx.类名.java文件最终将生成 包名_包名xxx_类名.h文件
方法native_init:Java_android_media_Test_native_1init 包名前加Java_,包名中的.变为_,方法中的_变_1

4.将android_media_Test.h里面的方法实现,然后将其编译为media_jni模块导入手机

5.静态注册的弊端
每个有声明了native方法的java类所生成的class文件都要用javah生成一个头文件
javah生成的函数名特别长,不方便书写
初次调用native函数时需要根据函数名字搜索对应的JNI层函数来建立关联关系,影响运行效率


3.数据类型的转换
基本数据类型java->native 基本类型前加j

java    boolean   byte   char   short   int   long   float   double
native  jboolean  jbyte  jchar  jshort  jint  jlong  jfloat  jdouble

引用数据类型
java    objects  Class   String   Object[]      Throwable  boolean[]         byte[]             ......     float[]      double[]
native  jobject  jclass  jstring  jobjectArray  jthrowable  jbooleanArray  jbyteArray    ......     jfloatArray  jdoubleArray

4.JNIEnv操作jobject
// frameworks/base/media/jni/android_media_MediaPlayer.cpp

    ........
    MyMediaScannerClient(JNIEnv *env, jobject client)
        :   ........
    {
        ALOGV("MyMediaScannerClient constructor");
        jclass mediaScannerClientInterface = // 找到MediaScannerClient类在JNI层对应的jclass实例
                env->FindClass(kClassMediaScannerClient); 

        if (mediaScannerClientInterface == NULL) {
            ALOGE("Class %s not found", kClassMediaScannerClient);
        } else {
            mScanFileMethodID = env->GetMethodID(
                                    mediaScannerClientInterface, // java类对应的JNI实例
                                    "scanFile", // 取得scanFile方法的jMethodID, GetFieldID为获取属性jFildID
                                    "(Ljava/lang/String;JJZZ)V"); 
            ........
        }
    }
    ........
    virtual status_t scanFile(const char* path, long long lastModified,
            long long fileSize, bool isDirectory, bool noMedia)
    {
        ........
        jstring pathStr; // 定义一个jstring对象,相当于java中String对象在JNI的代表
        // 通过JNIEnv的NewStringUTF方法构造一个jstring对象,path为UTF-8字符串
        // 也可调用NewString构造一个Unicode字符串
        // 返过来,通过GetStringChars可以获取一个Unicode字符串,GetStringUTFChars可以获取一个UTF-8字符串
        // 用完jstring对象后一定要释放,否则会造成内存泄露
        // ReleaseStringChars函数或者ReleaseStringUTFChars函数可以释放资源

        if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
            // JNI中发生异常时不会直接退出,而是在函数返回后到Java层后,虚拟机才会抛出抛出异常
            // ExceptionOccured函数用于判断是否发生异常
            // ThrowNew函数用于向Java层抛出异常 eg: mEnv->ThrowNew(jclass, chars)

            mEnv->ExceptionClear(); // 清理当前JNI层发生的异常
            return NO_MEMORY;
        }
        // 调用JNIEnv的CallVoidMethod函数来调用java方法
        // mClient表示是MediaScannerClient的jobject对象
        // mScanFileMethodID表示是scanFile的jMethodID,后面的是参数
        // Call<Type>Method 为调用非静态方法, CallStatic<Type>Method为调用静态方法
        // Get/Set<Type>Field为获取或设置Java类的属性
        // <Type>为Object,Boolean,Byte,Char,Short,Int,Long,Float,Double
        // jobject由jmethodid与jfileid组成

        mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
                fileSize, isDirectory, noMedia);
        ........
    }
........

5.JNI类型签名
签名字符串构造规则: (参数1类型标识参数2类型标识...参数n类型标识)返回类型标识
(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V
最右边的V表示返回类型为void
第一个参数类型标识中L表示该参数是引用类型,构造规则是:L包名; ,其中包名中的.用/替换

类型标识示意图
Z                B        C         S          I      J          F         D             Ljava/lang/String;   [I         [Ljava/lang/object;   
boolean   byte   char   short   int   long   float   double   String                         int[]    Object[]
例如: "(ZBCSLjava/lang/String;[Ljava/lang/object;)[I" 对应的函数为 int[] f(boolean bo, byte by, char ch, short sh, String string, Object[] objs)
Java提供了工具javap可直接生成函数或变量的签名信息,用如下
javap -classpath ./ -d ./ -s -p android.media.Test
-classpath ./ -d ./ 与上面解释相同
-s 表示输出内部数据类型的签名信息
-p 打印所有函数和成员的签名信息, 默认只打印公共的函数或变量的签名信息


6.垃圾回收
Java中创建的对象最后是由垃圾回收器来释放内存,可它对JNI会有什么影响呢
// ./Project/src/jni/android_media_Test.cpp

static jclass save_thiz = NULL;
JNIEXPORT void JNICALL Java_android_media_Test_native_1init(JNIEnv * env, jclass thiz){
  ........
  // 保存Java层传入的jclass对象
  save_thiz = thiz; // 这种赋值不会增加jclass的引用计数
  ........
}

JNIEXPORT void JNICALL Java_android_media_Test_native_1setup(JNIEnv *, jobject){
  ........
  // 在这里使用save_thiz有可能出错,此时save_thiz有可能是野指针,所指的对象被Java垃圾回收机制回收
  ........
}

对于以上问题,JNI提供了三种类型的引用
Local Reference: 
本地引用,JNI层函数返回时被垃圾回收
jobject obj = env->NewLocalRef(jobjects); // obj对象会在所在函数退出的时候被回收
env->DeleteLocalRef(obj); // 立即回收obj对象,对于不需要的对象立即回收非常必要
Global Reference: 
全局引用,不主动释放即不会被垃圾回收 
jobject mClient = joevn->NewGlobalRef(jobjects); // 在构造函数中创建
env->DeleteGlobalRef(mClient); // 在析构函数中回收
Weak Global Reference: 
弱全局引用,在使用的过程中可能被回收,在使用前需要调用JNIEnv的IsSameObject判断是否被回收
jobject mObject = env->NewWeakGlobalRef(thiz);
env->IsSameObject(mObject,thiz);
env->DeleteWeakGlobalRef(mObject);


void test(JNIEnv * env, jstring strings){
  ........
  for(int i=0; i<100000; i++){
    jstring c = env->NewString(strings); // c将会在test方法退出时释放
    ........
    // env->DeleteLocalRef(c);  // 如果此处不调用此方法将导致内存不立即释放
  }
  ........
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值