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

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

零、前言

網上有不少介紹View加載的文章,但緣分是天定的,幸福是本身的
對於一篇文章,你並不能徹底把握做者的思路。文章只能起到引導思想的做用。
不少東西不用本身的思想加工一下,是很難入腦的。複雜的原理層更是如此。
若是不畫幾幅圖,過一個月,你的文章看着就以爲不像本身寫的了...git

本文焦點
1.Windown對象是什麼時候在哪裏以什麼方式實現的?
2.PhoneWindow中的幾個核心View是什麼時候何地怎麼實現的?
3.Window上什麼時候添加View,ViewRootImpl在哪實現的?
4.ViewRootImpl是如何處理View的,它對View的測量、佈局、繪製有什麼關係?
5.本人做爲View的繪製粉,有必要知道View的OnDraw的Canvas對象是哪裏來的?
6.LayoutInflater是如何加載佈局的?
複製代碼

1、引入

1.類比於散扯

拿個人手機來講,在物理層面,這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個來回
複製代碼

也就是說顯示屏可以儲存的信息量是巨大的
硬件層也就是如何將屏幕的物理像素點與要顯示的顏色對應,這裏就不往下扯了
硬件層篇(若是將來我寫得出來的話...概率渺茫),再好好延伸一下github


2.Window對象與View

軟件層面來講,屏幕被咱們抽象成了一個抽象的Window對象
一共也就近2000行代碼,定義了Window很是多的抽象行爲編程

顏值擔當Activity的源碼分析中咱們遇到過它,不知你有無印象
Activity在啓動時經過Handler會調用ActivityThread的performLaunchActivity方法
在其中activity經過attach方法關聯到Window對象上,而Window就是在attach方法裏實例化的canvas

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;
}
複製代碼

2、PhoneWindow與DecorView :

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


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!");
}

複製代碼

總算理清爲何視圖樹的層次是這樣的了app

視圖樹.png


3、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篇一帶而過,這裏詳細梳理一下ide

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方法?

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傳入
複製代碼

4、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
相關文章
相關標籤/搜索