【Android源码】深入源码分析UI的绘制流程 附:Android源码解析资料分享

//PhoneWindow#installDecor

private void installDecor(){

if (mDecor == null) {

//代码3

mDecor = generateDecor(-1);

}

if(mContentParent == null){

//代码4

mContentParent = generateLayout(mDecor);

}

}

在 PhoneWindow#setContentView 代码1处 首先调用了 installDecor 方法,installDecor 中执行了 generateDecor 和 generateLayout ,代码3处generateDecor 方法初始化了 DecorView,DecorView 可以看做是Android View体系中最顶级的View ,其实就是一个 ViewGroup 对象,generateLayout 中传入了 DecorView ,generateLayout 中定义了一个变量 layoutResource ,用来保存在不同主题下的基本布局,也就是我们新建一个项目时你在IEDA上看到的第一个布局样式。

//PhoneWindow#generateLayout

protected ViewGroup generateLayout(DecorView decor) {

//用来保存布局文件

int layoutResource;

//省略代码根据不同的主题给 layoutResource 赋值

//将 layoutResource 布局文件添加到 DecorView上

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

//返回资源id为 com.android.internal.R.id.content 的View对象

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

return contentParent;

}

generateLayout 方法内部首先定义了一个变量 layoutResource 用来保存在不同主题下的布局文件,调用 DecorView#onResourcesLoaded 将 layoutResource 布局文件添加到 DecorView 中,(ViewGroup)findViewById(ID_ANDROID_CONTENT) 返回 layoutResource 敲定好后控件id为 com.android.internal.R.id.content 的控件对象,截止到目前为止,DecorView 被初始化,layoutResource 被添加到 DecorView 中,回到上面的代码2中执行将自己定义的布局文件添加到 mContentParent 中,所以到了这一步你的布局文件已经被解析好了,但是记住在这里没有发现任何一个执行布局测量,布局以及绘制的操作,这也是为什么在onCreate中你直接获取View的宽高获取不到的原因,因为在setContentView中只是执行了解析布局文件以及处理布局层级的操作,并最终得到了下面的结构:

VIew的布局层级.png

View是如何被添加到Window中的


前文中已经分析完了在 setContentView 中我们自己写的布局文件是怎么叠加在 DecorView 中的以及 View 的布局层级结构,但是 onCreate 方法中是不涉及到 View 对象的测量,布局以及绘制的,仅仅只是处理布局的解析而已,那么在什么时候处理这些事务呢,处理这些事务的具体流程是什么?核心代码在 ActivityThread 的handleResumeActivity 方法中,我们跟着一起看下:

final void handleResumeActivity(IBinder token,

boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason)

//执行Activity的onResume方法

r = performResumeActivity(token, clearHide, reason);

if (r != null) {

final Activity a = r.activity;

boolean willBeVisible = !a.mStartedActivity;

if (r.window == null && !a.mFinished && willBeVisible) {

//获取Activity里的PhoneWindow对象

r.window = r.activity.getWindow();

//根据PhoneWindow获取DecorView对象

View decor = r.window.getDecorView();

decor.setVisibility(View.INVISIBLE);

//获取 WindowManagerImpl ,ViewManager是一个接口

ViewManager wm = a.getWindowManager();

WindowManager.LayoutParams l = r.window.getAttributes();

a.mDecor = decor;

l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;

l.softInputMode |= forwardBit;

if (a.mVisibleFromClient) {

if (!a.mWindowAdded) {

a.mWindowAdded = true;

//最终调用WindowManagerImpl的addView方法

wm.addView(decor, l);

}

}

}

}

//WindowManagerImpl#addView

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {

//桥接模式 - WindowManagerGlobal执行具体的addView方法

mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);

}

//WindowManagerGlobal#addView

public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {

ViewRootImpl root;

synchronized(mLock){

//每次新建一个Activity的时候都会新建一个ViewRootImpl对象

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

try{

root.setView(view, wparams, panelParentView)

}catch(RuntimeException e){

throw e;

}

}

}

在 handleResumeActivity 方法中执行具体的绘制流程,首先是将View添加到 WindowManagerImpl 中,这中间利用到了桥接模式也就是具体的 addView 的操作是 WindowManagerGlobal 来执行的,WindowManagerGlobal 是一个单例类在它内部维护了三个集合 mViews、mRoots、mParams、分别用来盛放View对象、ViewRootImpl以及一些参数,这里其实没什么重点只是用来保存一下,重点还是在 ViewRootImpl 的 setView 方法,将 DecorView 与 ViewRootImpl 绑定在一起,在setView方法里执行测量、布局、绘制的流程。

小结

  • setContentView中只是执行了解析布局文件以及处理DecorView层次结构

  • 在 handleResumeActivity 中才会去执行View的绘制流程,可以这么理解在 onResume 之后View的绘制流程才会走完

  • 通过 WindowManagerImpl 的 addView 方法最终是托管给 WindowManagerGlobal 来处理

  • ViewRootImpl是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中并将ViewRootImpl对象和DecorView建立关联 root.setView(view, wparams, panelParentView)

View的绘制顺序


ViewRootImpl的setView方法流程.png

首先是 ViewRootImpl 的 setView 函数,这对我们而言是及其重要重要的方法,View 的具体的绘制流程就在这里,在 setView 方法中会先去调用 requestLayout 方法,在 requestLayout 中首先会去执行 checkThread 方法检查当前线程是不是主线程,所以当你看到在 onCreate 中也能在子线程中更新UI不要大惊小怪,因为只有在 onResume 方法执行的时候才会去检查线程,紧接着就是执行 scheduleTraversals ,下面是核心代码:

void scheduleTraversals() {

if (!mTraversalScheduled) {

mTraversalScheduled = true;

//代码 1 开启同步屏障

mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();

//发送异步消息

mChoreographer.postCallback(

Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

}

}

首先在代码1处开启了同步屏障,紧接着发送一个异步消息 TraversalRunnable ,熟悉Handler 的朋友肯定知道,同步屏障的目的是为了提高优先级,而UI界面作为用户接触到的第一个“事务”自然优先级必须提前,不能被别的任务阻塞,在 TraversalRunnable 的 run 方法中执行 doTraversal :

void doTraversal() {

if (mTraversalScheduled) {

mTraversalScheduled = false;

//移除同步屏障

mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

//执行具体的绘制流程

performTraversals();

}

}

doTraversal 中首先移除同步屏障,这点是必然的,不然别的普通消息也不会被执行,有添加屏障的地方就必须在合理的时机移除屏障,performTraversals 方法中执行具体的绘制流程也就是 performMeasure、performLayout、performDraw三件套,分别执行View的测量、布局以及绘制三步骤,大致流程可以用下图表示。

performTraversals大致工作流程.png

在 performTraversals 方法中会依次调用 performMeasure、performLayout、performDraw这三个过程来完成顶级View的 measure、layout、draw 的过程,以measure举例子在measure过程中会调用 onMeasure 方法,在 onMeasure 中将测量的流程传递给子View,依次递归完成 View 的 measure 过程,layout 和 draw 的过程也大同小异,measure过程决定了View的宽/高,Measure完成以后,可以通过getMeasuredWidth和getMeasuredHeight方法来获取到View测量后的宽/高,在几乎所有的情况下它都等同于View最终的宽/高,Layout过程决定了View的四个顶点的坐标和实际的View的宽/高,完成以后,可以通过getTop、getBottom、getLeft和getRight来拿到View的四个顶点的位置,并可以通过getWidth和getHeight方法来拿到View的最终宽/高。Draw过程则决定了View的显示,只有draw方法完成以后View的内容才能呈现在屏幕上。

View.post() 实现原理

===========================================================================

在 onCreate 函数中直接获取 View 的宽高你是获取不到的,理由在上面解释过,但是你可以通过 View.post() 的方式来获取View的宽高,下面跟着流程一起看下具体的实现原理。

//View.java

public boolean post(Runnable action) {

//mAttachInfo在View的dispatchAttachedToWindow方法中被赋值

final AttachInfo attachInfo = mAttachInfo;

if (attachInfo != null) {

return attachInfo.mHandler.post(action);

}

//HandlerActionQueue的post函数

getRunQueue().post(action);

return true;

}

//View.java

void dispatchAttachedToWindow(AttachInfo info, int visibility) {

//在 dispatchAttachedToWindow 中给 mAttachInfo 赋值

mAttachInfo = info;

if (mRunQueue != null) {

//执行 HandlerActionQueue#executeActions

mRunQueue.executeActions(info.mHandler);

mRunQueue = null;

}

}

在 post 方法中会给 attachInfo 赋值为 mAttachInfo,而 mAttachInfo 在View 的 dispatchAttachedToWindow 方法中被赋值,同时执行 HandlerActionQueue 的 executeActions 方法。

public class HandlerActionQueue {

private HandlerAction[] mActions;

//代码1

public void post(Runnable action) {

postDelayed(action, 0);

}

public void postDelayed(Runnable action, long delayMillis) {

final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

synchronized (this) {

if (mActions == null) {

mActions = new HandlerAction[4];

}

mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);

mCount++;

}

}

//代码2

public void executeActions(Handler handler) {

synchronized (this) {

final HandlerAction[] actions = mActions;

for (int i = 0, count = mCount; i < count; i++) {

final HandlerAction handlerAction = actions[i];

handler.postDelayed(handlerAction.action, handlerAction.delay);

}

mActions = null;

mCount = 0;

}

}

private static class HandlerAction {

final Runnable action;

final long delay;

public HandlerAction(Runnable action, long delay) {

this.action = action;

this.delay = delay;

}

}

}

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-FrkDWfj5-1715797914820)]

[外链图片转存中…(img-mpM0XgZK-1715797914822)]

[外链图片转存中…(img-6o3NLI8h-1715797914823)]

[外链图片转存中…(img-zsk24PLp-1715797914824)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值