我知道View的繪製流程分爲 measure
、layout
、draw
,那麼一個View顯示在Activity上的完整流程是什麼呢?
在Activity啓動流程中,Activity創建會執行Activity的acttach()
。
1、Activity & attach()
// 設置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();
在Activity的attach()方法中
設置/初始化 WindowManager
1.1 Window & setWindowManager()
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
創建WindowManager
對象 WindowManagerImpl
1.2 WindowManager 和 ViewManager
public interface WindowManager extends ViewManager {
...
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
...
}
}
WindowManager
是一個接口,裏面規定了一些Window的熟悉值和封裝了LayoutParams
內部類,同時又繼承了ViewManager
接口
/** Interface to let you add and remove child views to an Activity. To get an instance
* of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
*/
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updazhteViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
而ViewManager
只提供了三個方法:
- addView() : 添加View到Window
- updazhteViewLayout() : 更新View
- removeView() : 從Window移除View
WindowManager
和 ViewManager
都是接口,因此,上面三個方法的具體實現,在WindowManager
的實現類WindowManagerImpl
中
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
創建WindowManager
的實現類 WindowManagerImpl
1.3、WindowManagerImpl
public final class WindowManagerImpl implements WindowManager {
@UnsupportedAppUsage
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
private IBinder mDefaultToken;
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
return new WindowManagerImpl(displayContext, mParentWindow);
}
/**
* Sets the window token to assign when none is specified by the client or
* available from the parent window.
*
* @param token The default token to assign.
*/
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
// Only use the default token if we don't have a parent window.
if (mDefaultToken != null && mParentWindow == null) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
// Only use the default token if we don't already have a token.
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
...
}
WindowManagerImpl
是 WindowManager
的實現類,所以瞭解其內部構造非常重要。值得注意的有:
WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
聲明瞭一個全局的WindowManagerGlobal
對象,WindowManagerGlobal
負責處理WindowManager
具體操作addView()
、removeView()
、updateViewLayout()
等具體實現都在WindowManagerGlobal
中
2、ActivityThread & handleResumeActivity()
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
// 執行Activity.Resume()方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
// 在第一步中初始化的WindowManager,繼承ViewManager
ViewManager wm = a.getWindowManager();
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 將DecorView添加到Window上
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
} else if (!willBeVisible) {
...
}
...
}
在Activity
執行performResumeActivity
以後,會執行ViewManager.addView()
方法,將DecorView
添加到Window上。
由此可以得到:View的繪製是在Activity onResume()
之後進行的。
WindowManager的實現類是WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
然後執行WindowManagerGlobal.addView()
方法
3、WindowManagerGlobal & addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
...
}
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) {
throw e;
}
}
}
通過調用root.setView()
將DecorView加入到Window中
4、ViewRootImpl & setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
view.assignParent(this);
...
requestLayout();
...
}
}
}
該方法主要作用有:
-
將
DecorView
賦值給mView
-
調用
view.assignParent(this)
將ViewRootImpl
賦值給View中的mParent
。
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}
- 執行
requestLayout()
進行繪製
繼續執行requestLayout()
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
執行scheduleTraversals();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
通過mChoreographer.postCallback()
執行mTraversalRunnable
,在異步中執行繪製操作。
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;
}
}
}
最後會執行performTraversals()
執行View的繪製操作
5、ViewRootImpl & performTraversals()
private void performTraversals() {
...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
...
performDraw();
...
}
該方法會依次執行 performMeasure()
、performLayout()
、performDraw()
等方法,分別對View進行測量、佈局、繪製
二、requestLayout() 原理
在自定義View
和 ViewGroup
的時候,我們可以通過調用 requestLayout()
來對View重新進行測量、佈局和繪製。
requestLayout()
源碼如下:
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
會調用 mParent.requestLayout()
ViewParent
是一個接口,而在上面第4步 ViewRootImpl.setView()
方法中會調用 view.assignParent(this);
方法,將ViewRootImpl
賦值給 mParent
。
因此調用 mParent.requestLayout()
會執行 ViewRootImpl. requestLayout()
方法,繼而執行performTraversals()
方法,對View進行測量、佈局和繪製操作