Andriod View 繪制流程

綜述

每一個Activity組件都有一個關聯(lián)的Window對象洪鸭,用來描述一個應用程序窗口奇颠。每一個應用程序窗口內部又包含有一個View對象,用來描述應用程序窗口的視圖襟士。應用程序窗口視圖是真正用來實現(xiàn)UI內容和布局的盗飒,也就是說,每一個Activity組件的UI內容和布局都是通過與其所關聯(lián)的一個Window對象的內部的一個View對象來實現(xiàn)的陋桂。

基本概念

  • 窗口

對用戶來說, 窗口就是手機屏幕, 包括下面的那些home逆趣、back按鍵、狀態(tài)欄等嗜历。對于Activity來說, 窗口就是除系統(tǒng)狀態(tài)欄和系統(tǒng)按鍵的屏幕區(qū)域, 有window之類的概念宣渗。對于wms來說, 它沒有什么窗口的概念, 它能接受的只是一個個view而已。也就是Activity這里還有Window這個概念, 但在wms那里, 已經沒有window的概念了梨州。
窗口類型分為應用程序窗口: 就是一般應用程序的窗口, 比如我們應用程序的Activity的窗口痕囱。子窗口: 一般在Activity里面的窗口, 比如TabActivity。系統(tǒng)窗口: 系統(tǒng)的窗口, 比如輸入法暴匠、Toast鞍恢、墻紙等等…系統(tǒng)窗口不需要對應任何Activity, 也不需要有父窗口, 對于應用程序而言, 理論上是無法創(chuàng)建系統(tǒng)窗口的, 因為所有的應用程序都沒有這個權限, 然而系統(tǒng)進程卻可以創(chuàng)建系統(tǒng)窗口莺匠。WindowManager.LayoutParams里面有關于各種窗口的type類型定義, type還有個含義就是窗口的z-order, 值越大, 顯示的位置越在上面动猬。

  • Window殖侵、PhoneWindow

頂層窗口樣式和行為的抽象類, 概括了Android窗口的基本屬性和基本功能采郎。該類實例的getDecorView()方法返回的DecorView被用來作為頂層視圖添加到WM中夕凝。
創(chuàng)建時機: ActivityThread.handleLaunchActivity ---> ActivityThread.performLaunchActivity --->Activity.attach

  • WindowManager压怠、WindowManagerImpl芥映、WindowManagerGlobal

與一個特定的Display相關聯(lián), WindowManager主要用來管理窗口的一些狀態(tài)溅话、屬性崇败、view增加盅称、刪除、更新后室、窗口順序缩膝、消息收集和處理等。它面向的對象一端是屏幕, 另一端就是 view , 直接忽略我們以前的 Activity 或者 Dialog 之類的東東岸霹。WindowManager是一個接口類, 其真正的實現(xiàn)是WindowManagerImpl, 后者同時也是整個應用程序中所有Window的管理者疾层。

  • Activity

Activity是支持顯示UI的, 但不直接管理view樹或者ViewRoot, Activity并沒有與這兩者產生直接的聯(lián)系, 是通過中間 “Window”的對象。
創(chuàng)建過程:
1>贡避、使用代理模式啟動到ActivityManagerService中執(zhí)行痛黎;
2>、 創(chuàng)建ActivityRecord到mHistory記錄中刮吧;
3>湖饱、 通過socket通信到Zgote相關類創(chuàng)建process;
4>杀捻、通過ApplicatonThread與ActivityManagerService建立通信井厌;
5>、ActivityManagerService通知ActiveThread啟動Activity的創(chuàng)建致讥;
6>仅仆、ActivityThread創(chuàng)建Activity加入到mActivities中并開始調度Activity執(zhí)行;
7>垢袱、ActivityThread.handleLaunchActivity ---> ActivityThread.performLaunchActivity

screenshot

  • ViewRoot墓拜、ViewRootImpl

任何顯示在設備中的窗口如: Activity、Dialog等, 都包含一個ViewRoot實例请契。ViewRoot可以被理解為“View樹的管理者”, ViewRoot中的mView成員變量指向的就是它所管理的View樹的根咳榜。ViewRoot的核心任務就是與WindowManagerService進行通信, 從ViewRootImpl到WMS間的通信利用的是IWindowSession, 而反方向則是由IWindow來完成的。ViewRoot與ViewRootImpl的功能是一樣的, 只不過是Android不同版本的不同稱呼姚糊。
創(chuàng)建時機: ActivityThread.handleResumeActivity ---> WindowManager.addView---> WindowManagerGlobal.addView添加一個?view到VM中時, 與添加的view實例一一對應贿衍。

  • AcitivityManagerService

AMS提供了一個ArrayList mHistory來管理所有的activity, activity在AMS中的形式是ActivityRecord, task在AMS中的形式為TaskRecord, 進程在AMS中的管理形式為ProcessRecord。是個獨立的系統(tǒng)服務進程救恨。

  • ActivityThread

管理應用進程的主線程的執(zhí)行(相當于普通Java程序的main入口函數(shù)), 并根據(jù)AMS的要求(通過IApplicationThread接口, AMS為Client贸辈、ActivityThread.ApplicationThread為Server)負責調度和執(zhí)行activities、broadcasts和其它操作。ActivityThread是每一個應用程序所在進程的主線程, 循環(huán)消息處理擎淤。ActivityThread與AcitivityManagerService的通信是屬于進程間通信, 使用binder機制奢啥;

  • WindowManagerService

對系統(tǒng)中的所有窗口進行管理。WindowManager是運行在Application process中的, WindowManagerService是在system_server進程中運行, 兩者的通信是通過中間的會話層IWindowSession來進行的嘴拢。

附:相關簡化類結構

public class Activity {
    private Window mWindow;

    final void attach(...) {
        mWindow = PolicyManager.makeNewWindow(this);
        mWindow.setCallback(this);
    }
}

public class PhoneWindow extends Window{
    private DecorView mDecor;
}

public final class WindowManagerGlobal {
    //表示View樹的根節(jié)點
    private final ArrayList<View> mViews = new ArrayList<View>();
    //表示ViewRootImpl
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    //表示
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
}

public final class ActivityThread {
    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    }
    final void handleResumeActivity(...) {
    }
}

二桩盲、Android消息流與Activity中xml布局文件的解析

2.1 消息流

WmS收到用戶消息后需要把消息派發(fā)到窗口, View類本身并不能直接接收WmS傳遞過來的消息, 真正接收用戶消息的必須是IWindow類, 而實現(xiàn)IWindow類的是ViewRoot.W類。
  觸屏消息 ----> WindowManagerService ----> ViewRoot ----> decor view ----> Activity ----> 傳遞給指定的View席吴。

2.2 xml布局文件的解析相關類及流程

2.2.1 ViewGroup.LayoutParams類及其派生類

用來保存xml中每個控件的屬性值赌结。View通過LayoutParams類告訴其父視圖它想要的大小(即, 長度和寬度), 因此, 每個View都包含一個ViewGroup.LayoutParams類或者其派生類。

public class View implements Drawable.Callback, KeyEvent.Callback,
       AccessibilityEventSource {
   ...
   protected ViewGroup.LayoutParams mLayoutParams;
   ...
}

ViewGroup子類可以實現(xiàn)自定義LayoutParams, 自定義LayoutParams提供了更好地擴展性, ViewGroup.LayoutParams及其常用派生類的類圖(部分類圖)如下:


screenshot

2.2.3 xml布局文件的解析流程

LayoutInflater利用XML解析器將布局文件解析成一個完整的View樹, 所有Xxx.xml的布局文件都需要解析成一個完整的View樹孝冒。

    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        View result = root;

        // Temp is the root view that was found in the xml
        final View temp = createViewFromTag(root, name, attrs, false);

        ViewGroup.LayoutParams params = null;
        if (root != null) {
            // Create layout params that match root, if supplied
            params = root.generateLayoutParams(attrs);
            if (!attachToRoot) {
                // Set the layout params for temp if we are not
                // attaching. (If we are, we use addView, below)
                temp.setLayoutParams(params);
            }
        }

        // Inflate all children under temp
        rInflate(parser, temp, attrs, true, true);

        // We are supposed to attach all the views we found (int temp)
        // to root. Do that now.
        if (root != null && attachToRoot) {
            root.addView(temp, params);
        }

        // Decide whether to return the root that was passed in or the
        // top view found in xml.
        if (root == null || !attachToRoot) {
            result = temp;
        }

        return result;
    }

從上面得知, 我們將View的AttributeSet屬性傳遞給generateLayoutParams()方法, 讓其構建合適地LayoutParams對象柬姚,并且初始化屬性值weight和height。但更重要的是, ViewGroup的子類可以重載generateLayoutParams方法, 返回特定的LayoutParams對象, 例如: 對于LinearLayout而言, 則是LinearLayout.LayoutParams對象庄涡。

三量承、Android繪制流程

3.1 繪制起點: DecorView添加到窗口上

我們知道Activity中的PhoneWindow對象?會創(chuàng)建了一個DecorView(父類為FrameLayout)窗口頂層視圖, 然后通過LayoutInflater將xml內容布局解析成View樹形結構添加到DecorView頂層視圖中id為content的FrameLayout父容器上面。到此, 我們已經知道Activity的content內容布局最終會添加到DecorView窗口頂層視圖上面穴店。那么, DecorView是怎么添加到窗口的呢撕捍?這時候我們不得不從Activity是怎么啟動的說起, 當Activity初始化 Window和將布局添加到PhoneWindow的內部類DecorView類之后, ActivityThread類會調用handleResumeActivity方法將頂層視圖DecorView添加到窗口上。

handlerResumeActivity方法的實現(xiàn):

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        ...
            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 (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
            }
    ...
    }

WindowManagerImpl 中addView方法:

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

WindowManagerGlobal 中addView方法:

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

        synchronized (mLock) {
            ...
            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
            
            ///對每個窗口皆構建一個ViewRoot對象
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);

            ////將窗口對應地view泣洞、root忧风、wparams保存在屬性集合中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            // 調用ViewRoot對象去通知系統(tǒng)添加一個窗口
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            ...
        }
    }

ViewRootImpl中setView的方法:

    View mView; //所有窗口地root View
    final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                mWindowAttributes.copyFrom(attrs);    //保存WindowManager.LayoutParams屬性值 
                attrs = mWindowAttributes;
                ...
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                requestLayout();   //請求UI開始繪制。
                mInputChannel = new InputChannel();   //創(chuàng)建一個InputChannel對象斜棚,接受消息  
                try {
                    //通知WindowManagerService添加一個窗口,注冊一個事件監(jiān)聽管道, 用來監(jiān)聽: 按鍵(KeyEvent)和觸摸(MotionEvent)事件阀蒂。
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
                }
                ...
        }
    }

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

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

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

    void doTraversal() {
        if (mTraversalScheduled) {
            ...
            try {
                performTraversals();
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            ...
        }
    }
screenshot

ViewRootImpl中的方法

private void performTraversals() {
    ...
    windowSizeMayChange |= measureHierarchy(host, lp, res,desiredWindowWidth,     desiredWindowHeight);
    ...
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
    performDraw();
    ...
}

3.2 開始繪制performTraversals

View系統(tǒng)的繪制流程會從ViewRootImpl的performTraversals()方法中開始, 每一個視圖的繪制過程都必須經歷三個最主要的階段onMeasure()、onLayout()和onDraw()弟蚀。

3.2.1 第一步 onMeasure 決定View的大小

screenshot

measure函數(shù)的作用是為整個View樹計算實際的大小, 設置每個View對象的布局大性橄肌(“窗口”大小)义钉。實際對應屬性就是View中的mMeasuredHeight(高)和mMeasureWidth(寬)昧绣。方法中參數(shù)widthMeasureSpec和heightMeasureSpec, 這兩個值分別用于確定視圖的寬度和高度的規(guī)格和大小。
MeasureSpec的值由specSize和specMode共同組成的, 其中specSize記錄的是大小, specMode記錄的是規(guī)格捶闸。

  • EXACTLY 表示父視圖希望子視圖的大小應該是由specSize的值來決定的夜畴。子元素將被限定在給定的邊界里而忽略它本身大小删壮;
  • AT_MOST 表示子視圖最多只能是specSize中指定的大小, 開發(fā)人員應該盡可能小得去設置這個視圖, 并且保證不會超過specSize贪绘。
  • UNSPECIFIED 表示開發(fā)人員可以將視圖按照自己的意愿設置成任意的大小, 沒有任何限制。這種情況比較少見, 不太會用到央碟。

View中的方法:

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
                MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchingSize = isExactly &&
                getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
                getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        if (forceLayout || !matchingSize &&
                (widthMeasureSpec != mOldWidthMeasureSpec ||
                        heightMeasureSpec != mOldHeightMeasureSpec)) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                ...
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
        ...
    }

    //View中onMeasure默認實現(xiàn)
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        ...
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
        ...
    }

measure()這個方法是final的, 因此我們無法在子類中去重寫這個方法, 說明Android是不允許我們改變View的measure框架的税灌。然后在第9行調用了onMeasure()方法, 這里才是真正去測量并設置View大小的地方。之后會在onMeasure()方法中調用setMeasuredDimension()方法來設定測量出的大小, 這樣一次measure過程就結束了。
  當然, 一個界面的展示可能會涉及到很多次的measure, 因為一個布局中一般都會包含多個子視圖菱涤,每個視圖都需要經歷一次measure過程苞也。由父視圖在onMeasure中循環(huán)調用ViewGroup中的measureChildWithMargins實現(xiàn)子視圖的measure過程。

FrameLayout中方法:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        ...
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ...
            }
        }
        ...
    }

ViewGroup中的方法:

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

3.2.2 第二步 onLayout 決定View在ViewGroup中的位置

ViewRootImpl的performTraversals()方法會在measure結束后繼續(xù)執(zhí)行, 為視圖進行布局的, 也就是確定視圖的位置粘秆。并調用View的layout()方法來執(zhí)行此過程如迟。
ViewRootImpl中的方法

private void performLayout(WindowManager.LayoutParams lp, 
            int desiredWindowWidth, int desiredWindowHeight) {
    ...
    final View host = mView;
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
}

可以看到, 這里還把剛才測量出的寬度和高度傳到了layout()方法中.

View中的方法:

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

layout()方法接收四個參數(shù), 分別代表著左、上攻走、右殷勘、下的坐標, 當然這些坐標是相對于當前視圖的父視圖而言的。在layout()方法中, 首先會調用setFrame()方法來判斷視圖的大小是否發(fā)生過變化, 以確定有沒有必要對當前的視圖進行重繪, 同時還會在這里把傳遞過來的四個參數(shù)分別賦值給mLeft陋气、mTop劳吠、mRight和mBottom這幾個變量引润。
  View中的onLayout()方法就是一個空方法, 因為onLayout()過程是為了確定視圖在布局中所在的位置, 而這個操作應該是由布局來完成的, 即父視圖決定子視圖的顯示位置巩趁。

ViewGroup中的方法

    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

ViewGroup中的onLayout()方法是一個抽象方法, 這就意味著所有ViewGroup的子類都必須重寫這個方法。在onLayout()過程結束后, 我們就可以調用getWidth()方法和getHeight()方法來獲取視圖的寬高了淳附。

getWidth()方法和getMeasureWidth()方法到底有什么區(qū)別?
getMeasureWidth()方法在measure()過程結束后就可以獲取到了, 而getWidth()方法要在layout()過程結束后才能獲取到议慰。另外, getMeasureWidth()方法中的值是通過setMeasuredDimension()方法來進行設置的, 而getWidth()方法中的值則是通過視圖右邊的坐標減去左邊的坐標計算出來的。

3.2.3 第三步 onDraw

measure和layout的過程都結束后, 接下來就進入到draw的過程了奴曙。

screenshot

ViewRootImpl里的方法

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        ...
        // Draw with software renderer.
        final Canvas canvas;
        ...//設置canvas
        mView.draw(canvas);
        ...
    }

View中的方法:

    public void draw(Canvas canvas) {
        ...
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas);

            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // we're done...
            return;
        }
        ...
    }

    protected void onDraw(Canvas canvas) {
    }

    /**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     */
    protected void dispatchDraw(Canvas canvas) {

    }

onDraw為空方法, 因為每個視圖的內容部分肯定都是各不相同的, 這部分的功能需交給子類去實現(xiàn)别凹。dispatchDraw這一步的作用是對當前視圖的所有子視圖進行繪制。但如果當前的視圖沒有子視圖, 那么也就不需要進行繪制了洽糟。因此你會發(fā)現(xiàn)View中的dispatchDraw()方法又是一個空方法炉菲,而ViewGroup的dispatchDraw()方法中就會有具體的繪制代碼。onDrawScrollBars 是對視圖的滾動條進行繪制坤溃。

四拍霜、其他

窗口的UI最終是需要通過SurfaceFlinger服務來統(tǒng)一渲染的, 而SurfaceFlinger服務在渲染窗口的UI之前, 需要計算基于各個窗口的Z軸位置來計算它們的可見區(qū)域。而WindowManagerService服務就是負責計算好每一個窗口的Z軸位置之后, 還需要將它們設置到SurfaceFlinger服務中去, 以便SurfaceFlinger服務可以正確地渲染每一個窗口的UI薪介。

參考文獻:
http://blog.csdn.net/guolin_blog/article/details/16330267
http://www.cnblogs.com/franksunny/archive/2012/04/20/2459738.html
http://blog.csdn.net/qinjuning/article/details/8051811
http://blog.csdn.net/qinjuning/article/details/8074262
http://bbs.51cto.com/thread-1072344-1.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末祠饺,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子汁政,更是在濱河造成了極大的恐慌道偷,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件记劈,死亡現(xiàn)場離奇詭異勺鸦,居然都是意外死亡,警方通過查閱死者的電腦和手機目木,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門换途,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事怀跛【啻” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵吻谋,是天一觀的道長忠蝗。 經常有香客問我,道長漓拾,這世上最難降的妖魔是什么阁最? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮骇两,結果婚禮上速种,老公的妹妹穿的比我還像新娘。我一直安慰自己低千,他們只是感情好配阵,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著示血,像睡著了一般棋傍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上难审,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天瘫拣,我揣著相機與錄音,去河邊找鬼告喊。 笑死麸拄,一個胖子當著我的面吹牛,可吹牛的內容都是我干的黔姜。 我是一名探鬼主播拢切,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼地淀!你這毒婦竟也來了失球?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤帮毁,失蹤者是張志新(化名)和其女友劉穎实苞,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體烈疚,經...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡黔牵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了爷肝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猾浦。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡陆错,死狀恐怖,靈堂內的尸體忽然破棺而出金赦,到底是詐尸還是另有隱情音瓷,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布夹抗,位于F島的核電站绳慎,受9級特大地震影響,放射性物質發(fā)生泄漏漠烧。R本人自食惡果不足惜杏愤,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望已脓。 院中可真熱鬧珊楼,春花似錦、人聲如沸度液。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恨诱。三九已至媳瞪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間照宝,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工句葵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留厕鹃,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓乍丈,卻偏偏與公主長得像剂碴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子轻专,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容