最全的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閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容