在自定義View時,涉及到View的大小變化時,通常會涉及到一個函數requestLayout()
,字面意思大家都知道是要求重新執行View的繪制中的layout,但是requestLayout()
是如何做到讓View重新繪制的呢?只是簡單調用本身的layout(...)
方法么?
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
....
}
可以看到,View
類的requestLayout()
是調用ViewParent
的requestLayout()
方法,但是ViewParent
類只是一個接口沒有實現,所以我們去找mParent
的引用。
我們逐級打印出mParent
,會發現最后指向了PhoneWindow$DecorView
,也就是我們常說的根View。但是再看下去,發現DecorView
其實是FrameLayout
的子類,也就是說又指回了View
,這下似乎陷入了循環……
而mParent
也只有在assignParent
方法中實現了賦值,但View
類本身并沒有調用這個方法
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");
}
}
那么我們只能從DecorView
生成的地方來考慮了,眾所周知DecorView
作為Activity的根View,那么肯定和Activity生成的地方有關。我們從Activity的創建開始找,最后在ActivityThread
類中的handleResumeActivity()
方法中,找到對Activity中DecorView
的賦值
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
...
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 (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
}
...
}
getDecorView()
是Window
類下的一個抽象方法,而Android中實現了Window
的類只有PhoneWindow
,于是我們去PhoneWindow
中找
這里說明一點,
mDecor
的創建時間并不是調用getDecorView()
時,而是Activity
的onCreate
方法,因為ActivityThread
先調用了performLaunchActivity
方法創建了activity并執行onCreate
方法,而onCreate
方法中的setContentView
會調用PhoneWindow
的setContentView
方法,這里最先執行了installDecor()
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
好像還是沒有線索……但是我們發現在handleResumeActivity
方法中DecorView
的實例最后被一個ViewManager
的子類WindowManager
通過addView()
添加進去了,會不會跟這個WindowManager有關系呢?
//handleResumeActivity中指向的方法,在Activity.java中
public WindowManager getWindowManager() {
return mWindowManager;
}
指向了Activity中的一個變量mWindowManager
,其在Activity
的attach
方法中被初始化
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) {
...
mWindowManager = mWindow.getWindowManager();
...
}
調用到Window
類中的方法
/**
* Return the window manager allowing this Window to display its own
* windows.
*
* @return WindowManager The ViewManager.
*/
public WindowManager getWindowManager() {
return mWindowManager;
}
再去找mWindowManager
賦值的地方
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
終于發現了一個不一樣的類WindowManagerImpl
,這應該就是實現了WindowManager
接口的類,看下它的addView()
方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
又扔給了一個mGlobal
變量,這個變量屬于WindowManagerGlobal
類,可以看下這個類的介紹
**
* Provides low-level communication with the system window manager for
* operations that are not associated with any particular context.
*
* This class is only used internally to implement global functions where
* the caller already knows the display and relevant compatibility information
* for the operation. For most purposes, you should use {@link WindowManager} instead
* since it is bound to a context.
呃……英文不好自行找詞典,大概意思就是提供了一個全局變量來實現底層系統與表現層的溝通,再來看他的addView()
方法
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
可以看到,這里最關鍵的操作就是初始化了一個ViewRootImpl
類的對象,調用它的setView
方法注入之前的View。
那么ViewRootImpl
類是什么?它的setView
方法又做了什么呢?
我們先來看一下ViewRootImlp
類的Javadoc
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}.
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks
//View階層的頂級,實現了View和WindowManager之間需要的協議,這是WindowManagerGlobal
來研究一下它的setView
方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if(mView ==null){
//省略了部分無關代碼
mView = view;
requestLayout();
...
view.assignParent(this);
}
}
}
可以看到,setView
方法基本只有在第一次調用時才會生效,它先調用了自己的requestLayout()
方法,然后又調用View
的assignParent(ViewParent v)
方法把自己注入View中
這下就可以確定View
最上層的mParent
變量,最終指向的是ViewParentImpl
類的實例引用
那讓我們再來看看ViewParentImpl
中如何實現requestLayout()
方法的
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread(); //這里是Android中為什么不能用子線程修改ui的關鍵
mLayoutRequested = true; //修改標記為true
scheduleTraversals();
}
}
眾所周知,Android中是不能用子線程來更新ui的,原因就是在于
ViewRootImpl
類中的requestLayout
方法在執行時,會首先檢查當前線程是否是ViewRootImpl
實例創建的線程。而唯一的ViewRootImpl
對象是在主線程中被系統創建的。所以子線程中更新ui,就會導致checkThread()
方法驗證失敗而拋出異常
除了checkThread()
以外,還執行了scheduleTraversals()
方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
這里執行了Choreographer
類的實例的postCallback方法,方法最后將這個Runnable放入Message中發出到MessageQueue中并執行,我們來看這個Runnable做了什么
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
再看看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繪制中最關鍵的一個方法
private void performTraversals() {
//省略了部分無關代碼
final View host = mView;
...
host.dispatchAttachedToWindow(mAttachInfo, 0);//注入AttachInfo
...
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
mLayoutRequested = false; //清除標記
}
...
if (!mStopped || mReportNextDraw) {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
...
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
}
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||
viewVisibility != View.VISIBLE;
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
...
performDraw();
}
}
...
}
其中performMeasure
、performLayout
、performDraw
最后基本都是去執行了View的measure、layout、draw方法,這也是View繪制三大步驟調用的時間點。
這里我還特地標注一下host.dispatchAttachedToWindow(mAttachInfo, 0)
方法,這個方法給View中注入了mAttachInfo
的引用,而mAttachInfo
的初始化是在ViewRootImpl
的構造函數里
public ViewRootImpl(Context context, Display display) {
...
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
...
}
AttachInfo
這個類基本就是提供各種底層信息的,借此,Window和View的聯系就建立起來了。
最后上一張圖來整理下整個調用鏈