Android从点击应用图标到首帧展示的过程

本文参考掘金-志跃同学的博文 面试?看完这篇就够了-深入分析从点击应用图标到应用界面展示 写成,强烈推荐这位技术博主的其它文章,简洁干练,值得阅读

从点击桌面图标到应用首帧展示

这里分析冷启动的过程,从点击桌面icon到APP的首个页面展示出来,这里面根据主体不同,可以分为4个阶段:

  1. 阶段一:应用进程启动
  2. 阶段二:应用进程初始化
  3. 阶段三:Activity启动
  4. 阶段四:View绘制

阶段一:应用进程启动

该过程涉及到4个进程之间的通信:

  • Launcher进程,桌面本身也是一个应用程序,拥有自己的进程,在手机开机时启动
  • system_server进程,AMS就是运行在这个进程中,在手机开机时启动,由zygote孵化出
  • zygote进程,孵化器,所有应用进程都由它fork出来,在手机开机时由Init进程创建
  • 应用进程

它们的作用顺序如下:

  1. 当我们点击桌面应用图标时,Launcher进程通过Binder向AMS发起startActivity请求
  2. AMS收到请求时,处理intent信息,通过ProcessRecord判断应用进程是否存在,如果不存在,则通过socket IPCzygote进程发送创建新进程的请求
  3. zygote收到请求,fork出新的应用进程,应用进程加载ActivityThread类,并调用ActivityThread.main()函数,是应用进程的入口。到此完成应用进程启动

阶段二:应用进程初始化

初始化阶段,主要做了三件事:

  • 初始化并开启主线程Looper循环
  • 初始化Application
  • 和AMS建立Binder通信

这三件事都是在ActivityThread.main()函数中进行的。

ActivityThread.java

java 复制代码
public static void main(String[] args) {
    Looper.prepareMainLooper(); // 初始化主线程Looper
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    Looper.loop(); // 开启主线程消息循环
}

上述代码初始化主线程Looper并开启消息循环,在attach()中进行Application的初始化工作。

java 复制代码
final ApplicationThread mAppThread = new ApplicationThread();

private void attach(boolean system, long startSeq) {
    // 传入false代表非系统进程
    if (!system) {
        // 将ApplicationThread传入AMS,建立应用-AMS之间的Binder通信通道
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }
}

IActivityManager.attachApplication()中,完成应用进程向SystemServer注册的过程。

ApplicationThread.java

java 复制代码
private class ApplicationThread extends IApplicationThread.Stub {
    @Override
    public final void bindApplication(...) {
        sendMessage(H.BIND_APPLICATION, data);
    }
}

ApplicationThread类实现了Stub接口,表明它适用于跨进程通信的AIDL生成的接口文件,Stub是由服务端(这里是应用进程)实现得,客户端是运行在system_server中的AMS。

通过ActivityThread.attach(),AMS得到了ApplicationThreadBinder对象,可以通过它与应用进程进行交互,调用应用的各项生命周期方法。ApplicationThread是AMS与ActivityThread沟通的桥梁。

AMS调用ApplicationThread接口,后者会把任务和数据通过消息机制抛到主线程处理,H就是ActivityThread主线程Handler,用于接收AMS过来的任务。

H.BIND_APPLICATION消息的处理,最终落到ActivityThread.handleBindApplication()函数。

ActivityThread.java

java 复制代码
private void handleBindApplication(AppBindData data) {
    try {
        app = data.info.makeApplication(data.restrictedBackMode, null); // 实例化Application,内部调用了Application.attachBaseContext()
        if (!data.restrictedBackupMode) {
            if (!ArrayUtils.isEmpty(data.providers)) {
                installContentProviders(app, data.providers); // 实例化ContentProviders,内部调用了其onCreate()
            }
        }
        try {
            mInstrumentation.callApplicationOnCreate(app) // 内部调用Application.onCreate()
        }
        ...
}

分析了上面的代码,就解释了为什么是Application.attachBaseContext() -> ContentProvider.onCreate() -> Applicatoin.onCreate() 这个执行顺序了。

阶段三:Activity启动

应用进程启动之后,AMS通过Binder调用,借助ApplicationThreadActivityThread的主线程发送EXECUTE_TRANSACTION消息,来执行Activity的生命周期。最终会在主线程执行handleLaunchActivity()handleResumeActivity()等,在其内部调用ActivityonCreate()onResume()等生命周期方法。

ActivityThread.java

java 复制代码
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
    final Activity a = performLaunchActivity(r, customIntent);
    return a;
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    Activity activity = null;
    try {
        // 通过反射创建Activity对象
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    }
    ...
    try {
        // 调用Activity.onCreate()
        if (r.isPersistable()) {
            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
        } else {
            mInstrumentation.callActivityOnCreate(activity, r.state);
        }
    }
    ...
    return activity;
}

这里做了两件事情:

  • 通过反射实例化Activity
  • 调用其onCreate()

接下来是handleReusmeActivity()函数。

java 复制代码
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest, boolean isForward, String reason) {
    // 内部执行onResume(),执行成功后返回true
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }
    // 获取decorView
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    
    ViewManager wm = a.getWindowManager();
    // 将decorView添加到WM
    wm.addView(decor, l);
}

WindowManager.addView()内部会把任务交给真正的执行者(被代理对象)WindowManagerGlobal

WindowManagerGlobal.java

java 复制代码
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) {
    // 实例化ViewRootImpl,它是View与Window交互的桥梁
    root = new ViewRootImpl(view.getContext(), display);
    
    try {
        // 核心代码,调用setView()添加View
        root.setView(view, wparams, panelParentView, userId);
    }
    ...
}

ViewRootImpl.setView()最核心的代码是调用了requestLayout(),触发View的绘制过程。

阶段四:View绘制

前文提到在Activity创建过程中,AMS通过Binder调用到Activity.onCreate()Activity.onResume(),在这两个函数里会进行UI的创建和渲染。

通常我们在onCreate()中会setContentView(layoutResId)

这部分代码在不同版本的SDK上差异较大,本文以下代码基于Android 13(SDK 33)分析

java 复制代码
// AppCompatActivity.java
public void setContentView(@LayoutRes int layoutResID) {
    initViewTreeOwners();
    getDelegate().setContentView(layoutResID);
}

getDelegate()返回的是AppCompatDelegateImpl

java 复制代码
// AppDelegateImpl
@Override
public void setContentView(int resId) {
    // 实例化DecorView
    ensureSubDecor();
    // DecorView = title + content
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    // 将自定义布局添加进content
    LayoutInflater.from(mContent).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

DecorView

是一个ViewGroup,继承自FrameLayout,包含titlecontent两部分。它是Window与应用UI交互的桥梁,是整个Activity的根布局。

执行完setContentView()后,完成DecorView的创建,并且把自定义布局加入到了它的content中。但此时View只是走完了创建,并没有真正绘制,并不可见。只有完成绘制后,才可以获取其真正宽高。

requestLayout()

AMS借助ApplicationThread,向ActivityThread发起绘制请求,会执行到ActivityThread.handleResumeActivity()函数,最终调用到ViewRootImpl.setView(),执行requestLayout()发起绘制流程。

ViewRootImpl

是Window和View沟通的桥梁,它实现了ViewParent接口,其构造函数中有两个成员变量值得关注:

java 复制代码
public final class ViewRootImpl implements ViewParent, View.AttachInfo.callbacks... {
    public ViewRootImpl(@UIContext context, Display display, IWindowSession session, boolean usefChoreographer) {
        // 保存当前线程(主线程)
        mThread = Thread.currentThread();
        // 编舞者,屏幕刷新机制的关键
        mChoreographer = usefChoreographer ? Choreographer.getSfInstance() : Choreographer.getInstance();
}

requestLayout()主要做了2件事:

  • 检查线程,保证当前线程就是创建ViewRootImpl的线程
  • 调用scheduleTraversals()

在View.attach()完成之前,可以在非主线程更新UI

scheduleTraversals()

java 复制代码
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        // 1.发送同步屏障
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 2.向Choreographer post 一个 callback
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyrendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        // 3.在callback中执行doTraversal
        doTraversal();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 4.移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        // 5.onMeasure, onLayout, onDraw
        performTraversals();
        ...
    }
}
  • 关于消息同步屏障,详细介绍可以移步到 Android 消息机制,这里简单理解一下其目的就是为了保证渲染任务得到高优先级执行
  • Choreographer是屏幕刷新机制的关键实现,详细介绍可以移步到 屏幕刷新机制,这里也简单了解一下:往 Choreographer 里面 postCallback目的是为了在系统下一帧渲染时间到来的时候执行Runnable中的逻辑,对应上面也就是执行 performTraversals()

performTraversals()

最关键的作用是调用performMeasure()performLayout()performDraw(),对应View内部的onXXX()函数。

View的三步绘制流程,可以参考以下这张图:

全部流程精简总结

  1. Launcher进程通过Bindersystem_server进程的AMS发送启动Activity请求
  2. AMS判断如果应用进程不存在,通过socket通知zygote进程,fork出应用进程
  3. 应用进程启动以后,调用ActivityThread.main()启动消息循环,建立与AMS之间的Binder通信
  4. AMS通过Binder调度ActivityonCreate()onResume()生命周期
  5. onCreate()中通过setContentView()传入自定义布局构建以DecorViewRootView
  6. onResume()后通过Choreographer屏幕刷新机制,开启View的绘制流程,执行onMeasure()onLayout()onDraw()

附录

什么是fork进程

当一个进程调用fork()时,会创建一个新的子进程,它是父进程的副本,包括代码、数据、堆栈等。在fork()之后,父进程和子进程将并发继续执行之前在父进程中相同的代码。

父进程在fork()后会获得子进程的pid

因此,在fork()后通常伴随着pid的检查,如果pid0(即子进程的pid),表明当前代码执行于父进程中。如果pid=0,说明当前正位于子进程。

以下是Demo:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();
    if (pid < 0) {
        // fork failed
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // child process
        printf("I am the child process, my PID is %d\\n", getpid());
    } else {
        // parent process
        printf("I am the parent process, my PID is %d and my child's PID is %d\\n", getpid, pid);
    }
    return 0;
}

zygote的作用

Zygote作为应用程序进程的孵化器,在Init进程中创建它,它预加载了很多常用的类和资源,位应用程序进程提供一个初始化好的运行环境。当需要创建新的应用程序进程时,系统通过zygote进程来fork出新的应用程序进程,这么做的主要原因是为了提高应用程序的启动速度和资源共享,简化启动流程。、

  • 提高应用程序启动速度:Zygote 进程在系统启动时预加载了许多常用的类和资源,这些类和资源在内存中只有一份,可以被所有应用程序进程共享。当通过 Zygote 进程 fork 出新的应用程序进程时,新进程可以直接使用这些已加载的类和资源,无需再次加载。这样可以大大减少应用程序启动时的类加载和资源初始化时间,提高启动速度
  • 资源共享:由于 Zygote 进程预加载的类和资源在内存中只有一份,它们可以被所有应用程序进程共享复用。这样可以减少系统的内存占用,提高资源利用率
  • 简化应用程序启动流程:通过 Zygote 进程来创建应用程序进程,可以简化启动流程,减少启动过程中的错误和异常。Zygote 进程为应用程序提供了一个统一的、经过良好测试的运行环境,有助于提高应用程序的稳定性和兼容性

参考资料

相关推荐
拉不动的猪2 小时前
# 关于初学者对于JS异步编程十大误区
前端·javascript·面试
熊猫钓鱼>_>4 小时前
Java面向对象核心面试技术考点深度解析
java·开发语言·面试·面向对象··class·oop
进击的野人6 小时前
CSS选择器与层叠机制
css·面试
T___T9 小时前
全方位解释 JavaScript 执行机制(从底层到实战)
前端·面试
9号达人9 小时前
普通公司对账系统的现实困境与解决方案
java·后端·面试
勤劳打代码9 小时前
条分缕析 —— 通过 Demo 深入浅出 Provider 原理
flutter·面试·dart
努力学算法的蒟蒻10 小时前
day10(11.7)——leetcode面试经典150
面试
进击的野人10 小时前
JavaScript 中的数组映射方法与面向对象特性深度解析
javascript·面试
南山安10 小时前
以腾讯面试题深度剖析JavaScript:从数组map方法到面向对象本质
javascript·面试
橘颂TA12 小时前
【剑斩OFFER】算法的暴力美学——二分查找
算法·leetcode·面试·职场和发展·c/c++