Android View 绘制流程 过程分析

Android View 绘制流程 过程分析,第1张

前言

今天整理了一下Android View的绘制流程,记录一下


一、如何获取View的宽高

我们经常会遇到要获取控件宽高的情况,可以通过以下两种方式来获取。

1. 通过View的post()
	MFragmentTabView tabView = findViewById(R.id.fragment_tab_view);
	tabView.post(new Runnable() {
	    @Override
	    public void run() {
	        tabView.getWidth();
	        tabView.getHeight();
	    }
	});
2. 通过ViewTreeObserver
       MFragmentTabView tabView = findViewById(R.id.fragment_tab_view);
       tabView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
           @Override
           public void onGlobalLayout() {
               tabView.getWidth();
               tabView.getHeight();
           }
       });
二、View真正开始绘制是从哪里开始的呢

我们知道,在Activity的onCreate(),onResume() 中,都无法通过View的getHeight()、getWidth() 获取真正的宽高,说明此时还没有进行完View的绘制流程,那么View的绘制到底是从哪里开始的呢,我们从Activity的启动流程里已经知道,Activity生命周期被执行的第一个方法是attach().

1. 检查attach()方法
	final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
		......
		//实例化Window对象,并设置各种回调方法
        mWindow = new PhoneWindow(this, window, activityConfigCallback); 
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        ......
    }

这里并没有关于View绘制的内容,只是创建了Window接口的唯一实现类PhoneWindow的对象实例,还有一些入参的赋值 *** 作。然后我们接下来按照声明周期,去看onCreate()方法,发现onCreate()方法里也没有View绘制的方法,但是我们会在onCreate()回调中调用setContentView()方法,传入我们的布局文件。因为从Android21以后出现了AppCompatActivity来支持Meterial Design等特性,我们现在的Activity都是继承自AppCompatActivity,然后我们会发现setContentView方法最后是在 AppCompatDelegateImpl中实现

2. 检查setContentView()方法
@Override
public void setContentView(View v) {
    //生成DecorView的子View
    ensureSubDecor();
    //找到R.id.content控件,并清空子View
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    //将我们activity中设置的xml文件转成的View放到R.id.content控件中
    contentParent.addView(v);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
}

进入到ensureSubDecor()方法中,发现是通过createSubDecor()创建的SubDector,

mSubDecor = createSubDecor();

在createSubDecor() 方法中,

private ViewGroup createSubDecor() {
    // 此处省略了根据主题Theam设置Window feature代码
    .......
    
    // 确保创建了Window对象,并将Window和Activity关联起来
    ensureWindow();
    // 创建DecorView及其子View contentParent,并关联到Window
    mWindow.getDecorView();

    // 此处省略了根据不同条件,给subDecor赋值不同的布局文件,生成SubDecor对象
    .......
   
    // 获取SubDecor中R.id.action_bar_activity_content控件
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);
    // 获取DecorView中R.id.content控件
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        // 将DecorView中content控件的子View移除并添加到SubDecor的content控件中
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }
        windowContentView.setId(View.NO_ID);
        //将R.id.content赋值给SubDecor的content控件
        contentView.setId(android.R.id.content);
        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }

    //将SubDecor设置到Window中->将subDecor设置为DecorView的子View
    mWindow.setContentView(subDecor);
	.......
    return subDecor;
}

关于DecorView的创建,从PhoneWindow的getDecorView中,进入到installDecor()方法中,

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        //生成DecorView并关联Window
        mDecor = generateDecor(-1);
        ......
    }
    if (mContentParent == null) {
        //生成DecorView的子View
        mContentParent = generateLayout(mDecor);
        ......
    }
}

在 generateDecor()中,会new一个DecorView对象,然后我们看一下generateLayout()如何生成mContentParent对象

protected ViewGroup generateLayout(DecorView decor) {
	//此处省略了各种设置flag和feature的方法
    ......

    //此处省略了生成布局id的代码 
    ......
    
    //实例化上面确定的布局,并将该布局添加到DecorView里
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    //从DecorView中找到R.id.content控件
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	......
    return contentParent;
}

再解释一下mWindow.setContentView(subDecor)方法的作用

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //清空View
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        .......
    } else {
        //将SubDecor添加到mContentParent中
        mContentParent.addView(view, params);
    }
    ......
}

至此,setContentView()方法介绍完了,但是也没有找到开始绘制的源头,根据声明周期,我们去查onResume()方法,通过之前的启动Activity的文章,我们知道Activity的onResume是在ActivityThread的handleResumeActivity()中调用的,源码如下

3. 检查ActivityThread的handleResumeActivity()方法
ActivityThread.java类

@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
    ......

    //调用onResume()
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }
    ......

    final Activity a = r.activity;
    ......
    
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        //通过PhoneWindow获取DecorView
        View decor = r.window.getDecorView();
        //先隐藏,防止谈起软件盘
        decor.setVisibility(View.INVISIBLE);
        //获取WindowManager对象
        ViewManager wm = a.getWindowManager();
        ......
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                //将DecorView添加到WindowManager中
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    }
    ......
}

然后我们在WindowManager的实现类WindowManagerImpl中,发现addView()方法委托给WindowManagerGlobal类来实现,

WindowManagerGlobal.java类
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    //省略检测代码
    ......
    ViewRootImpl root;
    View panelParentView = null;
    synchronized (mLock) {
        ......
        //创建ViewRootImpl对象
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        try {
            //将View和ViewRootImpl关联起来
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            ......
        }
    }
}

到此,终于到了我们View绘制最关键的类ViewRootImpl了

4. 检查ViewRootImpl类

先看一下ViewRootImpl的构造函数中几个重要的数据

public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
        boolean useSfChoreographer) {
    ......
    //IWindowSession对象,是一个IBinder接口,客户端通过WindowSession *** 作WMS
    mWindowSession = session; 
    ......
    //View关联到Window上的信息,每个View都有一个AttachInfo对象
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
            context);
    ......
    //接收系统硬件发出的Vsync垂直同步信号,例如60Hz的屏幕,16.7ms发出一次信号,
    //通知页面进行View绘制、手势 *** 作、View动画等 *** 作 
    mChoreographer = useSfChoreographer
            ? Choreographer.getSfInstance() : Choreographer.getInstance();
    ......
}

接下来我们看一下setView()方法

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
        int userId) {
    synchronized (this) {
        if (mView == null) {
			mView = view;
			......
            // Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            // 真正开启三大流程的地方
            requestLayout(); 
            // 输入监听频道,mWindowSession.addToDisplayAsUser的参数,因此ViewRootImpl也是输入监听的开始类
            InputChannel inputChannel = null;
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                inputChannel = new InputChannel();
            }
            ......
            try {
                ......
                //通过WindowSession将window注册到WMS
                res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), userId,
                        mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                        mTempControls);
                ......
            } 
            ......
            if (inputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                //系统输入事件的监听 
                mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                        Looper.myLooper());
				......
            }
            //ViewRootImpl是最顶层的父节点,但不是View的子类,而是实现了ViewParent接口
            view.assignParent(this);
            ......
        }
    }
}

接下来我们看一下requestLayout()方法

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        //检查线程
        checkThread();
        mLayoutRequested = true;
        //执行遍历
        scheduleTraversals();
    }
}

此时有一个问题,子线程中真的不能 *** 作View吗,答案是可以 *** 作,但是必须要在ViewRootImpl调用checkThread()方法之前,就不会报错。
接下来我们看scheduleTraversals()方法

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        //标志位,防止同一帧的情况下执行多次
        mTraversalScheduled = true;
        //发送消息屏障,为了让Handler的异步消息先执行,从而使ViewRootImpl的measure、layout、draw方法优先执行
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); 
        //发送异步消息,执行遍历的Runable方法
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

mTraversalRunnable 是一个Runnable对象,在run中执行了 doTraversal()方法,如下

void doTraversal() {
    if (mTraversalScheduled) {
        //取消标记
        mTraversalScheduled = false;
        //移除屏障消息,因为已经要开始进行三大流程了
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
		......
        //执行遍历
        performTraversals();
        ......
    }
}

然后我们会发现performTraversals()是一个近800行的方法,这个方法主要就是用来计算是否需要执行三大流程的

//执行测量
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        //执行View的measure方法
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

//执行布局
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
	......
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        //执行View的layout方法
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        .......
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

//执行绘制
private void performDraw() {
	......
	//调用draw()
	draw(fullRedrawNeeded);
	......
}
private boolean draw(boolean fullRedrawNeeded) {
	......
	//调用drawSoftware()
	drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets);
	......
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
     boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
	......
	//执行View的draw()
	mView.draw(canvas);
	......
}

至此,我们终于捋清了View的三大流程是如何被一步步执行的。
用一张图表示一下:

个人理解,如有问题,请指正

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/787639.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存