View的制作流程剖析(源码级)
View 在什么时分添加到屏幕(window)上的
定论:从源码剖析得知 View 是在 Activity 的 onResume() 履行后才会制作到屏幕上, 在 (ViewRootImpl)root.setView(view, wparams, panelParentView, userId); 里边的编舞者 把view送到屏幕上
ActivityThread.handleResumeActivity
performResumeActivity
r.activity.performResume(r.startsNotResumed, reason);
mInstrumentation.callActivityOnResume(this);
activity.onResume();
View decor = r.window.getDecorView();
WindowManagerImpl.addView(decor, r.window.getAttributes());
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId());
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view); // DecorView
mRoots.add(root); // ViewRootImpl
mParams.add(wparams); // WindowManager.LayoutParams
root.setView(view, wparams, panelParentView, userId);
WindowManagerImpl、WindowManagerGlobal、ViewRootImpl 责任
WindowManagerImpl: 确认 View 属于哪个屏幕,哪个父窗口
WindowManagerGlobal:办理整个进程 一切的窗口信息
ViewRootImpl:WindowManagerGlobal 的实际操作者,操作自己的窗口(只有一个)
ViewRootImpl.setView
requestLayout();
checkThread();//保证在当前线程与view创立线程(默许主线程) 是相同的,否则抛出异常
scheduleTraversals();
// 刺进音讯屏障 ,音讯链 来了个音讯屏障(优先级最高的),音讯链就需求开个口子(开口子的地方一般有指定的)让其插队
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 编舞者 发送回调办法,这回调办法 运行一次 就会 自动移除
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
doTraversal()
performTraversals(); // 制作view。预丈量 -> 布局窗口 -> 控件树丈量 -> 布局 -> 制作
notifyRendererOfFramePending();
HardwareRenderer.nNotifyFramePending(mNativeProxy);
pokeDrawLockIfNeeded()
mWindowSession.pokeDrawLock(mWindow);
res = mWindowSession.addToDisplayAsUser // 把 窗口添加到 WMS 上
WindowManagerService.addWindow
view.assignParent(this); // 指定ViewRootImpl 为父容器
面试题:UI更新只能在主线程履行吗?
答:不是的,依据checkThread() 函数可知,只要在 view的 创立线程下 更新都可以,或者在checkThread之前履行
ViewRootImpl 结构办法
mThread = Thread.currentThread(); //拿到创立它的线程,MainThread(默许)
// 脏区域:搜集哪些地方需求更改的
// 比方说 TextView 改变了值,这块区域就变为脏区域,
// 下次制作的时分就可以更好的确认哪些view变化了,需求进行重新制作的
mDirty = new Rect();
mAttachInfo = new View.AttachInfo() // 保存当前窗口的一些信息
所以说UI更新,可分三种状况进行更新:
- 在与View创立的相同的线程里更新
- 在checkThread() 之前( 例如onCreate() 、onResume() ),也能在其他线程里更新
- 在子线程中,新建 ViewRootImpl 目标
mChoreographer编舞者 把view送到屏幕上
//更新UI的音讯优先级是最高的,所以用异步音讯和音讯屏障刺进 更新UI音讯
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
doTraversal()
performTraversals(); // 制作view。预丈量 -> 布局窗口 -> 控件树丈量 -> 布局 -> 制作
// 履行 遍历
performTraversals@ViewRootImpl.java
// 1、预丈量(lp.width == ViewGroup.LayoutParams.WRAP_CONTENT)才会最多 丈量三次
windowSizeMayChange |= measureHierarchy()
1、设置一个值,进行第一次丈量
2、获取一个状态值
3、改变大小 baseSize = (baseSize+desiredWindowWidth)/2;
4、进行第二次丈量
5、假如还不满足,直接给自己的最大值,第三次丈量(不确认的)
假如 windowSizeMayChange = true // 表示还要丈量
// 2、布局窗口
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
// 3、控件树丈量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
onMeasure // 必定会调用 setMeasuredDimension() 设置参数
// 4、布局
performLayout(lp, mWidth, mHeight);
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
onLayout(changed, l, t, r, b);
child.layout // ViewGroup 管子view的布局
// 5、制作
performDraw();
draw()
// 这儿的翻滚,为了避免中心的控件被遮住而进行的翻滚。比方:输入框输入的时分,view向上翻滚
scrollToRectOrFocus(null, false);
if (isHardwareEnabled()) { // 硬件加速制作
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else { // 软件制作
drawSoftware(...);
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. 制作背景 Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
* 7. If necessary, draw the default focus highlight
*/
mView.draw(canvas);
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
}
padding 与 margin 所占空间差异
假如是View:加上自己的 padding(占用view 本身的空间)
假如是容器:加上孩子的 margin(孩子设置的 margin 占用的是 父容器的空间)
自定义 ViewGroup 是不履行 onDraw 办法
为什么会不履行了?看以下源码剖析:
// 硬件加速制作
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);@ViewRootImpl.java
updateRootDisplayList(view, callbacks);@ThreadedRenderer.java
updateViewTreeDisplayList(view);
view.updateDisplayListIfDirty();
updateDisplayListIfDirty@View.java
mView.mPrivateFlags // 依据这个标志位 判断是否跳过draw办法
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
child.draw(canvas, this, drawingTime);@ViewGroup.java // 调用 三参数的 draw函数
} else {
draw(canvas);
}
定论:由于跳过了单参数的 draw(canvas) 办法,只履行dispatchDraw(canvas)
如何知道体系服务的实际实现类
在 体系服务注册文件 SystemServiceRegistry.java
//体系服务注册文件 SystemServiceRegistry.java
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx); // WindowManagerImpl 实体目标
}});
Activity.attach
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)(留意了:获取体系服务),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager(); // 所以这儿 得到的是 WindowManagerImpl 实体目标