【Android源码】深入源码分析UI的绘制流程 附,JSP计算机毕业项目

//代码2:将我们自己写的布局文件添加到 mContentParent 中

mLayoutInflater.inflate(layoutResID, mContentParent);

//…

}

//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);

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

深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。

因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
img
img
img

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
img

的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。**

因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
[外链图片转存中…(img-6wkzSYvu-1712542113338)]
[外链图片转存中…(img-8BjA3WrZ-1712542113338)]
[外链图片转存中…(img-kLs7obsm-1712542113339)]

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-3etAwyKh-1712542113339)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值