面试?看完这篇就够了-深入分析从点击应用图标到应用界面展示_从点击图标到启动将发生哪些过程(1)

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  1. Looper.prepareMainLooper()Looper.loop() 开启了主线程消息循环,之后就可以通过消息机制往主线程 post 任务了
  2. main 是一个静态方法,可以看到这里实例化了一个 ActivityThread 并调用了 attach 方法

final ApplicationThread mAppThread = new ApplicationThread();

// 已精简非核心代码
private void attach(boolean system, long startSeq) {
// 1. 这里传入的是 false, 所以一定会进入这个分支
if (!system) {
// 2. 这里做的事情就是将 ApplicationThread 传到 AMS 中,往后 AMS 和 应用就可以通过 Binder 直接通信了
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// …
} else {
// …
}
}

可以看到 attach 里面主要做了一件事情就是和 AMS 建立 binder 通信,我们看下 ApplicationThread 的实现

private class ApplicationThread extends IApplicationThread.Stub {

@Override
public final void bindApplication(…) {
//…
sendMessage(H.BIND_APPLICATION, data);
}

//…
}

可以看到 ApplicationThread 实现了 Stub 接口,了解 AIDL 的同学都知道,这个就是用于跨进程通信的 AIDL 生成的接口文件,Stub 是由服务端实现的,这里的服务端就是应用进程,具体来说服务端的实现是 ActivityThread,而客户端就是运行在 system_server 的 AMS 了;

所以 ApplicationThread 是一个 Binder 对象,attach 方法就是把这个对象传给 AMS, 这样 AMS 就可以通过这里的接口来调用 ActivityThread 的代码了, ApplicationThread 就是 AMS 和 ActivityThread 沟通的桥梁。

一般 AMS 调用 ApplicationThread 的接口,ApplicationThread 会把任务和数据通过消息机制抛到主线程处理,其中 H 就是在 ActivityThread 中的一个主线程的 Handler,这里面处理了很多来自 AMS 请求的任务

  1. 经过上面的步骤,应用进程和 system_server 进程已经建立了 Binder 通信,接着 system_server 通过 Binder 调用 bindApplication,最终落到 ActivityThreadhandleBindApplication() 方法

private void handleBindApplication(AppBindData data) {
// …
try {
app = data.info.makeApplication(data.restrictedBackupMode, null); // 1. 实例化 Application, 内部调用了 Application.attachBaseContext()

if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers); // 2. 实例化 ContentProvider,调用 ContentProvider.onCreate()
}
}

//…
try {
mInstrumentation.callApplicationOnCreate(app); // 3. 这里面调用 Application.onCreate()
} catch (Exception e) {
// …
}
} finally {
// …
}

// …
}

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

阶段三:Activity 启动阶段

应用进程启动之后,紧接着 AMS 通过 Binder 调用,通过 ApplicationThreadActivityThread 中主线程发送 EXECUTE_TRANSACTION 消息,来执行 Activity 的生命周期;最终会在主线程执行 handleLaunchActivityhandleResumeActivity 等,这里面就会调用 Activity 的 onCreateonResume 等生命周期。

handleLaunchActivity

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);
// …
} catch (Exception e) {
// …
}

try {
// 调用 Activity.onCreate()
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
// …
} catch (SuperNotCalledException e) {
// …
}

return activity;
}

可以看到这里就做了两件事情:实例化 Activity 然后调用 Activity.onCreate()

handleResumeActivity

@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
// 1. 这里面会执行 onResume()
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
// 2. 获取 decorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);

ViewManager wm = a.getWindowManager();
// 3. 将 decorView 添加到 WindowManager 中
wm.addView(decor, l);
}

wm.addView 最终会调用到 android.view.WindowManagerGlobal#addView

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
// 实例化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);

// do this last because it fires off messages to start doing things
try {
// 核心代码,调用 setView 传入 view
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}

ViewRootImpl.setView 里面最核心的代码就是调用了 requestLayout

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
// …
requestLayout();
//…
}

requestLayout 里面就是 View 的绘制流程了

阶段四:View 绘制阶段

前面提到在 AMS 的调度下,Activity 会执行生命周期 onCreateonResume,应用界面要显示出来,这两个生命周期是关键;我们一般会在 onCreate 里面调用 setContentView 来设置自定义布局,下面就看看,setContentView 里面都做了些什么事情

setContentView()

以下代码基于 Android13(SDK 33)分析

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

getDelegate 返回的是 AppCompatDelegateImpl

// AppCompatDelegateImpl
@Override
public void setContentView(int resId) {
// 1. 实例化 DecorView
ensureSubDecor();
// 2. R.id.content 就是承载我们自定义 View 的容器
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
// 3. 将我们自定义 View 添加到 R.id.content 中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}

逻辑比较清晰,先实例化一个 DecorView,然后将我们自定义 View 添加到 DecorView 中,那这个 DecorView 是什么呢

DecorView

DecorView 是一个继承自 FrameLayout 的 View,里面一般包含一个标题栏和一个内容区,其中内容区的 id 是 android.R.id.content,这里附一张 示意图

我们这里可以简单理解为:DecorView 就是整个 Activity 的 Root View,用来容纳我们设置的自定义 View 布局;

所以执行完 setContentView 之后,一个以 DecorView 为 Root 的 View 树就创建好了,但是清注意,这个时候仅仅是构建好一个 View 树,View 还没有真正开始进入绘制流程,此时 View 不可见,也拿不到宽高。

我们可以通过 View.post {} 来拿到宽高,这是为什么呢?感兴趣可移步到 Android 消息机制

requstLayout()

回顾之前分析 handleResumeActivity 源码,我们知道,这里面先调用了 onResume(),然后最终调用到 ViewRootImpl.setView,在 ViewRootImpl.setView 里面调用了 requestLayout 开启 View 绘制流程。

首先看看这个 ViewRootImpl 是什么

ViewRootImpl

public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
AttachedSurfaceControl { }

首先从定义上看,ViewRootImpl 不是 View,而是一个实现了 ViewParent 接口的对象。它的构造方法中有两个成员需要重点关注下

public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
// 保存当前线程,对于这里分析的启动 Activity 的场景,这里是主线程
mThread = Thread.currentThread();
// Choreographer 是屏幕刷新机制的关键,后续会单独讲
mChoreographer = useSfChoreographer
? Choreographer.getSfInstance() : Choreographer.getInstance();
}

这里对 ViewRootImpl 的介绍先不再深入,我们留个印象即可,我们重点看下 requestLayout 做了什么事情

// ViewRootImpl
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
“Only the original thread that created a view hierarchy can touch its views.”);
}
}

这里主要做了两件事情:检查线程、调用 scheduleTraversals()

checkThread

mThread 就是实例化 ViewRootImpl 的时候的线程,我们前面提到 AMS 通过 Binder 调用最终向 ActivityThread 主线程发消息来执行 Activity 生命周期,所以 mThread 就是主线程;requestLayout 调用的地方也是主线程触发的,所以这里检测一定是通过的。不过如果我们在代码中手动调用 View.requestLayout,最终也会走到这里,此时的当前线程就是调用线程,所以这也解释了为什么我们不能在非主线程更新 UI。

一定不能在非主线程更新 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. 执行 doTraversal
doTraversal();
}
}

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 4. 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing(“ViewAncestor”);
}

// 5. onMeasure、onLayout、onDraw
performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

  1. 关于消息同步屏障,详细介绍可以移步到 Android 消息机制 ,这里简单理解一下其目的就是为了保证渲染任务得到高优执行。
  2. Choreographer 是屏幕刷新机制的关键实现,详细介绍可以移步到 屏幕刷新机制 ,这里也简单了解一下:往 Choreographer 里面 postCallback 目的是为了在系统下一帧渲染时间到来的时候执行 Runnable 中的逻辑,对应上面也就是执行 performTraversals()

下面重点介绍下 performTraversals() 这个方法内部实现特别复杂,代码很长,这里就不贴源码了,最关键的三行代码就是 performMeasure()performLayout()performDraw(),而这三个方法内部对应的就是我们熟知的 onMeasure()onLayout()onDraw()

关于 measure layout draw 的流程,这里放一张图,一图胜千言

总结

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

答疑解惑

什么是 fork 进程?

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

r屏幕刷新机制,开启 View 的绘制流程,执行onMeasure() onLayout() onDraw()`

答疑解惑

什么是 fork 进程?

[外链图片转存中…(img-cxBmb0CL-1715510300549)]
[外链图片转存中…(img-d7Gc02t9-1715510300550)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值