所得與所見:[-View周邊-] 框架層

張風捷特烈發表於2019-02-19

紙上得來終覺淺,絕知此事要躬行

零、前言

網上有很多介紹View載入的文章,但緣分是天定的,幸福是自己的
對於一篇文章,你並不能完全把握作者的思路。文章只能起到引導思想的作用。
很多東西不用自己的思想加工一下,是很難入腦的。複雜的原理層更是如此。
如果不畫幾幅圖,過一個月,你的文章看著就覺得不像自己寫的了...

本文焦點
1.Windown物件是何時在哪裡以什麼方式實現的?
2.PhoneWindow中的幾個核心View是何時何地怎麼實現的?
3.Window上何時新增View,ViewRootImpl在哪實現的?
4.ViewRootImpl是如何處理View的,它對View的測量、佈局、繪製有什麼關係?
5.本人作為View的繪製粉,有必要知道View的OnDraw的Canvas物件是哪裡來的?
6.LayoutInflater是如何載入佈局的?
複製程式碼

一、引入

1.類比於散扯

所得與所見:[-View周邊-] 框架層

拿我的手機來說,在物理層面,這View就是由2430*1080=2624400個畫素點陣構成的一塊螢幕。
用ARGB_8888的圖片來說,每個畫素點可以承載256*256*256*256=4294967296種顏色
如果讓我的手機顯示滿屏的顯示ARGB_8888的圖片,則一共可以顯示:
2624400*4294967296=1 1271 7121 7162 2400種圖片,這是多少呢? 也就是16個數量級
地球赤道周長40075.02千米 = 40075020米 =4007502000釐米,
如果每張圖片列印成1釐米高的照片可以繞地球赤道281265圈,是不是很多呢?

|--科普小知識:一個天文單位:1 A.U. = 149597870700米 為地日距離 
1 1271 7121 7162 2400 / 149597870700 00 約為 75 ,也就是從地球連到太陽能連37個來回
複製程式碼

也就是說螢幕能夠儲存的資訊量是巨大的
硬體層也就是如何將螢幕的物理畫素點與要顯示的顏色對應,這裡就不往下扯了
硬體層篇(如果未來我寫得出來的話...機率渺茫),再好好延伸一下


2.Window物件與View

軟體層面來說,螢幕被我們抽象成了一個抽象的Window物件
一共也就近2000行程式碼,定義了Window非常多的抽象行為

顏值擔當Activity的原始碼分析中我們遇到過它,不知你有無印象
Activity在啟動時通過Handler會呼叫ActivityThread的performLaunchActivity方法
在其中activity通過attach方法關聯到Window物件上,而Window就是在attach方法裡例項化的

Window物件的建立.png

---->[ActivityThread#成員變數]------------------
private Window mWindow; // 這就是Window的成員變數

---->[ActivityThread#performLaunchActivity]------------------
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
    window = r.mPendingRemoveWindow;
    r.mPendingRemoveWindow = null;
    r.mPendingRemoveWindowManager = null;
}
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);
        
---->[Activity#attach]------------------
|-- 可以得知重要的一點:mWindow的實現類是PhoneWindow ------------
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) {
        ...
    mWindow = new PhoneWindow(this, window);//<----注意這裡對mWindow進行初始化
    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);
    }
    mUiThread = Thread.currentThread();
       ...
    mWindow.setWindowManager(//設定WindowManager
            (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;
}
複製程式碼

二、PhoneWindow與DecorView :

所在包com.android.internal.policy
接下來就要看這個老祖宗級別的檢視:DecorView,PhoneWindow有一個該類成員變數
在PhoneWindow兩參初始化的時候,如果傳入的Window物件非空,那麼mDecor就直接引用
如果不知道什麼是DecorView,看下圖:

所得與所見:[-View周邊-] 框架層


1.PhoneWindow的建構函式
---->[PhoneWindow的成員變數]------------------
private DecorView mDecor;
private LayoutInflater mLayoutInflater;
ViewGroup mContentParent;

---->[PhoneWindow的建構函式#兩參]------------------
public PhoneWindow(Context context, Window preservedWindow,
        ActivityConfigCallback activityConfigCallback) {
    this(context);//呼叫參構造
    mUseDecorContext = true;
    if (preservedWindow != null) {//----如果傳入的Window不為空----
        mDecor = (DecorView) preservedWindow.getDecorView();//把老祖宗拿到
        mElevation = preservedWindow.getElevation();
        mLoadElevation = false;
        mForceDecorInstall = true;
        getAttributes().token = preservedWindow.getAttributes().token;
    }
    // Even though the device doesn't support picture-in-picture mode,--儘管該裝置不支援“畫中畫”模式,
    // an user can force using it through developer options.--使用者可以通過開發人員選項強制使用它
    boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
            DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
    mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
            PackageManager.FEATURE_PICTURE_IN_PICTURE);
    mActivityConfigCallback = activityConfigCallback;
}

---->[PhoneWindow的建構函式#一參]------------------
public PhoneWindow(Context context) {
    super(context);
    //這裡根據context例項化佈局載入器物件:mLayoutInflater
    mLayoutInflater = LayoutInflater.from(context);
}
複製程式碼

2. DecorView及子佈局的初始化

PhoneWindow上核心View的建立.png

---->[DecorView]------------------
|--它是一個FrameLayout,說明可以隨意新增View
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks{...}


---->[PhoneWindow#setContentView]------------------
|-- 有沒有他鄉遇故知的感覺:setContentView

@Override
public void setContentView(int layoutResID) {
    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;
}

---->[PhoneWindow#installDecor]------------------
private void installDecor() {
   mForceDecorInstall = false;
   if (mDecor == null) {
       mDecor = generateDecor(-1);//<----此處例項化DecorView 
       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);//例項化mContentParent
       

---->[PhoneWindow#generateDecor]------------------
protected DecorView generateDecor(int featureId) {
    ...
    return new DecorView(context, featureId, this, getAttributes());
}

---->[PhoneWindow#generateLayout]------------------
|--這方法挺長的,前面一堆過於屬性或樣式的設定,核心就幾行程式碼,差點沒找著
protected ViewGroup generateLayout(DecorView decor) {
    ...[屬性樣式的設定]...
    mDecor.startChanging();
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //主角登場:contentParent 通過一個framework層的ID_ANDROID_CONTENT獲取
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ...[屬性樣式的設定]...
    return contentParent;
}

---->[Window#ID_ANDROID_CONTENT]------------------
|--該id在Window類中定義
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

這樣兩個檢視的大佬[mDecor和mContentParent]就例項化完成
setContentView方法中 mLayoutInflater.inflate(layoutResID, mContentParent);
為將子元素新增到mContentParent這個ViewGroup中,我的好奇心讓我非常想知道
com.android.internal.R.id.content對應的佈局是什麼?
複製程式碼

3.尋找mContentParent對應的xml

這裡有點意思findViewById 原本是View的方法,查詢出該View內部對應id名的佈局
PhoneWindow裡怎麼就直接呼叫了? 答案在它老爸Window裡
核心就是要找到這個com.android.internal.R.id.content

---->[Window#findViewById]------------------
@Nullable //可見是通過getDecoFrView()來操作的
public View findViewById(@IdRes int id) {
    return getDecorView().findViewById(id);
}
---->[Window#findViewById]------------------
public abstract View getDecorView();

---->[PhoneWindow#findViewById]------------------
|--可見是通過最頂層的mDecor來findViewById的
@Override
public final View getDecorView() {
    if (mDecor == null) {
        installDecor();
    }
    return mDecor;
}


---->[PhoneWindow#generateLayout]------------------
|--在這裡可以看到使用了一個叫screen_simple佈局(踏破鐵鞋終尋處...)
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
    layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
    // Embedded, so no decoration is needed.
    layoutResource = R.layout.screen_simple;
    // System.out.println("Simple!");
}

複製程式碼

總算理清為什麼檢視樹的層次是這樣的了

檢視樹.png


三、Android眾管家之一:WindowManager

1.ActivityThread中mWindowManager的例項化

說實話,現在看到XXXManager我的心都有點方...

WindowManager相關.png

---->[WindowManager]------------------
public interface WindowManager extends ViewManager {}

---->[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);
}
|-- WindowManager是一個介面,繼承自ViewManager,定義了WindowManager應有的行為動作
|-- ViewManager只有三個介面方法:新增、更新、移除


---->[Activity#attach]------------------
|--回到例項化mWindow之後,會將Window設定給WindowManager
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();

---->[Window#setWindowManager]------------------
public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
    setWindowManager(wm, appToken, appName, false);
}

void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    mAppToken = appToken;
    mAppName = appName;
    mHardwareAccelerated = hardwareAccelerated
            || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
    if (wm == null) {//如果為空通過,Context.WINDOW_SERVICE獲取WindowManager物件
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    //---劃重點,WindowManager的實現類是WindowManagerImpl
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
複製程式碼

2. handleResumeActivity中新增檢視

這部分在Activity篇一帶而過,這裡詳細梳理一下

ViewRootImpl的初始化.png

---->[ActivityThread#handleResumeActivity]------------------
//回撥onResume後進行window介面顯示
 if (r.window == null && !a.mFinished && willBeVisible) {
     r.window = r.activity.getWindow();//獲取Window
     View decor = r.window.getDecorView();//獲取DecorView
     decor.setVisibility(View.INVISIBLE);//DecorView設成可見
     ViewManager wm = a.getWindowManager();//獲取WindowManager,這裡用父介面承接型別
     WindowManager.LayoutParams l = r.window.getAttributes();
     a.mDecor = decor;
     l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
     l.softInputMode |= forwardBit;
     if (r.mPreserveWindow) {//如果之前已經有儲存的Window
         a.mWindowAdded = true;
         r.mPreserveWindow = false;
         ViewRootImpl impl = decor.getViewRootImpl();//拿來用
         if (impl != null) {
             impl.notifyChildRebuilt();
         }
     }
     if (a.mVisibleFromClient && !a.mWindowAdded) {
         a.mWindowAdded = true;
         //---劃重點,這裡WindowManager要將DecorView加入檢視了
         wm.addView(decor, l);
     }


---->[WindowManagerImpl#addView]----------------
|-- 這裡呼叫了mGlobal的addView
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    
---->[WindowManagerImpl#成員變數]----------------
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();


---->[WindowManagerGlobal#addView]----------------
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    ...
    ViewRootImpl root;
    ...
    root = new ViewRootImpl(view.getContext(), display);//這裡建立了ViewRootImpl
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    }
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
       ...
    }
}
複製程式碼

3.View的幕後大Boss--ViewRootImpl

[烽火狼煙Handler]篇提過,那個著名的異常:非main執行緒無法更新UI 。
為什麼所有非main執行緒重新整理View都會走ViewRootImpl.requestLayout方法?

所得與所見:[-View周邊-] 框架層

ViewRootImpl#setView的初始化.png

|---注意ViewRootImpl不是一個View,只是實現了ViewParent這個操作介面
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {

---->[ViewRootImpl#setView]---------------------------
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;//二話不說,私家珍藏先
            ...
            //requestLayout---------用來檢查執行緒和發出遍歷任務
            requestLayout();
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                mInputChannel = new InputChannel();
            }
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                //將該Window新增到螢幕。
                //mWindowSession實現了IWindowSession介面,它是Session的客戶端Binder物件.
                //addToDisplay是一次AIDL的跨程式通訊,通知WindowManagerService新增IWindow
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
            ...
            view.assignParent(this);
            ...
        }
    }
|--mWindowSession 何許人也 ?  [IWindowSession]人士
複製程式碼

可見IWindowSession是通過AIDL來實現的,按照套路,找他的實現類

IWindowSession.png

|---實現類IWindowSession.Stub,就他了
final class Session extends IWindowSession.Stub{
    ...
    final WindowManagerService mService;
}

---->[Session#addToDisplay]-----------------
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        Rect outOutsets, InputChannel outInputChannel) {
        //這裡通過mService將來新增到Window上
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

---->[WindowManagerService]-----------------
|---實現類IWindowManager的AIDL介面,到此為止...
public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
複製程式碼

4.ViewRootImpl的requestLayout方法

顧名思義,是發起佈局請求,這部分參考了這篇文章,寫得挺棒的,就是有點晦澀,這裡加圖說明一下

ViewRootImpl#requestLayout.png

---->[ViewRootImpl#requestLayout]--------------------
@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

---->[ViewRootImpl#checkThread]--------------------
|-- 這就是子執行緒無法更新View的原因
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

---->[ViewRootImpl#scheduleTraversals]--------------------
|--遍歷的任務安排
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(//執行mTraversalRunnable這個遍歷任務
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
---->[ViewRootImpl#遍歷任務]--------------------
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
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");
        }
        //劃重點:請求佈局的核心方法
        performTraversals();
        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

---->[ViewRootImpl#performTraversals]--------------------
|---這個一方法一共近1000行,翻著怪累人的...但此方法十分重要
|---分別呼叫mView的measure、layout、draw
    private void performTraversals() {
        final View host = mView;
        ...
        //開始進行佈局準備
        if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null) {
            ...
            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                    
            --->    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    ...
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
                    if (lp.horizontalWeight > 0.0f) {//如果LayoutParams的horizontalWeight大於0 
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;/再次此測量為true
                    }
                    if (lp.verticalWeight > 0.0f) {//如果LayoutParams的verticalWeight大於0 
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;/再次此測量為true
                    }
                    if (measureAgain) {
                        ...
            --->        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
                    layoutRequested = true;
                }
            }
        } 
        ...
        final boolean didLayout = layoutRequested /*&& !mStopped*/ ;
        boolean triggerGlobalLayoutListener = didLayout
                || mAttachInfo.mRecomputeGlobalAttributes;
        if (didLayout) {
    --->    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            ...
        }
        ...
        if (!cancelDraw && !newSurface) {
            if (!skipDraw || mReportNextDraw) {
                ...
    --->        performDraw();
            }
        } else {
           ...
        mIsInTraversal = false;
    }
}
複製程式碼

5.ViewRootImpl為操作View的三大方法

通過上面所以的焦點集中在performMeasure、performLayout、performDraw的身上

---->[ViewRootImpl#performMeasure]--------------------
|--看著真爽...直接讓呼叫mView的measure方法
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);
    }
}

---->[ViewRootImpl#performLayout]--------------------
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    ...
    final View host = mView;//host記錄一下當前View
    ...
    try {
        //此處呼叫了當前View的layout方法
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        mInLayout = false;
        ...略很多
}

---->[ViewRootImpl#performDraw]--------------------
|--這個方法就豪無人性了,核心在draw
private void performDraw() {
    if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
        return;
    }
    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;
    ...
    try {
        draw(fullRedrawNeeded);
        ...
        
---->[ViewRootImpl#draw]--------------------
|--關於這個方法我翻了一下,感興趣的只有這三行
mAttachInfo.mTreeObserver.dispatchOnDraw();
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)

|--這就要看一下mAttachInfo這個成員變數了
---->[ViewRootImpl#成員變數mAttachInfo]--------------------
final View.AttachInfo mAttachInfo;

---->[ViewRootImpl#ViewRootImpl]--------------------
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);

---->[ViewRootImpl#setView]--------------------
mAttachInfo.mRootView = view;

---->[View#mTreeObserver]--------------------
final ViewTreeObserver mTreeObserver = new ViewTreeObserver();

---->[ViewTreeObserver#dispatchOnDraw]--------------------
public final void dispatchOnDraw() {
    if (mOnDrawListeners != null) {
        final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
        int numListeners = listeners.size();
        for (int i = 0; i < numListeners; ++i) {
    --->    listeners.get(i).onDraw();
        }
    }
}

---->[ViewRootImpl#drawSoftware]--------------------
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        // Draw with software renderer.
        final Canvas canvas;
        try {
            final int left = dirty.left;
            final int top = dirty.top;
            final int right = dirty.right;
            final int bottom = dirty.bottom;
            canvas = mSurface.lockCanvas(dirty);
            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }
            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } 
        ...
        try {
        ...
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;
        ...
            try {
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
        --->    mView.draw(canvas);//view執行繪製函式
                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } 
            ...
        return true;
    }

|--不知你以前也沒有疑問,View的onDraw裡的canvas是哪裡來的?
|-- onMeasure 的兩個引數哪裡來的?且看下面
---->[View#draw]--------------------
|---全文搜尋了一下,疑惑解開,回撥的就是這個canvas! 
public void draw(Canvas canvas) {
    ...
    // Step 3, draw the content
    if (!dirtyOpaque) onDraw(canvas);
}

---->[View#measure]--------------------
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    onMeasure(widthMeasureSpec, heightMeasureSpec);
}
複製程式碼

6.我覺得這裡有必要在提一下setContentView
---->[Activity# setContentView(int)]----------------
public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

---->[Activity# setContentView(View)]----------------
public void setContentView(View view) {
    getWindow().setContentView(view);
    initWindowDecorActionBar();
}

---->[Activity# getWindow]----------------------
|---mWindow在哪裡初始化的還記得嗎? Activity#attach方法剛才說過
public Window getWindow() {
    return mWindow; 
}
|--也就是呼叫了PhoneWindow#setContentView(id),即剛才我們分析的方法

---->[PhoneWindow# setContentView(View)]----------------------
@Override
   public void setContentView(View view) {
        //這也可以看出new出來的View是填充父View的
       setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
   }

   @Override
   public void setContentView(View view, ViewGroup.LayoutParams params) {
       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();
       }
   }
複製程式碼

繞了這麼多,來理一下。

1.Windown物件是何時在哪裡以什麼方式實現的?
|--Windown物件當在開啟Activity時,由activity的attach方法建立的PhoneWindow物件

2.PhoneWindow中的幾個核心View是何時何地怎麼實現的?
|--PhoneWindow中的兩個核心View是在installDecor方法中初始化的

3.Window上何時新增View,ViewRootImpl在哪實現的?
|--handleResumeActivity中觸發WindowManager的addView方法,
|--ViewRootImpl在WindowManagerGlobal 的addView方法中被例項化

4.ViewRootImpl是如何處理View的,它對View的測量、佈局、繪製有什麼關係?
|--核心方法是ViewRootImpl#setView,在WindowManagerGlobal#addView中被觸發
|--通過ViewRootImpl#requestLayout進行處理,使用TraversalRunnable進行操作
|--處理View的核心方法在於performTraversals,觸發測量、佈局、繪製

5.本人作為View的繪製粉,有必要知道View的OnDraw的Canvas物件是哪裡來的?
|--由performDraw-->draw-->drawSoftware觸發View.draw方法,將canvas傳入
複製程式碼

四、LayoutInflater載入佈局

本打算在下一篇講的,這篇內容有點少,就放這篇吧。
LayoutInflater可以實現xml--->View 的轉化,在PhoneView裡使用了:
mLayoutInflater.inflate(layoutResID, mContentParent);
是它的第二個用法:將一個xml佈局新增到mContentParent中

佈局載入.png

 mLayoutInflater = LayoutInflater.from(context);//例項化
 mLayoutInflater.inflate(layoutResID, mContentParent);
 
---->[LayoutInflater#from]-----------------------
|--該靜態方法通過一個系統服務獲取佈局載入器LayoutInflater
public static LayoutInflater from(Context context) {
   LayoutInflater LayoutInflater =
           (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   if (LayoutInflater == null) {
       throw new AssertionError("LayoutInflater not found.");
   }
   return LayoutInflater;


---->[LayoutInflater#inflate(int,ViewGroup)]-----------------------
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}

---->[LayoutInflater#inflate(int,ViewGroup,boolean)]-----------------------
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    ...
    //這裡通過res獲取了Xml資源解析器
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

---->[Resources#getLayout]--------------
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
    return loadXmlResourceParser(id, "layout");//載入xml資源解析器
}

---->[Resources#loadXmlResourceParser]--------------
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
        throws NotFoundException {
    final TypedValue value = obtainTempTypedValue();
    try {
        final ResourcesImpl impl = mResourcesImpl;
        impl.getValue(id, value, true);
        if (value.type == TypedValue.TYPE_STRING) {
            //ResourcesImpl的loadXmlResourceParser方法...
            return impl.loadXmlResourceParser(value.string.toString(), id,
                    value.assetCookie, type);
        }
        throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                + " type #0x" + Integer.toHexString(value.type) + " is not valid");
    } finally {
        releaseTempTypedValue(value);
    }
}
|--關於解析器的載入,就追到這裡吧,它是有ResourcesImpl#loadXmlResourceParser載入的

---->[LayoutInflater#inflate(XmlPullParser,ViewGroup,boolean)]-----------------------
|--看一個方法:一看名,二看參,三看返回,四看限 
|--返回了View,我們便要去追這個View在哪裡的定義的,是怎麼例項化的
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        mConstructorArgs[0] = inflaterContext;
--->    View result = root;
        try {//接下來便是對Xml的解析
            int type;
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            final String name = parser.getName();
            ...
            if (TAG_MERGE.equals(name)) {//如果是標籤是TAG_MERGE,即merge
               ...
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
        --->    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                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 against its context.
                rInflateChildren(parser, temp, attrs, 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;
                }
            }
        }...
        } finally {
            // Don't retain static reference on context.--不要保留對context的靜態引用
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
--->    return result;
    }
}

|--核心來看形成View的是"createViewFromTag(root, name, inflaterContext, attrs)"方法
|--意外收穫是看到了第三參"attachToRoot"的作用,只有true才會被加入到ViewGroup的檢視樹上  

---->[LayoutInflater#createViewFromTag三參]-----------------------
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}

---->[LayoutInflater#createViewFromTag四參]-----------------------
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {//view標籤
        name = attrs.getAttributeValue(null, "class");
    }
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }
    if (name.equals(TAG_1995)) {//TAG_1995=blink
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }
    try {
        View view;//<------------↓ 下面劃重點了,要考的 ↓------------
        if (mFactory2 != null) {//Factory2勾起了我的洪荒記憶...不過一般是空,除非自行設定
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }//-----------------Factory暫且不管------------
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {//名字沒點的...如</TextView> 上面的走這裡
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {//名字有點的...如自定義的控制元件走這裡
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        return view;
    } catch (InflateException e) {
      ...
    }
}
|--現在球傳給了"onCreateView"和"createView" 兩人

---->[LayoutInflater#onCreateView]-----------------------
protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
    |---看到這裡估計你可以猜到,人家姓"android.view.",名name ,反射一下就ok了
    return createView(name, "android.view.", attrs);
}

---->[LayoutInflater#createView]-----------------------
public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;
    try {
        |-----劃重點,通過反射建立View物件,這裡貌似看鴻陽的換膚教程用到過---------
        if (constructor == null) {
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);
            if (mFilter != null && clazz != null) {
                boolean allowed = mFilter.onLoadClass(clazz);
                if (!allowed) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor
            if (mFilter != null) {
                // Have we seen this name before?
                Boolean allowedState = mFilterMap.get(name);
                if (allowedState == null) {
                    // New class -- remember whether it is allowed
                    clazz = mContext.getClassLoader().loadClass(
                            prefix != null ? (prefix + name) : name).asSubclass(View.class);
                    boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                    mFilterMap.put(name, allowed);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                } else if (allowedState.equals(Boolean.FALSE)) {
                    failNotAllowed(name, prefix, attrs);
                }
            }
        }
        Object lastContext = mConstructorArgs[0];
        if (mConstructorArgs[0] == null) {
            mConstructorArgs[0] = mContext;
        }
        Object[] args = mConstructorArgs;
        args[1] = attrs;
        final View view = constructor.newInstance(args);<-----建立ok了
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        mConstructorArgs[0] = lastContext;
        return view;<-----view滾了回去
    }
|---好了,基本的佈局載入就這樣,當然其中還有很多別的處理,這裡不扯了
|-- 到這裡你應該有個整體的脈絡了,就這樣,bye 
複製程式碼

後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 附錄
V0.1-- 2018-2-18

釋出名:所得與所見:[-View周邊-] 框架層
捷文連結:https://juejin.im/post/5c6b71b7f265da2db9125f90

2.更多關於我
筆名 QQ 微信
張風捷特烈 1981462002 zdl1994328

我的github:https://github.com/toly1994328
我的簡書:https://www.jianshu.com/u/e4e52c116681
我的掘金:https://juejin.im/user/5b42c0656fb9a04fe727eb37
個人網站:http://www.toly1994.com

3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援

icon_wx_200.png

相關文章