Android JNI 高级编程

Posted ayanwan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android JNI 高级编程相关的知识,希望对你有一定的参考价值。

       JNI作为java空间与C空间的沟通桥梁,在android中起到至关重要的作用。本文讲述jni的高级编程,需要有一定的jni基础,先分析了android源码中的jni编程模式,然后阐述一些实用的jni编程技巧。基础文章可以参考以下相关文章:

Android NDK开发环境搭建

Android JNI概述

JNI各种类型转换总结


        在android源码中,有两种JNI的注册方式,一种是:在android系统启动之初,使用startReg方法进行注册;另一种是使用static静态代码块中System.loadLibrary方法来加载动态库。

1、startReg

/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)

    /*
     * This hook causes all future threads created in this process to be
     * attached to the JavaVM.  (This needs to go away in favor of JNI
     * Attach calls.)
     */
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);

    ALOGV("--- registering native functions ---\\n");

    /*
     * Every "register" function calls one or more things that return
     * a local reference (e.g. FindClass).  Because we haven't really
     * started the VM yet, they're all getting stored in the base frame
     * and never released.  Use Push/Pop to manage the storage.
     */
    env->PushLocalFrame(200);

    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) //gRegJNI包含了几百个原生函数
        env->PopLocalFrame(NULL);
        return -1;
    
    env->PopLocalFrame(NULL);

    //createJavaThread("fubar", quickTest, (void*) "hello");

    return 0;
static const RegJNIRec gRegJNI[] = 
    REG_JNI(register_com_android_internal_os_RuntimeInit),
    REG_JNI(register_android_os_Process),
    REG_JNI(register_android_os_Binder),
...
下面以register_android_os_Binder为例,分析android 原生jni编程

1.1 register_android_os_Binder

int register_android_os_Binder(JNIEnv* env)

    if (int_register_android_os_Binder(env) < 0)
        return -1;
    if (int_register_android_os_BinderInternal(env) < 0)
        return -1;
    if (int_register_android_os_BinderProxy(env) < 0)
        return -1;

    jclass clazz;

    clazz = env->FindClass("android/util/Log");
    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.util.Log");
    gLogOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
    gLogOffsets.mLogE = env->GetStaticMethodID(
        clazz, "e", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I");
    assert(gLogOffsets.mLogE);

    clazz = env->FindClass("android/os/ParcelFileDescriptor");
    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
    gParcelFileDescriptorOffsets.mConstructor
        = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");

    clazz = env->FindClass("android/os/StrictMode");
    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.StrictMode");
    gStrictModeCallbackOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
    gStrictModeCallbackOffsets.mCallback = env->GetStaticMethodID(
        clazz, "onBinderStrictModePolicyChange", "(I)V");
    LOG_FATAL_IF(gStrictModeCallbackOffsets.mCallback == NULL,
                 "Unable to find strict mode callback.");

    return 0;

       先看int_register_android_os_Binder方法。

static struct bindernative_offsets_t

    // Class state.
    jclass mClass;//用于保存java空间中的binder的实例
    jmethodID mExecTransact;//用于保存binder类中的execTransact方法

    // Object state.
    jfieldID mObject;//用于保存JavaBBinderHolder对象的指针

 gBinderOffsets;

const char* const kBinderPathName = "android/os/Binder";

static int int_register_android_os_Binder(JNIEnv* env)

    jclass clazz;

    clazz = env->FindClass(kBinderPathName);
    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Binder");

    gBinderOffsets.mClass = (jclass) env->NewGlobalRef(clazz);// mClass是在jni空间获取了一个binder实例
    gBinderOffsets.mExecTransact
        = env->GetMethodID(clazz, "execTransact", "(IJJI)Z");//获取并保存binder的成员方法execTranscat
    assert(gBinderOffsets.mExecTransact);

    gBinderOffsets.mObject
        = env->GetFieldID(clazz, "mObject", "J");//获取并保存了binder的成员变量mObject
    assert(gBinderOffsets.mObject);

    return AndroidRuntime::registerNativeMethods(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));//注册native方法
(1)gBinderOffsets结构体是关键。

首先,该结构体里面保存了jni中需要变量或方法的ID;

其次,这个是static属性,这保证了该结构体只能在本文件中使用,更为重要的是,这保证了该变量的值在程序整个运行期间都不释放。这样就可以减少获取java类中成员变量和成员函数的时间,进而提高运行效率。这种编程思路是应该提倡使用的。
(2)AndroidRuntime::registerNativeMethods该方法就是简单的注册native方法,这里就不分析了。

        同样的,int_register_android_os_BinderInternal和int_register_android_os_BinderProxy也是都是一样的编程手法:使用静态结构体在注册native方法之前,获取并保存变量或方法的ID。


1.2 android_os_Binder_init的jni分析

该方法在binder的构造函数的调用:

   /**
     * Default constructor initializes the object.
     */
    public Binder() 
        init();

        if (FIND_POTENTIAL_LEAKS) 
            final Class<? extends Binder> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) 
                Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            
        
    
其源码如下:

static void android_os_Binder_init(JNIEnv* env, jobject obj)

    JavaBBinderHolder* jbh = new JavaBBinderHolder();
    if (jbh == NULL) 
        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
        return;
    
    ALOGV("Java Binder %p: acquiring first ref on holder %p", obj, jbh);
    jbh->incStrong((void*)android_os_Binder_init);
    env->SetLongField(obj, gBinderOffsets.mObject, (jlong)jbh);//将JavaBBinderHolder赋值给mObject。
由此可见,在实例化Binder的时候,就JavaBBinderHolder对象指针保存在了gBinderOffsets.mObject里。

(1)JavaBBinderHolder分析

class JavaBBinderHolder : public RefBase

public:
    sp<JavaBBinder> get(JNIEnv* env, jobject obj)
    
        AutoMutex _l(mLock);
        sp<JavaBBinder> b = mBinder.promote();
        if (b == NULL) 
            b = new JavaBBinder(env, obj);
            mBinder = b;
            ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%" PRId32 "\\n",
                 b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount());
        

        return b;
    

    sp<JavaBBinder> getExisting()
    
        AutoMutex _l(mLock);
        return mBinder.promote();
    

private:
    Mutex           mLock;
    wp<JavaBBinder> mBinder;
;
该类的get(JNIEnv* env, jobject obj)方法实例化了JavaBBinder方法,并返回该对象。

(2)JavaBBinder的jni分析

static JavaVM* jnienv_to_javavm(JNIEnv* env)

    JavaVM* vm;
    return env->GetJavaVM(&vm) >= 0 ? vm : NULL;


static JNIEnv* javavm_to_jnienv(JavaVM* vm)

    JNIEnv* env;
    return vm->GetEnv((void **)&env, JNI_VERSION_1_4) >= 0 ? env : NULL;


class JavaBBinder : public BBinder

public:
    JavaBBinder(JNIEnv* env, jobject object)
        : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object))//使用初始化列表对成员变量进行初始化
    
        ALOGV("Creating JavaBBinder %p\\n", this);
        android_atomic_inc(&gNumLocalRefs);
        incRefsCreated(env);
    

    bool    checkSubclass(const void* subclassID) const
    
        return subclassID == &gBinderOffsets;
    

    jobject object() const
    
        return mObject;
    

protected:
    virtual ~JavaBBinder()//释放mObject成员变量。
    
        ALOGV("Destroying JavaBBinder %p\\n", this);
        android_atomic_dec(&gNumLocalRefs);
        JNIEnv* env = javavm_to_jnienv(mVM);
        env->DeleteGlobalRef(mObject);
    

    virtual status_t onTransact(
        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
    
        JNIEnv* env = javavm_to_jnienv(mVM);

        ALOGV("onTransact() on %p calling object %p in env %p vm %p\\n", this, mObject, env, mVM);

        IPCThreadState* thread_state = IPCThreadState::self();
        const int32_t strict_policy_before = thread_state->getStrictModePolicy();

        //printf("Transact from %p to Java code sending: ", this);
        //data.print();
        //printf("\\n");
        jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
            code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);//调用execTransact()方法,是mObject,可以想到是stub。
        jthrowable excep = env->ExceptionOccurred();

        if (excep) 
            report_exception(env, excep,
                "*** Uncaught remote exception!  "
                "(Exceptions are not yet supported across processes.)");
            res = JNI_FALSE;

            /* clean up JNI local ref -- we don't return to Java code */
            env->DeleteLocalRef(excep);
        

        // Check if the strict mode state changed while processing the
        // call.  The Binder state will be restored by the underlying
        // Binder system in IPCThreadState, however we need to take care
        // of the parallel Java state as well.
        if (thread_state->getStrictModePolicy() != strict_policy_before) 
            set_dalvik_blockguard_policy(env, strict_policy_before);
        

        jthrowable excep2 = env->ExceptionOccurred();
        if (excep2) 
            report_exception(env, excep2,
                "*** Uncaught exception in onBinderStrictModePolicyChange");
            /* clean up JNI local ref -- we don't return to Java code */
            env->DeleteLocalRef(excep2);
        

        // Need to always call through the native implementation of
        // SYSPROPS_TRANSACTION.
        if (code == SYSPROPS_TRANSACTION) 
            BBinder::onTransact(code, data, reply, flags);
        

        //aout << "onTransact to Java code; result=" << res << endl
        //    << "Transact from " << this << " to Java code returning "
        //    << reply << ": " << *reply << endl;
        return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;
    

    virtual status_t dump(int fd, const Vector<String16>& args)
    
        return 0;
    

private:
    JavaVM* const   mVM;
    jobject const   mObject;
;

(1)成员标量:mVM:就是JavaVM;mObject:是关键,传入的就是Binder的stub对象。

(2)构造和析构函数完成了对mObject的实例化与释放。此外构造函数中还考虑了jnienv的多线程问题。

(3)onTransact方法调用的是mObject的execTransact方法,由此可以联想到这里的mObject就是stub。

(4)NewGlobalRef与DeleteGlobalRef成对出现。申请了就要释放,防止内存泄漏。

那么mObject究竟是在什么地方传入的呢?答案是:ibinderForJavaObject

sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj)

    if (obj == NULL) return NULL;

    if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) 
        JavaBBinderHolder* jbh = (JavaBBinderHolder*)
            env->GetLongField(obj, gBinderOffsets.mObject);
        return jbh != NULL ? jbh->get(env, obj) : NULL;
    //stub

    if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) 
        return (IBinder*)
            env->GetLongField(obj, gBinderProxyOffsets.mObject);
    //proxy

    ALOGW("ibinderForJavaObject: %p is not a Binder object", obj);
    return NULL;

        在stub的流程中,先获通过gBinderOffsets的mObject变量获得JavaBBinderHolder的指针,然后调用其get方法,将stub保存在JavaBBinder中的mObject成员变量中。而ibinderForJavaObject则是有android_os_Parcel_writeStrongBinder传入参数

static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr, jobject object)

    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) 
        const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
        if (err != NO_ERROR) 
            signalExceptionForError(env, clazz, err);
        
    

简单的说就是如下关系:

          gBinderOffsets.mObject--->JavaBBinderHolder.get()--->JavaBBinder.mObject--->stub

       分析了很多,binder的jni还是挺复杂的,建议先了解一下binder机制,这里将总结一下:每个线程都只有一个JNIEnv,在反调用java的方法是注意JNIEnv需要通过JavaVM进行转换。

      在native method中,JNIEnv作为第一个参数传入。那么在JNIEnv不作为参数传入的时候,该如何获得它?JNI提供了两个函数:(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)和(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)。两个函数都利用JavaVM接口获得JNIEnv接口。GetEnv是1.2版本以后的新增接口。


2、System.loadLibrary

        这是应用编程使用native方法的惯用方法。下面以MediaPlayer.java为例。

public class MediaPlayer
    static 
        System.loadLibrary("media_jni");
        native_init();//关键
    

    private static native final void native_init();
    ...
        简单的说,System.loadLibrary()的作用就是调用相应库中的JNI_OnLoad()方法。
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)

    JNIEnv* env = NULL;
    jint result = -1;

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

    if (register_android_media_MediaPlayer(env) < 0) //注册native_init()方法。
        ALOGE("ERROR: MediaPlayer native registration failed\\n");
        goto bail;
    

    if (register_android_media_MediaScanner(env) < 0) 
        ALOGE("ERROR: MediaScanner native registration failed\\n");
        goto bail;
    

   ...

    /* success -- return valid version number */
    result = JNI_VERSION_1_4;

bail:
    return result;
显然,这些也仅仅是注册native方法而已。而值得关注的是MediaPlayer.java中的一个native方法:native_init()。

struct fields_t 
    jfieldID    context;
    jfieldID    surface_texture;

    jmethodID   post_event;

    jmethodID   proxyConfigGetHost;
    jmethodID   proxyConfigGetPort;
    jmethodID   proxyConfigGetExclusionList;
;
static fields_t fields;

static void
android_media_MediaPlayer_native_init(JNIEnv *env)

    jclass clazz;

    clazz = env->FindClass("android/media/MediaPlayer");
    if (clazz == NULL) 
        return;
    

    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) 
        return;
    

    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) 
        return;
    

    fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
    if (fields.surface_texture == NULL) 
        return;
    

    clazz = env->FindClass("android/net/ProxyInfo");
    if (clazz == NULL) 
        return;
    

    fields.proxyConfigGetHost =
        env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;");

    fields.proxyConfigGetPort =
        env->GetMethodID(clazz, "getPort", "()I");

    fields.proxyConfigGetExclusionList =
        env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
       也是使用静态结构体在注册native方法之前,获取并保存变量或方法的ID。同样的本质,不同的手法而已。这种编程手法是值得学习的:注册一个native_init()方法,然后在该方法中获取并保存变量或方法的ID,以提高效率。


3、测试

           本文写了一个测试程序,用于验证本文的观点。

(1)试验说明:java调用native函数getInfo(Infor *pInfor),native将获取到的数据传给java空间,以便java空间使用。Infor结构体如下:

typedef struct 
	unsigned char A[2];  
	unsigned char B[12];  
	unsigned char C[4];  
	unsigned char D[9]; 
	unsigned char E; 
	unsigned char F[4];  
	unsigned char G[4]; 
	unsigned char H[4];  
	unsigned char E[16]; 
	unsigned char I[64];
	unsigned char T[7];
	
 Infor;  
         jni 代码就不贴了。
(2)实验数据如下(单位:s)。其中,old是在getInfo函数中获取ID,执行完函数耗时情况;new是在native_init()中获取ID的情况。显然,速度提升了0.15s左右。

  1 2 3 4 5 6 7 8 average
old 0.365 0.312 0.306 0.299 0.327 0.472 0.299 0.301 0.335125
new 0.182 0.184 0.178 0.204 0.181 0.182 0.194 0.18 0.185625


4、总结

(1)获取并保存变量或方法的ID,以提高效率。
(2)在native method中,JNIEnv作为第一个参数传入。那么在JNIEnv不作为参数传入的时候,JNI提供了两个函数:(*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL)和(*jvm)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_2)。两个函数都利用JavaVM接口获得JNIEnv接口。GetEnv是1.2版本以后的新增接口。


如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!】

以上是关于Android JNI 高级编程的主要内容,如果未能解决你的问题,请参考以下文章

Android JNI&NDK编程小结及建议

超全Android JNI&NDK编程总结

Android JNI编程——使用AndroidStudio编写第一个JNI程序

Android中的JNI和NDK编程实践

Android中的JNI和NDK编程实践

Android JNI编程——JNI概念以及C语言Dev-C++开发环境搭建编写HelloWorld