1. 程式人生 > >Android應用層View繪製流程之DecorView與ViewRootImpl

Android應用層View繪製流程之DecorView與ViewRootImpl

概述

一直對Android中View的整個繪製流程不是很瞭解,View是怎麼新增到Activity當中去的?當View中的內容發生改變的時候是怎樣執行介面的重新整理的?因此,今天準備從原始碼的角度來對View的整個繪製流程來進行分析,原始碼基於API25。由於篇幅限制,這篇文章只分析頂層檢視DecorView的顯示邏輯,具體的View樹繪製三部曲:measure,layout,draw將在下篇博文進行深入剖析。

從Activity的setContentView方法說起

我們都知道給Activity設定佈局通常就是呼叫其setContentView方法,這個方法有幾個過載的方法,如下所示:

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

 public void setContentView(View view) {
       getWindow().setContentView(view);
       initWindowDecorActionBar();
   }

public void setContentView(View view, ViewGroup.LayoutParams params) {
       getWindow().setContentView(view, params);
       initWindowDecorActionBar();
}

從上面的三個方法可以看出其均呼叫了getWindow()的相對應的方法,我們來看getWindow()方法:

public Window getWindow() {
       return mWindow;
   }

可以看出這個方法返回的是一個Window型別的變數mWindow,那麼這個mWindow肯定是在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) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); .......... .......... }

mWindow是一個PhoneWindow型別的變數,其實我們通過檢視抽象類Window最開始的簡介可以知道,PhoneWindow是Window的唯一子類!

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {

接下來檢視PhoneWindow的setContentView方法,跟Activity對應,也有三個過載的方法:


 @Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

   if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

@Override
public void setContentView(View view) {
    setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

對setContentView(View view,ViewGroup.LayoutParams)方法進行分析:

  • 首先判斷mContentParent是否為null,如果為null的話就執行方法installDecor,這個mContentParent是一個ViewGroup型別,這個方法如下所示:
 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
        ........
        .......
}

generateDecor用於產生mDecor,mDecor是DecorView型別,是整個Activity的頂層檢視,DecorView是FrameLayout的子類,有興趣的可以看看DecorView原始碼,這裡只是給個結論。

  • 然後是generateLayout方法,這個方法很長,看關鍵程式碼:

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      if (contentParent == null) {
          throw new RuntimeException("Window couldn't find content container view");
      }
    ....
    return contentParent;

從上述程式碼可以看出,上面講到的mContentParent是頂層檢視mDecor中的一個子View,這個子View的id為:

  /**
    * The ID that the main layout in the XML layout file should have.
    */
   public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
  • 因此,執行了installDecor方法之後就得到了mDecor和mContentParent,然後是一句很關鍵的程式碼,如下所示:
     mContentParent.addView(view, params);

通過這句程式碼就把我們在Activity當中設定的佈局檢視加入了mContentParent裡面。

層次關係為:DecorView > contentParent > Activity中的佈局。

由於mContentParent是ViewGroup型別,檢視ViewGroup#addView方法,如下所示:

/**
    * Adds a child view with the specified layout parameters.
    *
    * <p><strong>Note:</strong> do not invoke this method from
    * {@link #draw(android.graphics.Canvas)}, {@link #onDraw(android.graphics.Canvas)},
    * {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
    *
    * @param child the child view to add
    * @param index the position at which to add the child or -1 to add last
    * @param params the layout parameters to set on the child
    */
   public void addView(View child, int index, LayoutParams params) {
       if (DBG) {
           System.out.println(this + " addView");
       }

       if (child == null) {
           throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
       }

       // addViewInner() will call child.requestLayout() when setting the new LayoutParams
       // therefore, we call requestLayout() on ourselves before, so that the child's request
       // will be blocked at our level
       requestLayout();
       invalidate(true);
       addViewInner(child, index, params, false);
   }

這個addView方法最後會呼叫requestLayout()和invalidate()方法,這兩個方法會導致整個View樹進行重新繪製,這樣就把我們在Activity當中自定義的佈局檔案給顯示出來了!

整個過程總結如下:

Activity.setContentView -> PhoneWindow.setContentView ->初始化PhoneWindow中的mDecor和mContentParent -> 把Activity當中的佈局檢視加入mContentParent -> 導致整個View樹進行重新繪製,從而把佈局檔案顯示出來!

DecorView是怎麼顯示出來的

前面說了DecorView是整個Activity的頂層檢視,那麼這個DecorView是怎麼顯示出來了的呢?主要實現過程在ActivityThread的handleResumeActivity方法裡面,如下所示:

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
        return;
    }

    // If we are getting ready to gc after going to the background, well
    // we are back active so skip it.
    unscheduleGcIdler();
    mSomeActivitiesChanged = true;

    // TODO Push resumeArgs into the activity for consideration
    r = performResumeActivity(token, clearHide, reason);

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

        if (localLOGV) Slog.v(
            TAG, "Resume " + r + " started activity: " +
            a.mStartedActivity + ", hideForNow: " + r.hideForNow
            + ", finished: " + a.mFinished);

        final int forwardBit = isForward ?
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

        // If the window hasn't yet been added to the window manager,
        // and this guy didn't finish itself or start another activity,
        // then go ahead and add the window.
        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            try {
                willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                        a.getActivityToken());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            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;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }

        ................................
    }
}

如上述方法所示,首先從ActivityThread儲存的mActivities列表裡面得到一個ActivityClientRecord物件,這個物件儲存了Activity的一些資訊。

  • 得到Activity之後呼叫View decor = r.window.getDecorView();方法得到頂層檢視DecorView,這個檢視前面說過是儲存在PhoneWindow裡面,也就是一個Activity對應一個 PhoneWindow,從而對應一個DecorView。
  • 然後是呼叫ViewManager wm = a.getWindowManager();方法得到Activity當中的WindowManager物件,那為什麼返回的是ViewManager物件呢?檢視WindowManager介面,如下所示,發現WindowManager是繼承自ViewManager介面的。
      public interface WindowManager extends ViewManager {

ViewManager介面如下所示,從註釋可以看出這個介面主要是用來往Activity當中新增或者移除View。

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}
  • 得到Activity當中的WindowManager之後,呼叫wm.addView(decor, l);方法,就把DecorView加入了WindowManager。我們知道WindowManager只是一個介面,具體的實現類是WindowManagerImpl,看下其addView方法:
@Override
   public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
       applyDefaultToken(params);
       mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
   }

發現這個addView方法其實是呼叫了成員變數mGlobal的addVeiw方法,mGlobal是WindowManagerGlobal型別,我們來看下其addView方法,如下所示:

public void addView(View view, ViewGroup.LayoutParams params,
           Display display, Window parentWindow) {
       ........................
       ViewRootImpl root;
       View panelParentView = null;

      .........................
           root = new ViewRootImpl(view.getContext(), display);

           view.setLayoutParams(wparams);

           mViews.add(view);
           mRoots.add(root);
           mParams.add(wparams);
      ...........................

       // do this last because it fires off messages to start doing things
       try {
           root.setView(view, wparams, panelParentView);
       } catch (RuntimeException e) {
           // BadTokenException or InvalidDisplayException, clean up.
           synchronized (mLock) {
               final int index = findViewLocked(view, false);
               if (index >= 0) {
                   removeViewLocked(index, true);
               }
           }
           throw e;
       }
   }

通過上述程式碼可以發現頂層檢視DecorView最終被加入到了ViewRootImpl裡面,且應該是在其setView方法裡面執行了某些操作,導致DecorView被顯示出來。這個方法比較長,大家可以自行檢視,在裡面有一句關鍵的程式碼:

// 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();

requestLayout方法如下:

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

在這個方法當中呼叫了scheduleTraversals方法,如下所示:

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

在這個方法當中把mTraversalRunnable給post到了訊息佇列裡面,來看看這個Runnable裡面執行了什麼操作:

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

從上面可以看出在TraversalRunnable裡面執行了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方法,這個方法最終負責整個View樹的繪製流程,因此這個方法比較關鍵。這個方法比較長,其負責繪製的View樹的核心語句如下,其中mView就是頂層檢視DecorView。

private void performTraversals() {
        ......
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
        ......
        mView.draw(canvas);
        ......
    }

上面對頂層檢視DecorView的顯示機制進行了深入剖析,通過一層層分析,最終得出ViewRootImpl負責整個View樹的繪製。measure,layout,draw是View樹繪製三個主要流程,只有理解了這三個基本流程的原理,在自定義View的時候才能做到遊刃有餘(當然還有View事件分發機制也很關鍵)!關於這三個流程的具體細節剖析將在下一篇部落格中進行講解。感謝大家的閱讀!

相關推薦

Android應用View繪製流程DecorViewViewRootImpl

概述 一直對Android中View的整個繪製流程不是很瞭解,View是怎麼新增到Activity當中去的?當View中的內容發生改變的時候是怎樣執行介面的重新整理的?因此,今天準備從原始碼的角度來對View的整個繪製流程來進行分析,原始碼基於API25。由於

Android應用View繪製流程原始碼分析

1 背景 看見沒有,如上圖中id為content的內容就是整個View樹的結構,所以對每個具體View物件的操作,其實就是個遞迴的實現。 前面《Android觸控式螢幕事件派發機制詳解與原始碼分析一(View篇)》文章的3-1小節說過And

Android系統原始碼分析--View繪製流程-setContentView

上一篇分析了四大元件之ContentProvider,這也是四大元件最後一個。因此,從這篇開始我們分析新的篇章--View繪製流程,View繪製流程在Android開發中佔有非常重要的位置,只要有檢視的顯示,都離不開View的繪製,所以瞭解View繪製原理對於應用開發以及系統的學習至關重要。由於View

ndroid系統原始碼分析--View繪製流程-inflate

上一章我們分析了Activity啟動的時候呼叫setContentView載入佈局的過程,但是分析過程中我們留了兩個懸念,一個是將資原始檔中的layout中xml佈局檔案通過inflate載入到Activity中的過程,另一個是開始測量、佈局和繪製的過程,第二個我們放到measure過程中分析,這一篇先

Android Activity的UI繪製流程setContentView方法詳解

概述 對於Android開發人員來說,想必對setContentView方法不會陌生,每當我們建立一個Activity時,都會重寫該Activity的onCreate方法,在該方法中我們必須要呼叫setContentView方法來顯示我們指定的佈局或者View

Android應用View觸控事件分發機制

概述 前兩篇部落格從原始碼的角度對View繪製流程進行了分析,那麼當用戶需要跟View進行互動的時候,比如點選按鈕的時候,按鈕是如何得到點選事件的呢?當用戶在螢幕上進行點選或觸控的時候,事件是如何傳遞到各個View的呢?這個就是本篇部落格研究的點:View事件

Android GUIView繪製流程

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested =

Android View繪製流程 Layout 和 Draw 過程詳解 (二)

View 的繪製系列文章: Android View 繪製流程之 DecorView 與 ViewRootImpl Android View 的繪製流程之 Measure 過程詳解 (一) Android View 的繪製流程之 Layout 和 Draw 過程詳解 (二) 在上一篇 

Android應用到Framework到HAL再到驅動的整個流程分析

本篇參考老羅的例項進行總結。老羅寫六篇,層層巢狀,他告訴了我們流程,但沒有說程式設計思想,所以,即使知道怎麼做也很快會忘調,因此打算總結下每層之間是怎麼呼叫的,以加深印象。不對細節進行探討。細節可以參見老羅的blog:http://blog.csdn.net/luoshen

Android View繪製流程

或許你已經看到過很多部落格總結的View的繪製流程的.我這篇部落格是跟著原始碼去看,對自己學到的知識加以印證.水平有限,僅供參考,如有錯誤,歡迎指正 我在之前的部落格就已經說明了Activity的啟動到顯示的相關流程,現在我們來看下View具體的工作流程. 上次

Android開發——View繪製流程

網上講解View的繪製流程有很多優秀的文章。主要分為三個步驟:分別是measure、layout和draw。measure根據父佈局的尺寸以及自己想要的尺寸得到最終自己的尺寸,layout用於確定子View的位置,draw負責繪製自己。View分為View和Vi

android-進階(3)-自定義view(2)-AndroidView繪製流程以及相關方法的分析

最近正在學自定義view,這篇文章主要講view的繪製流程和一些相關的方法,淺顯易懂,寫的非常好,忍不住就轉載了。             前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。

AndroidView繪製流程以及invalidate()等相關方法分析

            前言: 本文是我讀《Android核心剖析》第13章----View工作原理總結而成的,在此膜拜下作者 。同時真摯地向渴望瞭解Android 框架層的網友,推薦這本書,希望你們能夠在Android開發裡學到更多的知識 。             整個V

Android View繪製流程原始碼淺析

Android中View的繪製是一個面試的必答題,網上他人的博文也很多,本文旨在分析出大致流程。 廢話不說,read the fucking source code! 先從ActivityThread主執行緒啟動Activity說起,當Activity初始化 Window和

Android View 繪製流程(Draw) 完全解析

前言 前幾篇文章,筆者分別講述了DecorView,measure,layout流程等,接下來將詳細分析三大工作流程的最後一個流程——繪製流程。測量流程決定了View的大小,佈局流程決定了View的位置,那麼繪製流程將決定View的樣子,一個View該顯示什麼

Android View框架總結(六)View佈局流程Draw過程

View的Draw時序圖ViewRootImpl.performTraversals過程ViewRootImpl.performDraw過程View.draw方法View.dispatchDraw過程LinearLayout的onDraw過程 View的Draw時序圖

View繪製流程二:View繪製入口原始碼分析

一、回顧 由上一篇筆記 《View的繪製流程之一:setContentView()方法原始碼分析》,我們知道了 Activity 的 Layout 佈局最後會儲存在 DecorView 中的 Layout 佈局中的 FrameLayout 中,但是還沒有進行

Android View 繪製流程 invalidate 和postInvalidate 分析--從原始碼角度

整個View樹的繪製流程是在ViewRootImpl.java類的performTraversals()函式展開的,該函式做的執行過程可簡單概況為  根據之前設置的狀態,判斷是否需要重新計算檢視大小(measure)、是否重新需要佈局檢視的位置(layout

Android View繪製流程看這篇就夠了

前言        自定義View、多執行緒、網路,被認為是Android開發者必須牢固掌握的最基礎的三大基本功。Android View的繪製流程原理又是學好自定義View的理論基礎,所以掌握好View的繪製原理是Android開發進階中無法繞過的一道坎。而關乎到原理

View繪製流程簡述

在view的繪製過程中,一般會分如下三個過程:  measure() —— 測量view的大小  layout() —— 計算view在父view中的位置  draw() —— 繪製view  measure方法總呼叫了onMeasure方法,layout方法