一、引子程序
问题:下面的代码哪些可以获得到控件的高度?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.tv);
Log.e(TAG, "height1 = " + mTextView.getMeasuredHeight());
Log.e(TAG, "height4 = " + mTextView.getHeight());
mTextView.post(new Runnable() {
@Override
public void run() {
Log.e(TAG, "height2 = " + mTextView.getMeasuredHeight());
}
});
}
@Override
protected void onResume() {
super.onResume();
Log.e(TAG, "height3 = " + mTextView.getMeasuredHeight());
}
答案:height1、height3、height4都获取不到,height2可以获取到。
那么view的回值流程到底是什么样子的
二、流程分析
--> 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);
三、handleResumeActivity()方法的执行流程。
调用ActivityThread.java里的handleResumeActivity()
1. 调用ActivityThread.java里的performResumeActivity()
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
-
调用Acitvity的perforResume()方法
当前activity的performResume方法。
r.activity.performResume(r.startsNotResumed, reason);
-
通过仪表类调用CallActivityOnResume()方法。
mInstrumentation.callActivityOnResume(this);
-
调用activity的onResume方法。
也就是我们在写acitivity写的生命周期中的onresume。只不过这里是父类的及super.onResume()。
activity.onResume();
2.获取activity里的PhoneWindow、DecorView和windowManager的属性.
3.windowManager来addView
-
调用wm的addView方法
wm.addView(decor, l);
-
wm的创建
wm是在Activity.java里attach方法创建的
windowManager的实现类是windowManagerIml.所以调用的是windowManagerIml的addView方法。(在activity的attach方法里通过看源码查到是windowManagerIml是实现类)
-
windowManagerIml的addView方法
调用的是WindowManagerGlobal的addView方法。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
-
WindowManagerGlobal的addView方法
1.root = new ViewRootImpl(view.getContext(), display);//创建ViewRootImpl 2.mViews.add(view);//把DecorView添加到链表里。 mRoots.add(root);//把viewRootImp添加到链表里。 mParams.add(wparams);//把WindowManager.LayoutParams放入链表里。 3.root.setView(view, wparams, panelParentView, userId);//当前窗口的信息保存在,ViewRootImpl里。
WindowManagerImpl、WindowManagerGlobal、ViewRootImpl
WindowManagerImpl:确定 View 属于哪个屏幕,哪个父窗口(创建的时候会跟一个window绑定)
WindowManagerGlobal:管理整个进程 所有的窗口信息
ViewRootImpl:WindowManagerGlobal 实际操作者,操作自己的窗口
4.ViewRootImpl的setView方法。
1.requestLayout();//请求遍历
->checkThread()//哪个线程创建了ui,就在哪个线程更新。
->scheduleTraversals()
->mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//执行里面的runnable方法。
->doTraversal()
->performTraversals();//绘制view
2.res = mWindowSession.addToDisplayAsUser()//把窗口添加到wms上,session可以理解为wms的代理对象
3.mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());//事件处理
4.view.assignParent(this);//给DecorView设置一个parent.这里是把viewRootImp传递给view控件的parent成员变量上。
ViewRootImpl的构造方法
mThread = Thread.currentThread();//拿到当前线程
mDirty = new Rect();//脏区域,收集要被修改的控件。
mWinFrame = new Rect();//窗口的位置和尺寸
mAttachInfo = new View.AttachInfo()//保存当前窗口的一些信息。
performTraversals()@viewrootImpl.java循环遍历
1.windowSizeMayChange |= measureHierarchy(host, lp, res,desiredWindowWidth, desiredWindowHeight);//预测量
2.relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);//重新布局窗口
->mWindowSession.relayout()//调用wms里的方法。
3.performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
4.performLayout(lp, mWidth, mHeight);
5.performDraw();
measureHierarchy()预测量的执行逻辑
预测量最多会测量3次。
1.if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT)//当是WRAP_CONTENT,进行预测量
2.给一个值,进行performMeasure()
->View.measure()方法。进行遍历测量。
3. 如果不满足要求,换一个值再进行遍历测量。baseSize = (baseSize+desiredWindowWidth)/2;
4.如果不满足要求,进行最后i一次遍历测量 把窗口的宽高都给出去。(如果测量的结果跟父容器给的大小不一样,后续需继续测量)
performMeasure()进行view的测量。
在onMeasure()方法里会递归的测量每个控件的宽高。
perfoemMeasure()@viewrootImpl.java
->measure()@View.java
->onMeasure()->重写后需要调用setMeasuredDension()
MeasureSpc简单介绍
是一个32位的int值,高两位代表的模式,低30位代表的值。
mode:UNSPECIFIED、EXACTLY、AT_MOST
performLayout()
在onlayout方法里会调用每个控件的布局方法。
performLayout@viewrootImpl.java
->host.layout()@View.java
->onLayout()//自定义view的方法。
->child.layout()
->onLayoutChangeListeneger()//控件布局改变的监听
计算宽高的时候:
view:需要加上自己的padding.
viewGroup:需要加上孩子的margin.
performDraw()绘制控件树
performDraw@viewRootImpl.java
->draw()@viewRootImpl.java
->scrollToRectOrFocus(null, false);//处理滚动(弹出软键盘,整个布局上移。)
->mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);//硬件加速绘制。
->drawSoftware()//软件绘制
->view.draw(Canvas)
->onDraw(Canvas)//绘制自己。
->dispatchDraw(Canvas)//绘制子view的。
四、面试题
如何在子线程中刷新ui:
1.在没创建出viewRootImpl的时候刷新。
2.在子线程中创建viewRootImpl