模拟Android系统Zygote启动流程

发布于:2024-04-19 ⋅ 阅读:(28) ⋅ 点赞:(0)

版权声明:本文为梦想全栈程序猿原创文章,转载请附上原文出处链接和本声明

前言:

        转眼时间过去了10年了,回顾整个10年的工作历程,做了3年的手机,4年左右的Android指纹相关的工作,3年左右的跟传感器相关的工作,其实,我个人还是想了解Android底层的相关知识,目前对于啥都了解一点,都不太深入的,让我很焦虑,记录自己学习的相关知识,这个系列的文章思路是抽丝剥茧,记录Android相关知识的关键部分,目前是从Android8.0开始。

一.前置学习:了解Android的启动流程

文笔不好,只是记录下来给自己回顾,或者分享给志同道合的人一起交流跟学习;学习本文章,你需要了解Android基本的启动流程,在这里不讲,需要自行去查看相关的资料

二.Android如何进入Zygote的java代码

这个是Framework的模拟相关代码,基本上是一比一还原

通过相关Android启动流程的相关知识,可以清楚知道,系统在执行执行并解析init.rc的过程中,启动了路径为/system/bin/app_process的可执行文件;我们知道,在Linux中,死的可执行文件称为程序,活的可执行文件称为进程;可执行文件的程序入口是main函数(这个不是很严谨)

app_process.cpp文件

int main(int argc, char* const argv[])
{

    char* argBlockStart = "test";
    size_t argBlockLength = 5;

    AppRuntime runtime(argBlockStart, argBlockLength);
    runtime.start("com.huahua.moon.android.ZygoteInit", true);

    return 0;
}

所以根据目前的main函数,需要去看下runtime.start方法,下面是app_process.cpp的整个文件内容

#include "AndroidRuntime.h"

#include <stdlib.h>

#include <stdio.h>
#include <unistd.h>

class AppRuntime : public AndroidRuntime
{
public:
    AppRuntime(char* argBlockStart, size_t argBlockLength)
        : AndroidRuntime(argBlockStart, argBlockLength)
    {

    }

};

int main(int argc, char* const argv[])
{

    char* argBlockStart = "test";
    size_t argBlockLength = 5;

    AppRuntime runtime(argBlockStart, argBlockLength);
    runtime.start("com.huahua.moon.android.ZygoteInit", true);

    return 0;
}

可以看到,runtime是AppRuntime类的对象,这个类继承自AndroidRuntime类,AppRuntime类中没有看到start函数的相关声明跟定义,所以,这个肯定是在其父类AndroidRuntime中声明跟定义的所以需要看AppRuntime跟AndroidRuntime相关的类;

这个是AndroidRuntime.h文件

#include <stddef.h>
#include <stdlib.h>

#include <jni.h>

#include <vector>

using namespace std;
using std::vector;

class AndroidRuntime
{
public:
    AndroidRuntime(char* argBlockStart, size_t argBlockSize);
    virtual ~AndroidRuntime();

    void addOption(const char* optionString, void* extra_info = NULL);

    void start(const char* className, bool zygote);

    void exit(int mode);

    static AndroidRuntime* getRuntime();

    static char* toSlashClassName(const char* className);

private:
    int startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote);

    vector<JavaVMOption> mOptions;
    char* const mArgBlockStart;
    const size_t mArgBlockLength;
    static JavaVM* mJavaVM;
};

这个是AndroidRuntime.cpp部分文件

void AndroidRuntime::start(const char* className, bool zygote)
{
    printf("AndroidRuntime start className = %s, zygote = %d \n", className, zygote);

     JNIEnv* env;
     if (startVm(&mJavaVM, &env, zygote) != 0)
     {
        return;
     }

     jstring classNameStr = env->NewStringUTF(className);
     char* slashClassName = toSlashClassName(className);

     printf("JavaVM slashClassName '%s'\n", slashClassName);
     jclass startClass = env->FindClass(slashClassName);
     if (startClass == NULL)
     {
        printf("JavaVM unable to locate class '%s'\n", slashClassName);
     }
     else
     {
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V");
        if (startMeth != NULL)
        {
            jobjectArray strArray;
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        }
     }
}

所以上面app_process.cpp的main函数中,最终调用的是AndroidRuntime.cpp中的start函数,传递的参数className = ("com.huahua.moon.android.ZygoteInit"  zygote = true;因为我这篇文章是模拟,只是概述关键点,所有有其他的很多东西没有提及到;这里跟我上一次的文章是有联系的,具体也可以参考我上篇博客linux运行可执行文件,通过c语言调用java的main方法

上面的代码,主要是一下几点:

  • 启动JVM虚拟机
  • 转换class Name的格式
  • 找到对应的class
  • 获取对应class的main函数
  • 执行main函数

这里有一个细节,就是我们传递的进来的class名是"com.huahua.moon.android.ZygoteInit",这个名字是不符合JAVA相关的JNI规范的,需要修改成"com/huahua/moon/android/ZygoteInit"这种名字

startVm函数是启动java虚拟机

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{
    JavaVMInitArgs initArgs;

#if 0
    addOption("-Djava.class.path=.");

    initArgs.version = JNI_VERSION_1_4;
    //initArgs.options = mOptions.editArray();
    //initArgs.nOptions = mOptions.size();
    initArgs.ignoreUnrecognized = JNI_FALSE;
#endif


    JavaVMOption options[1];
    options[0].optionString = "-Djava.class.path=.";

    initArgs.version = JNI_VERSION_1_4;
    initArgs.nOptions = 1;
    initArgs.options = options;

    if (JNI_CreateJavaVM(pJavaVM, (void **)pEnv, &initArgs) < 0)
    {
        printf("JNI_CreateJavaVM failed \n");
        return -1;
    }
    else
    {
        printf("JNI_CreateJavaVM create success  \n");
    }

    return 0;
}

toSlashClassName函数是把"com.huahua.moon.android.ZygoteInit"转化成"com/huahua/moon/android/ZygoteInit"这种格式

char* AndroidRuntime::toSlashClassName(const char* className)
{
    char* result = strdup(className);
    for (char* cp = result; *cp != '\0'; cp++) {
        if (*cp == '.') {
            *cp = '/';
        }
    }
    return result;
}

三.模拟结果

通过执行app_process 可执行文件, 输出了java 代码的main函数中的系统输出,证明了Android 进入了Zygote的java代码

ZygoteInit.java 的代码如下图所示

三.注意问题

模拟代码是一个稍微完整的工程代码,本篇文章只是包含了其中的一部分

四.如何编译测试

1.用本工程的java代码编译出class文件,使用java命令或者intellij idea 工具编译都行

2.使用本工程的cpp部分代码,编译出app_process 可执行文件,目前本工程的构建工具是Cmake

3.拷贝到out目前, 请注意目录结构

五.思考问题

真的只有面向对象语言有封装的特性吗? 

此工程中的cpp代码从封装性,跟C语言的代码对比而言.(ps:在这个头文件中竟然能看到类的相关私有数据成员)