//代码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中只是执行了解析布局文件以及处理布局层级的操作,并最终得到了下面的结构:
前文中已经分析完了在 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)
首先是 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 方法中会依次调用 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的内容才能呈现在屏幕上。
===========================================================================
在 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年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。**
因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
[外链图片转存中…(img-6wkzSYvu-1712542113338)]
[外链图片转存中…(img-8BjA3WrZ-1712542113338)]
[外链图片转存中…(img-kLs7obsm-1712542113339)]
既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!
由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频
如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-3etAwyKh-1712542113339)]