likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 8
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()函数,是应用进程的入口。到此完成应用进程启动

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

阶段二:应用进程初始化

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

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

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

ActivityThread.java

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

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

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

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

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

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()函数。

@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

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)分析

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

getDelegate()返回的是AppCompatDelegateImpl

// 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的根布局。

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

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

requestLayout()

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

ViewRootImpl

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

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()

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();
        ...
    }
}

performTraversals()

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

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

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

全部流程精简总结

  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:

#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 进程为应用程序提供了一个统一的、经过良好测试的运行环境,有助于提高应用程序的稳定性和兼容性

参考资料

转载自:https://juejin.cn/post/7353234023097630739
评论
请登录