(二)高级UI View的绘制流程

227 阅读3分钟

一、引子程序

问题:下面的代码哪些可以获得到控件的高度?


    @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的属性.

image.png

3.windowManager来addView

  • 调用wm的addView方法

wm.addView(decor, l);
  • wm的创建

wm是在Activity.java里attach方法创建的

windowManager的实现类是windowManagerIml.所以调用的是windowManagerIml的addView方法。(在activity的attach方法里通过看源码查到是windowManagerIml是实现类)

image.png

  • 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