最全的View绘制流程(上)— Window、DecorView、ViewRootImp的关系

如需转载请评论或简信,并注明出处,未经允许不得转载

目录

前言

对于接触Android开发不久的同学来说,要写一个页面,我们大多数时候都是先创建一个layout.xml布局文件,在布局文件中进行页面搭建,然后通过ActivitysentContentView()将布局文件设置到Activity中,这样Android系统就自动帮我们绘制了这个页面。我们知道,在Android中,一个页面是由一个个View组合而成的,那我们有没有想过,Android中View的绘制流程是怎么样的呢?本文将分为上下两部分,上部分主要讲ActivityWindowWindowManagerViewRoot等相关概念及互相之间的联系,了解这些有助于我们对View的绘制流程有一个更系统的认识,下部分会详细介绍view的measurelayoutdraw的过程

Activity、Window、DecorView的关系.png

Window和DecorView的创建

我们先从我们比较熟悉的activity.setContentView()说起,查看这个方法的源码

private Window mWindow;

public void setContentView(@LayoutRes int layoutResID) {
    //调用window对象的setContentView()
    getWindow().setContentView(layoutResID);
    //创建ActionBar
    initWindowDecorActionBar();
}
public Window getWindow() {
        return mWindow;
}

我们发现,ActivitysetContentView()实际上调用的是WindowsetContentView()

mWindow什么时候被创建的呢?

Activity.java

//只截取部分主要代码
final void attach(...) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
            //创建PhoneWindow对象
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        ...
        //通过Context.getSystemService(Context.WINDOW_SERVICE)的方式获取WindowManager的实例
        //给window设置windowManger
        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();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
    }

可以看出,mWindow是在Activityattach()中被创建的,它实际上是一个PhoneWindow对象,PhoneWindow是Android提供的Window唯一实现类。且系统会为每个Activity创建一个与之对应的Window

创建Window后,还会通过Context.getSystemService(Context.WINDOW_SERVICE)的方式获取WindowManager的实例,并设置给WindowWindowManager是一个窗口管理类,稍后还会对WindowManager做更加深入的分析

下面来看PhoneWindowsetContentView()

@Override
public void setContentView(int layoutResID) {
    //先判断mContentParent是否初始化
    if (mContentParent == null) {
        //如果没有初始化调用installDecor
        //当前activity第一次调用sentContentView
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //如果Activity没有过度动画,多次调用sentContentView会走这里
        //移除mContentParent所有内部view
        mContentParent.removeAllViews();
    }

    //判断是否使用了Activity的过度动画
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //设置动画场景
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        //将资源文件通过LayoutInflater对象装换为View树
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        //这个方法在Activity是一个空实现
        //说明在Activity的布局改动时 (setContentView或者addContentView 方法执行完毕后会调用改方法)                  //所以各种View的findViewById方法什么的可以放在这里
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

这里我们重点关注installDecor(),看看这个方法做了什么

  //只截取部分主要代码
  private void installDecor() {
      if (mDecor == null) {
          //如果mDecor为空则创建一个DecorView实例
          mDecor = generateDecor();  
          mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
          mDecor.setIsRootNamespace(true);
          if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
              mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
          }
      }
      if (mContentParent == null) {
            //如果mContentParent为空则通过generateLayout创建一个
          mContentParent = generateLayout(mDecor);
          ...
        }
  }

installDecor() 主要干两件事情

  1. 如果mDecor没有初始化,则通过generateDecor()初始化
  2. 如果mContentParent 没有初始化,则通过generateLayout()初始化

generateDecor()很简单,就是new一个DecorView实例

protected DecorView generateDecor(int featureId) {
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext().getResources());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    //创建new DecorView实例
    return new DecorView(context, featureId, this, getAttributes());
}

下面来分析,generateLayout()

    //只截取部分主要代码
    protected ViewGroup generateLayout(DecorView decor) {
       //根据当前style修饰相应样式
       TypedArray a = getWindowStyle();
       ...
       //一堆if判断,根据设置的主题样式来设置DecorView的风格
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        } else if(...){
            ...
        }  

       //加载窗口布局
       int layoutResource;
       int features = getLocalFeatures();

       //根据features选择不同的layoutResource(布局文件资源)
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if(...){
            ...
        }
       //加载layoutResource
       View in = mLayoutInflater.inflate(layoutResource, null);
         //DecorView是FrameLayout,往DecorView中添加根布局View
       decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
       mContentRoot = (ViewGroup) in;
                
         //这里获取的就是mContentParent
         //ID_ANDROID_CONTENT = com.android.internal.R.id.content;
       ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
       if (contentParent == null) {
           throw new RuntimeException("Window couldn't find content container view");
       }
       ...
       return contentParent;
   }

根据设置的Window主题样式来设置DecorView的风格,接着为DecorView添加子View,而这里的子View则是上面提到的mContentParent,如果上面设置了FEATURE_NO_ACTIONBAR,那么DecorView就只有mContentParent一个子View。可以看我们文章一开始的那张图,图中就很好的阐述了DecorViewTitleViewmContentParent三者之间的关系

所以这也就是为什么,在设置Activity属性的时候,比如requestWindowFeature(Window.FEATURE_NO_TITLE) 需要在setContentView()之前调用才会生效

小结

  1. 一个Activity对应一个WindowWindow是在activity.attach()的时候被创建的
  2. Activity中调用setContentView()实际上是调用了WindowsetContentView()
  3. 调用setContentView()时,会去初始化DecorView以及其内部的TitleViewmContentParent,可以通过设置Window.FEATURE_NO_TITLE使得DecorView内部只存在mContentParent
  4. 我们在xml中定义的layout_activity.xml实际上是mContentParent的一个子View

通过WindowManager管理Window

上文中我们主要讲的是Window的创建、DecorView的创建以及DecorView的子View是如何被添加到DecorView中去的。每个Activity都有一个与之关联的Window,那如何管理Window呢?这时候就需要借助我们的WindowMananger类。WindowManager管理Window实际上就是在管理Window中的DecorView

下面来看一下DecorViewWindowManager是如何关联起来的

从Activity的启动开始分析(对Activity的启动过程感兴趣的还可以看Android应用进程的创建 — Activity的启动流程

  1. 使用代理模式启动到ActivityManagerService中执行
  2. 创建ActivityRecordmHistory记录中
  3. 通过socket通信到Zgote相关类创建process
  4. 通过ApplicatonThreadActivityManagerService建立通信
  5. ActivityManagerService通知ActiveThread启动Activity的创建
  6. ActivityThread创建Activity加入到mActivities中并开始调度Activity执行
  7. activityThread.handleLaunchActivity()

上面就是Activity启动的大致流程,紧接上面第7步,从ActivityThread开始执行到DecorView被添加到WindowManager中的过程如下(代码这里就省略了,有兴趣的自己可以搜索关键代码看一下),这里重点关注handleResumeActivity()方法

activityThread.handleLaunchActivity() —> activityThread.performLaunchActivity()—> activity.attach() —> activity.onCreate()—> activityThread.handleResumeActivity() —>activityThread.performResumeActivity() —> activity.onResume() —> windowManager.addView()

//只截取部分主要代码
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { 
    ...
    // 这里会调用到activity.onResume()方法
    ActivityClientRecord r = performResumeActivity(token, clearHide); 

    if (r != null) {
        final Activity a = r.activity;

        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
                // 获得Window对象
            r.window = r.activity.getWindow(); 
            // 获得Window中的DecorView对象
            View decor = r.window.getDecorView(); 
            decor.setVisibility(View.INVISIBLE);
            // 获得WindowManager对象
            //这个WindowManager就是在activity.attach()的时候和window一起创建的
            ViewManager wm = a.getWindowManager(); 
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                // 调用windowManager.addView方法
                wm.addView(decor, l); 
            }
            ...
            if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                r.activity.mVisibleFromServer = true;
                mNumVisibleActivities++;
                if (r.activity.mVisibleFromClient) {
                        //设置decorView可见
                    r.activity.makeVisible();
                }
            }
        }
    }
}

通过调用windowManager.addView(decor, lp),这样一来,DecorViewWindowManager就建立了联系。WindowManager继承了ViewManger接口,但实际上其本身依然是一个接口,实现类是WindowManagerImpl

//WindowManagerImpl implements WindowManager
//WindowManager extends ViewManager
public interface ViewManager
{
        public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}


//只截取部分主要代码
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;

    public WindowManagerImpl(Context context) {
        this(context, null);
    }

    private WindowManagerImpl(Context context, Window parentWindow) {
        mContext = context;
        mParentWindow = parentWindow;
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }

    public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
        return new WindowManagerImpl(displayContext, mParentWindow);
    }

    //往Window中添加view
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    //更新布局
    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }

    //将删除View的消息发送到MessageQueue中,稍后删除
    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    //立刻删除Window中的view
    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }
}

注意:同样实现了ViewManger接口的还有ViewGroup,我们知道ViewGroup也有addView方法,但是在ViewGroup中是将普通的view或者viewGroup作为Children加入,而在WindowManagerImpl是将DecorView作为根布局加入到PhoneWindow中去,所以两个方法的作用是截然不同的

我们发现,WindowManagerImpl并没有直接实现操作View的相关方法,而是全部交给WindowManagerGlobalWindowManagerGlobal是一个单例类—即一个进程中最多仅有一个。创建WindowManagerGlobal对象的方式如下

public static WindowManagerGlobal getInstance() {
    synchronized (WindowManagerGlobal.class) {
        if (sDefaultWindowManager == null) {
            sDefaultWindowManager = new WindowManagerGlobal();
        }
        return sDefaultWindowManager;
    }
}
ViewManager、WindowManager、WIndowManagerImpl、WIndowManagerGlobal关系UML图

深入分析WindowManagerGlobal

分析WindowManangerGlobal实际上就是分析其内部的三个方法

addView(View view, ViewGroup.LayoutParams params)

该方法的主要作用是将decorView添加到viewRootImp中,通过viewRootImp对其内部的view进行管理

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            //参数检查
            if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
            ...
        //判断是否有父Window,从而调整当前窗口布局参数(layoutParams)
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            //有,调整title和token
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            //无,应用开启了硬件加速的话,decorview就开启
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            if (mSystemPropertyUpdater == null) {
                //创建一个Runnable,用来遍历更新所有ViewRootImpl,更新系统参数
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                //添加到执行队列中
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            //看需要add的view是否已经在mViews中
            //WindowManager不允许同一个View被添加两次
            int index = findViewLocked(view, false);
            if (index >= 0) {
                // 如果被添加过,就看是否在死亡队列里也存在
                if (mDyingViews.contains(view)) {
                    // 如果存在,就将这个已经存在的view对应的window移除
                    mRoots.get(index).doDie();
                } else {
                    //否则,说明view已经被添加,不需要重新添加了
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
            }
                        // 如果属于子Window层级
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                //遍历viewRootImpl集合,看是否存在一个window的IBinder对象和需要添加的window的token一                                       //致,之后赋值引用
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
                        //创建一个ViewRootImpl对象并保存在root变量中
            root = new ViewRootImpl(view.getContext(), display);
                        //给需要添加的view设置params,也就是decorView
            view.setLayoutParams(wparams);
                        //将decoView、布局参数以及新建的ViewRootImpl保存在三个集合中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            try {
                //将decorView设置给ViewRootImpl
                //ViewRootImpl向WMS添加新的窗口、申请Surface以及decorView在Surface上的重绘动作
                //这才是真正意义上完成了窗口的添加操作
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
  • 父窗口修改新窗口的布局参数。可能修改的只有LayoutParams.tokenLayoutParams.mTitle两个属性。mTitle属性不必赘述,仅用于调试。而token属性则值得一提,每一个新窗口必须通过LayoutParams.tokenWMS出示相应的令牌才可以。在addView()函数中通过父窗口修改这个token属性的目的是为了减少开发者的负担。开发者不需要关心token到底应该被设置为什么值,只需将LayoutParams丢给一个WindowManager,剩下的事情就不用再关心了。父窗口修改token属性的原则是:如果新窗口的类型为子窗口(其类型大于等于LayoutParams.FIRST_SUB_WINDOW并小于等于LayoutParams.LAST_SUB_WINDOW),则LayoutParams.token所持有的令牌为其父窗口的ID(也就是IWindow.asBinder()的返回值)。否则LayoutParams.token将被修改为父窗口所属的Activity的ID(也就是在第4章中所介绍的AppToken),这对类型为TYPE_APPLICATION的新窗口来说非常重要。从这点来说,当且仅当新窗的类型为子窗口时addView()parentWindow参数才是真正意义上的父窗口。这类子窗口有上下文菜单、弹出式菜单以及游标等等,在WMS中,这些窗口对应的WindowState所保存的mAttachedWindow既是parentWindow所对应的WindowState。然而另外还有一些窗口,如对话框窗口,类型为TYPE_APPLICATION, 并不属于子窗口,但需要AppToken作为其令牌,为此parentWindow将自己的AppToken赋予了新窗口的的LayoutParams.token中。此时parentWindow便并不是严格意义上的父窗口了
  • 为新窗口创建一个ViewRootImpl对象。顾名思义,ViewRootImpl实现了一个控件树的根。它负责与WMS进行直接的通讯,负责管理Surface,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站。总之,ViewRootImpl是整个控件系统正常运转的动力所在,无疑是本章最关键的一个组件。
  • 将控件、布局参数以及新建的ViewRootImpl对象以相同的索引值添加到三个对应的集合mViewsmParams以及mRoots中,以供之后的查询之需。控件、布局参数以及ViewRootImpl三者共同组成了客户端的一个窗口。或者说,在控件系统中的窗口就是控件、布局参数与ViewRootImpl对象的一个三元组
LayoutParams、View、ViewRootImp的关系.png

updateViewLayout(View view, ViewGroup.LayoutParams params)

该方法的主要作用是更新decorViewlayoutParams,如layoutParams.width从100变为了200,则需要将这个变化通知给WMS使其调整Surface的大小,并让窗口进行重绘

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        //参数检查
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    //将layoutparams保存到decorView中
    view.setLayoutParams(wparams);
    synchronized (mLock) {
        // 获取decorView在三个集合中的索引
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        //更新layoutParams到集合中
        mParams.add(index, wparams);
        //调用viewRootImpl的setLayoutParams()使得新的layoutParams生效
        root.setLayoutParams(wparams, false);
    }
}

removeView(View view)

该方法的作用是从3个集合中删除此Window所对应的元素,包括decorViewlayoutPrams以及viewRootImpl,并要求viewRootImplWMS中删除对应的Window,并释放一切需要回收的资源

public void removeView(View view, boolean immediate) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
  
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        removeViewLocked(index, immediate);//其内部会调用root.die(immediate)
        if (curView == view) {
            return;
        }
      
        throw new IllegalStateException("Calling with view " + view
                + " but the ViewAncestor is attached to " + curView);
    }
}

要求viewRootImplWMS中删除窗口并释放资源的方法是调用viewRootImpl.die()函数。因此可以得出这样一个结论:viewRootImpl的生命从setView()开始,到die()结束

通过ViewRootImp管理View

ViewRootImpl实现了ViewParent接口,它是WindowManagerGlobal工作的实际实现者

我们先来看setView()做了什么

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     synchronized (this) {
         if (mView == null) {
            //mView保存了decorView
             mView = view;
            //mWindowAttributes保存了窗口所对应的LayoutParams
             mWindowAttributes.copyFrom(attrs);     
             attrs = mWindowAttributes;
             ...
                     //请求UI开始绘制
             requestLayout();   
             //初始化mInputChannel
             //InputChannel是窗口接受来自InputDispatcher的输入事件的管道
             //注意,仅当窗口的属性inputFeatures不含有INPUT_FEATURE_NO_INPUT_CHANNEL时
             //才会创建InputChannel,否则mInputChannel为空,从而导致此窗口无法接受任何输入事件
             mInputChannel = new InputChannel();   
             try {
                //通知WindowManagerService添加一个窗口,注册一个事件监听管道
                //用来监听 按键(KeyEvent)和触摸(MotionEvent)事件
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mInputChannel);
                }
                ...
        }
}

setView()内部的执行过程viewRootImp.setView() —> viewRootImp.requestLayout() —> viewRootImp.scheduleTraversals() —> viewRootImp.doTraversal() —> viewRootImp.performTraversals()—>进入view的绘制流程

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //线程检查,这里就是判断更新UI的操作是否在主线程的方法
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
}

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

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

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
        //开始view的绘制
            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
}

总结

本文主要帮助我们了解View的绘制开始之前经过了哪些流程,这有利于我们对整个View的绘制体系有一个更全面的认识,下面我们来具体分析View的
最全的View绘制流程(下)— Measure、Layout、Draw

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容