阅读 874

所得与所见:[-View周边-] 框架层

纸上得来终觉浅,绝知此事要躬行

零、前言

网上有很多介绍View加载的文章,但缘分是天定的,幸福是自己的
对于一篇文章,你并不能完全把握作者的思路。文章只能起到引导思想的作用。
很多东西不用自己的思想加工一下,是很难入脑的。复杂的原理层更是如此。
如果不画几幅图,过一个月,你的文章看着就觉得不像自己写的了...

本文焦点
1.Windown对象是何时在哪里以什么方式实现的?
2.PhoneWindow中的几个核心View是何时何地怎么实现的?
3.Window上何时添加View,ViewRootImpl在哪实现的?
4.ViewRootImpl是如何处理View的,它对View的测量、布局、绘制有什么关系?
5.本人作为View的绘制粉,有必要知道View的OnDraw的Canvas对象是哪里来的?
6.LayoutInflater是如何加载布局的?
复制代码

一、引入

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个来回
复制代码

也就是说显示屏能够储存的信息量是巨大的
硬件层也就是如何将屏幕的物理像素点与要显示的颜色对应,这里就不往下扯了
硬件层篇(如果未来我写得出来的话...几率渺茫),再好好延伸一下


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,看下图:


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

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

关注下面的标签,发现更多相似文章
评论