View 是什么时候被添加进去的?
我们 setContentView 只是创建了 DecorView ,并且解析 xml 将 View 添加到 DecorView 中了。
为什么在 Activity 的 onCreate 方法中通过 view.post 中就能获取到 各个元素的宽高呢?这是因为所有的 View 操作都是 Handler 的 msg ,在之前的 msg 已经在 layout了,而我们 post 过去的 msg 必然是在 layout 这个 msg 之后。因为高是通过 bottom - top 得到的,所以,还没layout 之前是没有的。
1 | public int getHeight(){ |
我们 window.addView 是在 performResume 之后,add 进去之后才会测量,所以我们执行 onResume 的时候,实际上也拿不到 元素的宽高的。
window 相关的各个类的功能:
WindowManagerImpl: 确定 view 属于哪个 window,哪个父窗口
WindowManagerGlobal : 管理整个进程、所有的 窗口信息,它是进程唯一的。
ViewRootImpl 是 WindowManagerGlobal 的实际操作者,只不过ViewRootImpl 只操作自己的 window
DecorView 是什么时候添加到 Window 上的?
handleResumeActivity
–> performResumeActivity
–> r.activity.performResume
–> mInstrumentation.callActivityOnResume
–> wm.addView(decor, l); (WindowManagerImpl.java)
–> WindowManagerGlobal.addView
–> 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);
接下来执行的过程:
ViewRootImpl.setView
–> requestLayout(); // 请求遍历
–> scheduleTraversals
–> mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
–> 上面的 mTraversalRunnable 中会执行到 doTraversal
–> performTraversals(); // 绘制View ,measure、layout、draw
将 window 添加到 WMS 上之后, DecorView 就会设置自己的 parent 为这个 ViewRootImpl ,所以,其实我们的 view 只需要getParent 得到 mParent ,之后 mParent 再次 getParent ,如此循环(往上遍历 View 树 ),最后肯定会到最顶部的 ViewRootImpl :
--> res = mWindowSession.addToDisplayAsUser // 将窗口添加到WMS上面 WindowManagerService
--> 事件处理
--> view.assignParent(this); //DecorView 设置自己的 parent 为 ViewRootImpl
ViewRootImpl 的构造函数里面有几行代码需要注意:
1 | mThread = Thread.currentThread();//创建它的线程 |
预测量
当前View 的 onMeasure 最多会执行多少次? 4 次,因为还可能(只有子 View 是 WrapContent的情况才有)有预测量(measureHierarchy 方法)过程,所以,我们也可以说 wrapContent 就是这样影响性能。预测量过程:
设置一个值,进行第一次测量
获取一个状态值,看看是否已经合适了
如果不合适,更改大小为某个值,进行第二次测量
如果还是不满意,直接给自己的最大值,进行第3次测量
第 4 步之后,如果还有问题,在预测量之后,还有一次 performMeasure ,所以最后,预测量里面可能最多有 3 次测量,当前 View 可能最多有 4次测量(当然,performMeasure 之后还可能引起父 view 的重新 measure ,这个另说)。
LayoutInflater.inflate () 的时候,如果 没有设置 parent 的话,那么xml 根部布局设置的布局参数无效(宽度、高度等)。因为会根据 parent.generateParams 来确定 xml 的 layoutParams 的,所以如果没有 parent ,inflate 出来就不会 setParams 。 generateLayoutParams 的例子如下:
1 | public LayoutParams generateLayoutParams(AttributeSet attrs) { |
MeasureSpec 是个 32 位的 int 型,低 30 是值,高 2 位是 mode 模式(AT_MOST、EXACTLY、UNSPECIFIED)
performLayout
child.layout
performDraw
我们有个输入框,当点击弹出输入法的时候,这个输入框可能会移动,这是怎么做到的呢?其实就是在 draw() 方法里面执行的 scrollxxx 某个方法实现的。
在 draw 方法里面,会判断是否开启硬件绘制,用 if-else 来分别使用 硬件绘制和软件绘制。
canvas 也是有软硬件绘制区分的。
绘制熟顺序: drawBackground 、onDraw、 drawForground
一般来说,ViewGroup 的 onDraw 是不执行的,只会执行 dispatchDraw ,而 view 则相反; 但是,如果我想让 ViewGroup 的 onDraw 方法执行,有几种方法?
setWillNotDraw(false) 设置为false
setBackground 设置背景
通过反射改变标志位
面试题?
UI 刷新只能在主线程进行吗?
我们知道出现报错是在 ViewRootImpl 的 requestLayout 的时候,会执行 checkThread ,而这个代码是:
1 | void checkThread() { |
意思是,如果 requestLayout 线程和 当初创建 ViewRootImpl 的线程不是同一个,则报错。所以,并不是说只有主线程才能刷新 UI ,而是只有在创建 ViewRootImpl 的线程才能刷新 UI。
那如何在子线程刷新UI 呢?
在 ViewRootImpl 创建之前调用,比如在onCreate 中创建线程 然后调用 textview.setText()
利用 Handler
在子线程中创建 ViewRootImpl:可以在子线程创建一个 WindowManager ,然后 addView ,比如我们页面上要有一个悬浮的按钮
在 WindowManager 的 addView 的时候,就会创建 ViewRootImpl 了
最后,放上2 张老师在ppt 上的图片:
requestLayout 的时序图:
invalidate 时序图: