自定义View基础(一)——追根溯源,透过源码认识ViewRoot,DecorView和performTraversals方法

来源:互联网 发布:有什么聊天软件 编辑:程序博客网 时间:2024/04/30 11:42

关于自定义View,可能会常常被我们所熟知,我们知道它的onMeasure(),onLayout()以及onDraw()方法,我们知道要用invalidate()使View进行重绘,调用requestLayout()会让这个View重新测量、布局。但是,我们有没有想过,这些方法的内部到底是一个怎样的流程?它们的内部到底又做了哪些工作?关于这样的疑问还有很多,接下来,我会慢慢探究。

想要对View的measure,layout,draw这三个过程有一个更深的了解,我们首先得知道这三个过程是在哪里调用的,所以,有必要好好介绍一下ViewRoot以及DecorView这两个不常见的概念

一、关于ViewRoot

1. ViewRoot是什么?

打开Android源码,搜索ViewRoot,我们并没有看到与ViewRoot完全相对应的类,但是我们发现ViewRootImpl这个类,通过命名方式我们也能知道,这是ViewRoot的实现类,也就是我们要看的东西,那么接下来,我们就看看ViewRootImpl这个类中,到底有哪些需要我们注意的东西呢?

2.ViewRootImpl是什么?

  The top of a view hierarchy, implementing the needed protocol between View  and the WindowManager.  This is for the most part an internal implementation  detail of {@link WindowManagerGlobal}.

这是源码中对于ViewRootImpl类的介绍,也就是说,这个类是连接View和WindowManager的纽带。我们可以认为,正是ViewRootImpl让View得以正确的显示出来。在ActivityThread中,也就是说Activity被创建完成,这个时候View,WindowManager的关系在Activity创建的过程中已经被关联上了,那么View,WindowManager是怎么建立关联的呢?ViewRootImpl这个类到底又做了哪些工作呢?


二、WindowManager,Window,View的理解

1.Window创建的过程

ActivityThread类中performLaunchActivity()方法会创建出Activity的实例,并调用onAttach()方法,如下:

ActivityThread.performLaunchActivity()

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {    ...    //创建出activity的实例    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);    ...    //调用Activity.attach(),对Window以及WindowManager做初始化工作    activity.attach(appContext, this, getInstrumentation(), r.token,                        r.ident, app, r.intent, r.activityInfo, title, r.parent,                        r.embeddedID, r.lastNonConfigurationInstances, config,                        r.referrer, r.voiceInteractor, window);    ...}

分析:performLaunchActivity运行的时候,会创建出对应的activity,之后执行activity的attach()方法,如下:

Activity.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) {            ...            //创建了一个PhoneWindow对象            mWindow = new PhoneWindow(this, window);            mWindow.setWindowManager(                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),                mToken, mComponent.flattenToString(),                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);            if (mParent != null) {                mWindow.setContainer(mParent.getWindow());            }            mWindowManager = mWindow.getWindowManager();            ...}

分析:在activity的attach方法中,会创建出一个PhoneWindow对象,PhoneWindow是Window的具体实现类,之后,通过setWindowManager方法为mWindow 设置了WindowManager对象,每个activity都会有一个PhoneWindow对象和WindowManager对象。

2.WindowManager创建的过程

Window.setWindowManager()

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,            boolean hardwareAccelerated) {            ...            mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);            ...}

分析:得到mWindowManager ,而这个mWindowManager就是和WindowManagerService(WMS)进行通信,也是WMS识别View具体属于那个Activity的关键,创建时传入IBinder 类型的mToken。这个mToken是一个IBinder,WMS就是通过这个IBinder来管理Activity里的View。

3.DecorView创建的过程

接下来,Activity在创建的时候执行onCreate()方法,调用setContentView()方法填充布局,setContentView方法代码如下:

Activity.setContentView()

public void setContentView(@LayoutRes int layoutResID) {        getWindow().setContentView(layoutResID);        initWindowDecorActionBar();    }

分析:setContentView方法中,有getWindow()获取到Window对象并执行setContentView(),继续查看PhoneWindow中的setContentView,代码如下:

Activity.setContentView()

public void setContentView(int layoutResID) {        ...        if (mContentParent == null) {            installDecor();        }         ...}

而installDecor()根据不同的Theme,创建不同的DecorView,DecorView是一个FrameLayout 。至此,PhoneWindow和DecorView两部分都已经创建好,但两者之间并没有任何的联系。

4.DecorView与PhoneWindow的关联

ActivityThread在执行完performLaunchActivity()之后,PhoneWindow和DecorView都已经被创建完成,但不存在联系,继续执行ActivityThread中的handleResumeActivity(),查看源码,如下:
ActivityThread.handleResumeActivity()

 final void handleResumeActivity(IBinder token,            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {        ...        if (r.window == null && !a.mFinished && willBeVisible) {                 //获得当前Activity的PhoneWindow对象                r.window = r.activity.getWindow();                 //获得当前phoneWindow内部类DecorView对象                View decor = r.window.getDecorView();                //设置窗口顶层视图DecorView可见度                decor.setVisibility(View.INVISIBLE);                 //得当当前Activity的WindowManagerImpl对象                ViewManager wm = a.getWindowManager();                WindowManager.LayoutParams l = r.window.getAttributes();                a.mDecor = decor;                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;                l.softInputMode |= forwardBit;                if (r.mPreserveWindow) {                    a.mWindowAdded = true;                    r.mPreserveWindow = false;                    ViewRootImpl impl = decor.getViewRootImpl();                    if (impl != null) {                        impl.notifyChildRebuilt();                    }                }                if (a.mVisibleFromClient && !a.mWindowAdded) {                    //标记根布局DecorView已经添加到窗口                    a.mWindowAdded = true;                    //将根布局DecorView添加到当前Activity的窗口上面                    wm.addView(decor, l);                }            }         ...}

分析:主要的解释清参见注释,接下来wm.addView()方法,wm应该查看其实现类WindowManagerImpl类的addView()方法,代码如下:

WindowManagerImpl.addView()

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {        applyDefaultToken(params);        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}

分析:继续跟踪代码,mGlobal.addView(),源码如下:

WindowManagerGlobal.addView()

public void addView(View view, ViewGroup.LayoutParams params,            Display display, Window parentWindow) {        ...        ViewRootImpl root;        View panelParentView = null;        ...        //获得ViewRootImpl对象root         root = new ViewRootImpl(view.getContext(), display);        ...        // do this last because it fires off messages to start doing things        try {            //将传进来的参数DecorView设置到root中            root.setView(view, wparams, panelParentView);        } catch (RuntimeException e) {        ...        }}

分析:在这里,我们终于又见到了一个前文熟悉的类ViewRootImpl ,在这里,他被new了出来,之后调用了setView()方法,并把decorView作为参数传入,继续跟踪,看ViewRootImpl 的setView()方法,源码如下:

ViewRootImpl .setView()

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {        synchronized (this) {            if (mView == null) {            //将顶层视图DecorView赋值给全局的mView                mView = view;            ...            //标记已添加DecorView             mAdded = true;            ...            //请求布局            requestLayout();            ...             } }

分析:在这里我们看到了requestLayout()方法,但是要注意的是,这个方法跟我们平时自定义控件时候所使用的requestLayout()是不同的方法,避免混淆,继续查看源码如下:

ViewRootImpl .requestLayout()

public void requestLayout() {        if (!mHandlingLayoutInLayoutRequest) {            checkThread();            mLayoutRequested = true;            scheduleTraversals();        }    }

ViewRootImpl .scheduleTraversals()

void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();            mChoreographer.postCallback(                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);            if (!mUnbufferedInputDispatch) {                scheduleConsumeBatchedInput();            }            notifyRendererOfFramePending();            pokeDrawLockIfNeeded();        }    }

ViewRootImpl .TraversalRunnable

final class TraversalRunnable implements Runnable {        @Override        public void run() {            doTraversal();        }    }

ViewRootImpl .doTraversal()

 void doTraversal() {        if (mTraversalScheduled) {            mTraversalScheduled = false;            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);            if (mProfile) {                Debug.startMethodTracing("ViewAncestor");            }            performTraversals();            if (mProfile) {                Debug.stopMethodTracing();                mProfile = false;            }        }    }

分析:峰回路转,终于看到了我们要找的方法performTraversals(),泪目。。。。。。。。

5.关于performTraversals()

ViewRootImpl .performTraversals()

private void performTraversals() {        // cache mView since it is used so much below...        final View host = mView;        if (host == null || !mAdded)            return;        //是否正在遍历        mIsInTraversal = true;        //是否马上绘制View        mWillDrawSoon = true;        ...        //顶层视图DecorView所需要窗口的宽度和高度        int desiredWindowWidth;        int desiredWindowHeight;        ...        //在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView        if (mFirst) {            mFullRedrawNeeded = true;            mLayoutRequested = true;            //如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要窗口的宽度和高度就是除了状态栏            if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL                    || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {                // NOTE -- system code, won't try to do compat mode.                Point size = new Point();                mDisplay.getRealSize(size);                desiredWindowWidth = size.x;                desiredWindowHeight = size.y;            } else {//否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高                DisplayMetrics packageMetrics =                    mView.getContext().getResources().getDisplayMetrics();                desiredWindowWidth = packageMetrics.widthPixels;                desiredWindowHeight = packageMetrics.heightPixels;            }    }    ...    //获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高     int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);     int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);      // Ask host how big it wants to be      //执行测量操作      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);    ...    //执行布局操作     performLayout(lp, desiredWindowWidth, desiredWindowHeight);    ...    //执行绘制操作    performDraw()}

ViewRootImpl .performMeasure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");        try {            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);        } finally {            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }    }

分析:继续查看源码performMeasure(),此时,我们就能看到View.measure()的调用了,performLayout()以及performDraw()和performMeasure()类似。


三、总结

这里写图片描述

*图片来自网络

这张图显示的是View完整的从无到有的显示过程,这篇文章最重要的是告诉了我们最顶端的performTraversals从哪里而来,performTraversals()方法中用到了performMeasure(),performLayout(),performDraw()这三个方法,从中我们也找到了我们想要的结果,找到了onMeasure(),onLayout(),onDraw()的源头,终于以后不用再用到这些方法的时候囫囵吞枣啦!

如有错误,欢迎指正
邮箱:hello_zhangchao@163.com

阅读全文
0 0
原创粉丝点击