首先抛出几个问题
1.如何在activity中获取view的宽和高?
2.下面引申出来了一个新的问题, onResume() 是什么时候调用的?还有 activity 的各个生命周期都是在哪被调用的?当然这就不是本文的内容了,这里不做过多的深入.
3.既然在 onCreate(); onStart(); onResume();中都无法直接获取 view 的宽高,所以确定 view 的测绘应该是在 onResume() 之后,那么 view 的测绘流程是重哪里开始的?
*********************我是分割线*************************
1.如何在activity中获取view的宽和高?(这里使用了 kotlin 就不改了)
@1.直接获取 view.width --- view.height 当然这是没什么用的
@2. view.post {
WLog.e("view:post {view.height}")
}
@3. //多次回调,页面上view树每次发生变化都会进行回调
view.viewTreeObserver.addOnGlobalLayoutListener {
//不想多次回调可以移除
// view.viewTreeObserver.removeOnGlobalLayoutListener { this }
WLog.e("onResume():viewTree {view.height}")
}
2.onResume() 是什么时候调用的?
首先来看切入点 ActivityThread.class.handleResumeActivity() (这里就不要纠结这个方法是在哪调用的了,内容又变多了)
可以看到这里执行完就已经切回到了 Activity 类里面了,然后我们在看看 Activity 类的情况
最后可以看到终于找到了心心念念的 onResume() 方法,执行的流程就是这个流程,多了不会说
3.view 的测绘流程是重哪里开始的?
继续回到 ActivityThread.class.handleResumeActivity() 方法中,来看看在 performResumeActivity(token, finalStateRequest, reason);之后都干了什么.
View decor = r.window.getDcorView(); 这个 decor 就是 PhoneWindow 里面的 decor ,这里装载了 activity 里面的 veiw
wm.addView(decor,l); 正式开始进入了view的测绘流程
decor.setVisibility(View.INVISIBNLE); 隐藏视图,防止软键盘弹出
去看 addView(); 方法 vm 就是 WindowManager , 实现了 ViewManager 所以也就有了 添加,更新,移除的能力
先找到WindowMananger的实现类WindowManangerImpl 里面可以看到实现了上面的三个方法,并且用 mGlobal 做了一层转发,
为什么这么做呢,一个页面就有一个 WindowManager 对象,一个应用有很多个 WindowManager ,这层转发就是把所有的 WindowManager 做了一个收拢,全部收拢到了 WindowManagerGlobal 类中,
收拢了入口和出口,对外暴露的只是一个方法的定义而已
好收一收,回到handleResumeActivity()方法,往下看实现了一个 ViewRootImpl 然后调用 vm.addView() 方法,从上面看,这个vm 就是WindowMananger,把decor添加到了windowMananger中,把传入进来的 Veiw(就是decor) 和 viewRoot 相关联了,重点来了,ViewRootImpl 这里关联之后就开始了 view 的 测量布局绘制流程
因为到了ViewRootImpl ,介绍一下 ViewRootImpl
1.WindowSession 将 Window 添加到 WindowManagerService 中 IWindowSession mWindowSession 是一个 Ibinder 接口,通过它把当前窗口注册到 WindowMananger 中
2.页面 Viwe 树的顶层节点,但并不是一个View,只是实现了ViewParent ,关联 Window 和 View ,所有页面的跟布局都是 decorView,decorView 的 parent 就是 viewRootImpl
3.choreographer 接收 Vsync 同步信号触发 View 的三大流程 mChoreographer 接收系统发出的 Vsync垂直同步信号
,(垂直同步信号:系统会每隔一段时间发出一个信号,这个时间间隔是根据手机屏幕的刷新率确定的,目的60hz = 一秒60次)
4.WindowInputEventReceiver 接收屏幕输入事件,分发手势
5.接收 Vsync 同步信号,触发 View 的动画重绘
上面提到的属性构造方法里都有,太长了不截图了
好了,回到 vm.addView(); 上面说了 vm 就是 WindowManager ,走你,来到 WindowManager 的实现类 WindowManagerImpl,我们看到了 addView()方法
继续跟进,来到了 WindowManagerGlobal 中,找到 addView() 方法.好长,来看最后的 setView()方法
点进去往下看往下看找到 requestLayout() 方法,翻一下:把我们的窗口注册到 WindowService 之前,先来安排一次页面view树的布局,测绘工作,在这个方法里面就是真正开始了view的三大流程
跟进 ,继续点进去
可以看到有一个 checkThread](); 方法,再点进去可以看到这个著名的异常,Only the original thread that created a view hierarchy can touch its views.
翻译:只有创建视图层次结构的原始线程才能触及其视图。(意思就是在异步线程中不能更新主线程UI)
这里面就有一个问题,在子线程中真的不能更新view么?
我们可以新建一个activity试一下, Thread({txText.setText("我是来自子线程")}).start() 结果是可以成功更新的,但是我们知道在子线程中更新view并不是所有场景都生效的,
如果对view的更新涉及到了 view.requestLayout() ,view.post,就不会成功
收一收,回到 requestLayout() ,我们看第二个方法,scheduleTraversals();根据方法名字就是安排一次任务的执行,
这里面首先 mTraversalScheduled 进行同一帧重复多次请求的过滤,然后通过mHandler 发送了一条屏障消息,开始布局测量绘制工作,
接着调用了 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
使用 mChoreographer 去注册了一个垂直同步信号的监听的回调,每一次垂直信号的到达都会执行 mTraversalRunnable,
好,来到 TraversalRunnable,可以看到调用了 doTraversal(); 接着跟进,首先重置标记为,然后移除了消息队列中的消息屏障,因为一旦调用了 doTraversal() 就说明了 UI 测绘这条任务已经开始执行了,使同步消息也可以执行,
接着就开始了巨长的 performTraversals();这个方法的作用就是根据当前页面的view树,判断有没有view发生了变化,比如大小,位置,属性等等,来计算要不要对我们当前页面的view树进行重新测绘重新布局,以及重新绘制的操作,
关键的代码有
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);里面调用 view.measure();
performLayout(); 里面调用了 host.layout(); 这个host 就是 mView 也就是 decorView;
performDraw(); 里面先调用了内部方法 draw(); 然后到了 drawSoftware(); 哎?在里面发现 mView.draw(canvas);
这里就可以看出 activity 的布局测量绘制工作确实发生在了 onResume() 后面,真正的开始是在 VeiwRootImpl中
完结撒花