按下电源键到启动Home应用过程详解(二)

一:前言
上一章节主要讲了Android系统架构和下载源码的过程,这节主要介绍一下JNI的知识

二:基础知识补充
1)JNI: Java Native Interface,是Java平台定义的一套本地编程接口,实现Java代码和本地代码的互相调用
2)JNI所处结构
Jni所处结构
这张图重点注意JNI是通过Dalvik虚拟机实现与应用层和应用框架层的交互,这和虚拟机的函数注册方式和实现方式是密不可分的;
3)JNI的注册方式
3.1)静态注册,这也是我们经常用的方法
-首先创建Java类,声明Native方法
-其次使用Javah命令生成C/C++的头文件
-最后实现Native方法
静态注册方法存在如下缺点
-得使用javah命令生成c/c++头文件
-javah生成的JNI层函数名特别长,和Java类有直接对应关系
-初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,影响运行效率
3.2)动态注册,Android源码里面采用这种方式
举个例子,在App中我们经常调用

Log.d(mTag,message);

但是我们的App中并没有load相关的so,其实在Android启动的过程中,系统采用动态注册的方式已经将相关SO加载起来,并实现了java层和native层方法的对应;

log.d的方法调用如下方法

    public static int d(String tag, String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    }
    /** @hide */ public static native int println_native(int bufID,
            int priority, String tag, String msg);

现在我们分析下这个native方法是如何进行动态注册和实现的
打开android_util_Log.cpp文件可以看到该方法的实现

/*
 * In class android.util.Log:
 *  public static native int println_native(int buffer, int priority, String tag, String msg)
 */
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
    const char* tag = NULL;
    const char* msg = NULL;
    ......
}

c++层的实现是如何和Java层进行关联的尼?
android_util_Log.cpp中有如下数组

/*
 * JNI registration.
 */
static const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
    { "logger_entry_max_payload_native",  "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};

该数组描述了java层的方法,方法签名和native层对应的方法; 该数组存储的类型为JNINativeMethod
这个定义可以在jni.h文件中找到

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
}

正如上面所述,该结构体保存的是声明函数和实现函数的对应关系;

那这种对应关系是如何实现的尼?这时候虚拟机就出场了,我们返回android_util_Log.cpp中查看如下方法

int register_android_util_Log(JNIEnv* env)
{
    jclass clazz = FindClassOrDie(env, "android/util/Log");
    ......
    return RegisterMethodsOrDie(env, "android/util/Log", gMethods, NELEM(gMethods));
}

通过该方法便可以告诉虚拟机声明函数和实现函数的关系,这样java层的方法便可以顺利找到底层函数的实现;

现在我们分析一下register_android_util_Log方法,该方法调用RegisterMethodsOrDie方法,这个方法在core_jni_helpers.h中

static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
                                       const JNINativeMethod* gMethods, int numMethods) {
    int res = AndroidRuntime::registerNativeMethods(env, className, gMethods, numMethods);
    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
    return res;
}

该方法将调用AndroidRuntime.cpp的registerNativeMethods方法

/*
 * Register native methods using JNI.
 */
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

在JNIHelp.CPP中我们可以查看到jniRegisterNativeMethods的实现

extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
    ...
    if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) <
    return 0;
}

从中我们可以最终调用的是JNIEnv的RegisterNatives方法完成声明函数和实现函数的绑定关系;顺利将gMethods里面的方法传递给虚拟机

那什么时候开始调用register_android_util_Log的方法,java层可以不用load so文件,直接调用log.d的方法?

在系统启动阶段会调用AndroidRuntime.cpp的startReg完成底层native方法的注册

/*
 * Register android native functions with the VM.
 */
/*static*/ int AndroidRuntime::startReg(JNIEnv* env)
{
   ......
    androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
    env->PushLocalFrame(200);
    if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
        env->PopLocalFrame(NULL);
     http://book.51cto.com/art/201311/418570.htm    return -1;
    }
    .......
    env->PopLocalFrame(NULL);
    return 0;
}

在上面的分析中,我们看到了JNIEnv这个指针,现在我们来分析一下这个指针
他的作用:调用底层JNI函数,访问虚拟机,进而操作Java对象(即调用Java方法),
在Jni.h中

#ifdef __cplusplus   //c++
typedef JNIEnv_ JNIEnv;
#else               //c
typedef const struct JNINativeInterface_ *JNIEnv;
#endif

可以看出在c++中JNIEnv实际上是JNIEnv_ ;而在c中JNIEnv实际上是JNINativeInterface_ ,
其中JNIEnv_的结构体为

struct JNIEnv_ {
    const struct JNINativeInterface_ *functions;
#ifdef __cplusplus

    jint GetVersion() {
        return functions->GetVersion(this);
    }
    ...

可以看出JNIEnv_ 是对JNINativeInterface_ 的封装,并调用了JNINativeInterface_的方法

struct JNINativeInterface_ {
    void *reserved0;
    void *reserved1;
    void *reserved2;

    void *reserved3;
    jint (JNICALL *GetVersion)(JNIEnv *env);

    jclass (JNICALL *DefineClass)
      (JNIEnv *env, const char *name, jobject loader, const jbyte *buf,
       jsize len);
    jclass (JNICALL *FindClass)
      (JNIEnv *env, const char *name);
      .......

从中可以看出如果是C++代码,我们可以直接使用env->findclass的形式完成函数调用,因为c++中完成了JNINativeInterface_ 的封装
而在c中,我们需要先对env进行解引用,才能调用对应的函数,即(*env)->findclass

注意点:JNIEnv只在当前线程中有效,本地方法中不能讲JNIEnv从一个线程传到另外一个线程;相同的java线程中多次调用同一个本地方法,传递的JNIEnv是一样的;一个本地方法可以被不同的java线程所调用,接收的JNIEnv是不同的;

java有很多重载方法,gMethods是如果区分的尼?这时候就要说到方法的签名和类签名了
方法的签名其规则为:(参数1类型签名参数2类型签名…参数N类型签名)返回值类型签名;
类的签名规则为:“L+全限定类名+;”

举个例子说明签名
java层: int add(int a,String b)
则签名为:(ILjava/lang/String;)I

首先Java的native方法中,传递的是Java的参数类型,这个只有通过虚拟机的转换为JNI类型,才能被JNI方法识别;
如Java类型的boolean转为为JNI类型为jboolean

其次关于native方法中参数的解释,以android_util_Log_println_native为例
第一个参数为JNIEnv的指针
第二个参数会根据java层是否为静态方法而有所不同,当java方法为静态的时候,则这个参数是对Java类的引用,当java方法为非静态的时候,则这个参数是该Java对象的引用;

如上所述,通过第二个参数我们便可以操作Java对象以及Java对象的方法,简单的来说就是利用JNI提供的类和对象的操作函数去实现;

三:重要知识点:全局引用,弱全局引用,局部引用
虚拟机中采用引用计数的方式来判断是否可以回收一个对象,当java层调用一个native的方法的时候,不同的引用方式将对java对象生命周期产生不同影响;然后通过全局变量或者静态变量的方式,是无法让对象实现计数加一的目的的;

-全局引用:可以增加引用计数,通过JNI的NewGlobRef方法创建;通过DeleteGlobalRef释放;如果不释放,将永远不会被垃圾回收;生命周期到显示释放
-弱全局引用:不能增加引用计数;通过JNI的NewWeakGlobalRef创建,通过DeleteWeakGlobalRef释放,生命周期到显示释放;其对应的Java对象生命周期取决于虚拟机,弱全局引用没有被释放,其引用的Java对象可能已经被释放;可以通过IsSameObject来判断弱引用指向的对象是否被回收;
-局部引用:可以增加引用计数;作用访问为本线程,生命周期为一次native调用;局部引用只在创建它的native方法中有效,当方法返回后,被虚拟机回收;

全局引用,默认不能超过2000个,否则会报内存不足的警告;logcat中有GREF over的提示,其中GREF便是全局引用;

局部引用,默认不能超过512个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值