1.main函数

一般来说,main函数被认为是咱们编写程序的起点,无论是C还是Java,咱们要接收外界的输入,一般是在命令行中,在程序的后边跟上咱们的main args参数:

wc a.txt // 运用wc 统计a.txt文本文件的行数
git commit -am "xxxx" // 运用git程序来commit一个内容,其间的{commit}/{-am}/{feat: xxx}都是参数

当然,Java会比较特别,一些在static代码块中的语句会先于main函数履行,这是JVM特有的类加载机制的特性。

而Android SDK中,为咱们供给的一切套件,包含早期的Activity、Fragment或者是现代的Lifecycle、ViewModel等等,这些都是只供给了一个根底的功用切片,咱们面向这个切片便可完成绝大多数功用。咱们在Activity中编写业务的时分,绝大多数日常开发场景下咱们并不需要去关怀main函数是怎么履行到Activity的onCreate,也不必特别去关怀main函数是怎么履行到办理Lifecycle的代码的,但这并不意味着main函数不存在。

当Android程序运行时,它一直存在于JVM的调用栈傍边,不过,你会发现有两个main函数:

Android消息机制(二)两个main函数与App的启动

2. 程序是从哪来的?

咱们大致能够将Android看做是一个Linux操作系统。而一个一般App已然是Android进程,那必定是运行在JVM中的程序,而JVM又是一个Linux程序,这样一来就形成了一种对应关系:

Android App <-> ART虚拟机 <-> Linux进程

所以,Android App的发动,必定和Linux进程的发动有关。

2.1 fork机制

fork机制咱们想必都有所了解,作为Linux最基本的一个操作之一,fork供给了一种创立子进程的方法,一般状况,咱们的程序是顺序履行的,而fork本意是叉子,也意味着分叉,言下之意便是当咱们的程序遇到fork调用时,当时的顺序履行流会在一个新的进程中,产生一个新的、相同的履行流,例如:

void main(){
    fork();
    int x = 10;
}

咱们的程序履行到fork之后,主程序(pid = 1233)在履行到fork之后,当时的履行流会被copy一份,并在一个新的进程中继续运行。假定新的进程的pid = 1234,那么此刻操作系统中会有两个main.c对应程序履行流的进程在履行:

Android消息机制(二)两个main函数与App的启动

咱们能够借助于fork函数的返回值来区别哪一个是原本的main进程(pid = 1233),哪一个是新仿制出来的子进程1234,由于在原main进程调用fork时,fork函数会在源程序中返回子进程的pid = 1233,也便是fork的返回值为1233,而子进程由于完整地copy了一份主程序的履行流,相同地,它也会有一个fork的返回值,但在子进程中,fork的返回值为0,在上述的程序流图中,咱们能够知道,左侧的是main进程,而右侧的是main fork出来的子进程。

2.2 懒加载与CopyOnWrite

Linux的fork机制相比较于传统的fork机制有一个比较特别的点,便是Linux并不会立即将完整的源程序的内存副本仿制到新的内存区域,而是采用写时仿制技术,即既有的“读”操作悉数映射到源程序的内存地址上,而只有“写”操作才会触发一个新的内存页写入,将新的内容写入到新进程的内存空间中,以保证新旧程序有完整又独立的内存空间。

这样的好处是清楚明晰的,一是fork创立子进程的时分,并不会涉及到很多的内存仿制,即使是占用上GB的程序程序内存,运用fork创立子进程,也不会在瞬间产生很多的内存仿制操作而消耗掉系统资源。其二是咱们程序运用的绝大多数的同享库、资源能够轻松地同享(比方初始进程加载了一堆资源文件,这些资源文件是可读的,这样一来其余的程序只需要经过fork机制树立的映射关系就能够访问到初始进程的资源文件,而不有必要去触发屡次的IO去文件系统中再次加载同一份资源文件)。

3. Android App的main函数

已然咱们知道,Android程序也是存在main函数的,同时Android程序也是经过「fork」创立出来的。 已然是fork,那必定有一个fork的源程序,它便是Android开发中,著名的:Zygote进程。Zygote便是Init进程经过解析init.rc文件之后,发动的一个进程,他有非常多的「特点」,可是咱们此刻只需要记住一个:即它是榜首个Java进程

init进程是Linux的榜首个用户空间的进程,而Zygote则是榜首个Java进程。

类比着,咱们也能够看看Android 是怎么实现可交互程序的,首先咱们能够试着做一件事,找到Android App的Main函数,由于咱们知道,可交互程序大概率是运行在一个循环里边的,所以咱们能够随意地,在任何或许停下来的地方打上断点,程序调用栈的最开端的栈帧必定便是这个循环了:

Android消息机制(二)两个main函数与App的启动

你会发现,有两个main函数,一个是ZygoteInit,另一个则是ActivityThread

3.1 榜首个main函数

前面提到了,Zygote进程是系统重建的榜首个Java进程。所以,Zygote进程的发动必定会去加载虚拟机。而其他的Java虚拟机进程,都需要经过fork Zygote进程来创立,由于fork操作的特性,会仿制Zygote进程的内存空间(包含资源数据、代码,乃至是PC指针),于是乎在子进程中也有一份如出一辙的Zygote虚拟机,子进程会从fork调用的位置接着往下走。

所以前面提到的,怎么区别fork出来的是子进程还是父进程是非常有必要的。

Android消息机制(二)两个main函数与App的启动

咱们来看看ZygoteInit.java中,它的main函数的代码,这个循环的主要任务,便是经过Socket链接,等候从外部传来的创立子进程的请求,然后经过fork创立子进程,然后封闭掉子进程的Socket链接,完成子进程的创立,然后自己进入下一次循环。

public static void main(String argv[]) {
    ZygoteServer zygoteServer = null;
    // ……
    Runnable caller;
    try {
        // ……
        // 预加载资源
        preload(bootTimingsTraceLog);
        // ……
        // 创立system_server
        if (startSystemServer) {
            Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
            if (r != null) {
                r.run();
                return;
            }
            // ……
            // 发动循环,不断地去查询Socket
            caller = zygoteServer.runSelectLoop(abiList);
        }
    } catch (Throwable ex) {
        Log.e(TAG, "System zygote died with exception", ex);
        throw ex;
    } finally {
        if (zygoteServer != null) {
            zygoteServer.closeServerSocket();
        }
    }
    // ……
}

要点在于runSelectLoop(abiList)代码中,内部是一个循环:

Runnable runSelectLoop(String abiList) {
    // ……
    while (true) {
       try {
          ZygoteConnection connection = peers.get(pollIndex);
          final Runnable command = connection.processOneCommand(this);
          // ……
}

其间的processOneCommand处理了具体的fork细节:

if (pid == 0) {
    // in child
    zygoteServer.setForkChild();
    zygoteServer.closeServerSocket();
    IoUtils.closeQuietly(serverPipeFd);
    serverPipeFd = null;
    return handleChildProc(parsedArgs, descriptors, childPipeFd,
            parsedArgs.mStartChildZygote);
} else {
    // In the parent. A pid < 0 indicates a failure and will be handled in
    // handleParentProc.
    IoUtils.closeQuietly(childPipeFd);
    childPipeFd = null;
    handleParentProc(pid, descriptors, serverPipeFd);
    return null;
}

当然,ZygoteInit的main函数还有其他的一些重要作用,比方它会去创立system_server、做资源的预加载等等。

3.2 第二个main函数

接下来,咱们看看ActivityThread中main方法中的循环:

public static void main(String[] args) {
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    // ……
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

咱们能够看到,最终一行抛出了个异常,所以一般状况下不会走到最终一行。

你能够想一下如果走到这一行,一般来说便是程序直接溃散,并给一个中止运行的提示。可是如果这个异常被线程catch住了会发生什么?

而且咱们看到了咱们的老熟人:Looper。说明主线程的循环必定是在这里初始化的,而咱们知道Looper.loop,本质上也是一个循环:

@SuppressWarnings("AndroidFrameworkBinderIdentity")
public static void loop() {
    // ……
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

loopOnce里边无非便是循环一次的操作:

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    ……
}

具体干了什么不是咱们今天关怀的,可是咱们能够看到:榜首行的mQueue.next()后边的注释写明晰:或许会阻塞,这其实就和Shell里边的getcmd()readLine()是一个意思。无非便是等等外部输入,只不过Shell的事件输入一般是从stdin中输入的,而这里的输入则不必定,Shell输入的是字符,不同的字符构成不同的指令;而Handler这里却并不是,它以Message为单位,接受来自外界的输入。

4. 总结

上面的内容,咱们大致上介绍了一下Linux的fork机制、Android的Java层的两个main函数别离的作用,从main函数动身,介绍了循环在Android音讯机制中的作用。

榜首个循环,运用Socket监听,不断地去监听其它程序的请求,如果有其他程序需要创立子进程,那么就从的ZygoteInit中创立一个子进程,然后封闭掉原先用作监听创立请求的Socket,然后发动ActivityThread的main函数,第二个循环,至此咱们了解的Message机制开端收效。