正常情况下我们在Activity的onCreate方法中调用了setContentView,在setContentView的流程后,xml被转成了View并添加到了DecorView,但是并没有真正的绘制。 绘制流程从ActivityThread.handleResumeActivity开始:

@Override
  public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
            ...
             final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);   //1:从这会走到Activity.onResume
             ...
            if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }
        }
            ...
   }

如果从1处往下追踪可以看到会先调用Activity的onResume方法,再往后走才会进行View的绘制和添加到Window。先略过,看主流程: DecorView被传进了WindowManager(wm.addView(decor, l)),而WindowManager是一个抽象类,addView方法属于他的父类ViewManager(也是抽象方法)。这个wm的实现者是谁呢? ViewManager wm = a.getWindowManager()->Activity中的方法:

public WindowManager getWindowManager() {
        return mWindowManager;
 }
 final void attach(Context context, ActivityThread aThread, ...
  mWindowManager = mWindow.getWindowManager() ;//mWindow的类型为Window
}

Window中的方法:

public WindowManager getWindowManager() {
        return mWindowManager;
    }
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        ...
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

因此上面的wm实现者为WindowManagerImpl,WindowManagerImpl.addView:

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

执行到WindowManagerGlobal.addView:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ...
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

new ViewRootImpl并调用了它的setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        ...
        // Schedule the first layout -before- adding to the window
        // manager, to make sure we do the relayout before receiving
        // any other events from the system.
        requestLayout();
        try { 
           //与wms通信,请求系统添加窗口
           res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                   mTempInsets);
           setFrame(mTmpFrame);
        }
        ...
 }

方法很长,但是最关键的是调用了requestLayout和mWindowSession.addToDisplay。

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

requestLayout先是进行了线程检查,接着调用了scheduleTraversals:

void checkThread() {
if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
}
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
}

在开始layout前checkThread方法先检查了当前线程是否是创建View的线程,如果不是则抛出异常(也就是只有主线程才能更新UI)。接着scheduleTraversals执行了mTraversalRunnable,即TraversalRunnable:

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

因此scheduleTraversals->doTraversal:

void doTraversal() {
     ...
     performTraversals();
     ...
}

performTraversals开始遍历,这个方法有800多行:

private void performTraversals() {
   WindowManager.LayoutParams lp = mWindowAttributes;
    int desiredWindowWidth;  //所需的窗口宽度
    int desiredWindowHeight;  //所需的窗口高度
    ....
    if (mFirst) { //第一次遍历
            mFullRedrawNeeded = true;
            mLayoutRequested = true;

            final Configuration config = mContext.getResources().getConfiguration();
            if (shouldUseDisplaySize(lp)) {
             //如果lp.type=TYPE_STATUS_BAR_PANEL、TYPE_INPUT_METHOD或TYPE_VOLUME_OVERLAY则使用屏幕宽高作为所需
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                desiredWindowWidth = mWinFrame.width();
                desiredWindowHeight = mWinFrame.height();
            }

            // We used to use the following condition to choose 32 bits drawing caches:
            // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
            // However, windows are now always 32 bits by default, so choose 32 bits
            mAttachInfo.mUse32BitDrawingCache = true;
            mAttachInfo.mWindowVisibility = viewVisibility;
            mAttachInfo.mRecomputeGlobalAttributes = false;
            mLastConfigurationFromResources.setTo(config);
            mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
            // Set the layout direction if it has not been set before (inherit is the default)
            if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
                host.setLayoutDirection(config.getLayoutDirection());
            }
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
        } else {
           //如果不是第一次遍历则使用frame的宽高,frame保存了上一次的大小
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                mFullRedrawNeeded = true;
                mLayoutRequested = true;
                windowSizeMayChange = true;
            }
        }
        ...
         if (layoutRequested) {
           if (mFirst) {
                // make sure touch mode code executes by setting cached value
                // to opposite of the added touch mode.
                mAttachInfo.mInTouchMode = !mAddedTouchMode;
                ensureTouchModeLocally(mAddedTouchMode); //确保开启触摸模式
            } else {
             ...
            
            }
            //measureHierarchy方法将会进行测量,最多三次
             windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
         }
         if (didLayout) {//layout
            performLayout(lp, mWidth, mHeight);
            ...
         }
         if (!cancelDraw) { //draw
            ...
            performDraw();
        }
        ...
        if (computesInternalInsets) {
        try {
                    mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
                            contentInsets, visibleInsets, touchableRegion);
                } catch (RemoteException e) {
                }
        }
}

省略大量代码,measureHierarchy、performLayout、performDraw分别进行了测量、布局和绘制的工作;

测量:

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
             final DisplayMetrics packageMetrics = res.getDisplayMetrics();
             //获取一个默认值
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                //第一次测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                   //第二次测量
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
 }
 if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            //第三次测量,使用ip.width/height
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
    }
  return windowSizeMayChange;
 }
 
  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
           ...   
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

measureHierarchy中共有三次调用performMeasure,performMeasure中调用了View的measure。 第一次:读取一个默认值R.dimen.config_prefDialogWidth(对话框大小)进行测量。 第二次:如果View.MEASURED_STATE_TOO_SMALL,给出的大小不足则扩大一点再次测量。 第三次:如果还是无法满足,则将窗口的全部分给该View。

performLayout和performDraw最终调用了View的layout和draw方法。 绘制工作完成后,回头看ViewRootImpl.setView的mWindowSession.addToDisplay: mWindowSession的类型为IWindowSession,它是一个Binder对象,Client端的代理,它的服务端实现为Session,因为可以在它的赋值处看到:

sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });

而openSession的实现则在WindowManagerService中:

@Override
    public IWindowSession openSession(IWindowSessionCallback callback) {
        return new Session(this, callback);
    }

Session.addToDisplay:

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                outInsetsState);
    }

mService即WindowManagerService,到这里,View的绘制完成后跨进程调用了wms的addWindow方法,wms会为这个窗口分配Surface,最终交给SurfaceFlinger处理绘制到屏幕上。 流程图:

android fragment 内放置 ViewPage2_windows

涉及的类:

Window:抽象类,定义了窗口的外观和行为策略(一个窗口具备什么功能、方法)。是View的直接管理者,View必须依附于Window这个抽象概念。他的唯一实现者是PhoneWindow。

WindowManager:接口类,继承了ViewManager接口。提供给应用层管理Widow(增加View到Window或移除、更新View)。

ViewManager:接口类,定义了addView、updateViewLayout、removeView三个接口方法。

WindowManagerImpl:WindowManager的实现者,创建了ViewRootImpl,重写了getDefaultDisplay方法,确定View 属于哪个屏幕,哪个父窗口。实际addView、removeView等操作交给WindowManagerGlobal执行。

WindowManagerGlobal:保存了View、ViewRootImpl、WindowManager.LayoutParams三个ArrayList列表,管理整个进程 所有的窗口信息。一个应用只会创建一个。通过它调用ViewRootImpl的方法。

ViewRootImpl:View树的树根并管理View树、触发View的测量布局和绘制、输入响应的中转站、通过session与WMS进行进程间通信(binder)。

WindowManagerService:窗口的最终管理者,它负责窗口的启动添加和删除,窗口的大小和层级也是由他管理。窗口的操作最终交给他实现。

相关问题: 1.为什么在子线程更新会报错? 因为调用了ViewRootImpl的checkThread,大致流程: View.invalidate --> … --> parent.invalidateChildInParent --> ViewRootImpl.invalidateChildInParent --> checkThread();

2.如何实现在子线程刷新Ui? 在ViewRootImpl 创建之前调用。由于handlerResumeActivity会先执行回调即activity的onResume方法,再往后走才会创建ViewRootImpl,因此在onresume方法之前子线程更新ui的流程是view.requestlayout->parent.requestlayout,parent即ViewRooltImpl,但此时并未创建不会执行到viewRootImpl的requestlayout也就不会执行checkThread方法。所有onResuem方法之前可以在子线程更新ui。或者在需要刷新Ui的子线程创建ViewRootImpl。