Activity是Android開發(fā)者寫第一行代碼起就開始接觸到的旋恼。而在onCreate方法中調(diào)用setContentView(R.layout.main_activity),恐怕也是絕大多數(shù)開發(fā)者的頭等任務(wù)哮内。然后我們可以調(diào)用findViewById(R.id.xxx)來獲取布局中的某一個(gè)View。通過給View設(shè)置點(diǎn)擊事件的監(jiān)聽來響應(yīng)用戶的操作掂为。就這么簡單我們和Android的View過了一段幸福的時(shí)光,直到有一天员串,在某個(gè)編碼過后的深夜勇哗,在剛要合上電腦的一剎那,View低沉卻又如心頭一擊的一句話寸齐,打破了很長時(shí)間以來看似平淡幸福的生活局面欲诺,“喂,我們在一起這么久了访忿,可是你真的了解我嗎瞧栗?”。是啊海铆,我了解View嗎迹恐,它從哪里來?它和Activity的關(guān)系是什么?為什么有時(shí)和它相處,它的脾氣好像總是有點(diǎn)怪卧斟,比如在onCreate方法中獲取View的width殴边,一直為0憎茂?還有onMeasure onLayout onDraw方法是從什么時(shí)候開始調(diào)用的?好吧是時(shí)候帶你一起來認(rèn)識(shí)認(rèn)識(shí)你覺得熟悉锤岸,卻又不是那么熟悉的View竖幔。
在開始本文前,還是按照慣例是偷,拋出幾個(gè)問題讓讀者思考拳氢,讓讀者帶著思考來閱讀文章,一來可以讓讀者更有針對(duì)性蛋铆,二來也可以反映出本文的大概內(nèi)容馋评,讓讀者一眼就能看到文章內(nèi)容的大概
setContentView一般都是在onCreate中調(diào)用,可以在onResume中調(diào)用嗎刺啦?
Activity的ContentView是什么時(shí)候在Activity上顯示給用戶看的
Window留特、Activity、View他們?nèi)咧g的關(guān)系是什么
WindowManager是什么玛瘸,它和View之間的關(guān)系是什么
WindowManagerService在整個(gè)View體系中充當(dāng)什么角色
ViewRootImpl是什么蜕青,它是什么時(shí)候創(chuàng)建的,它與View之間的關(guān)系是什么
View的measure糊渊、onMeasure右核、layout、onLayout再来、draw蒙兰、onDraw是什么意思,它們之間有什么關(guān)系
View的MeasureSpec是什么芒篷,它是怎么控制View和子View的測量的
View的requestLayout和invalidate區(qū)別是什么
上面9個(gè)問題搜变,你可能平時(shí)或多或少都有思考過。如果這些問題你都能答上來针炉,那么恭喜你挠他,你對(duì)View的掌握可以說是通透了。如果不是篡帕,那么請跟我一起探尋View的秘密
1. Activity.setContentView中發(fā)生了什么
1.1 Activity.setContentView
首先我們來看一下源碼殖侵,因?yàn)橹挥袃尚校灾苯淤N代碼吧
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
1.2. Activity.getWindow
public Window getWindow() {
return mWindow;
}
1.3. Activity.attach
好了镰烧,就不跟你貼mWindow的定義的源碼了拢军,請你直接翻閱源碼,mWindow是Window對(duì)象怔鳖。具體來說是PhoneWindow對(duì)象茉唉。接下來我們看下mWindow在什么時(shí)候初始化的,查詢一番,發(fā)現(xiàn)是在Activity的attach方法中度陆,被賦值的艾凯,如果你對(duì)attach方法感覺到陌生又好奇,可以參考這篇文章懂傀。在這里我也對(duì)attach方法做一個(gè)簡單的講解趾诗,省得讀者被中斷。簡單來說Activity并不是一個(gè)真正的Context對(duì)象蹬蚁,Activity只是有名無實(shí)恃泪,真正的Context對(duì)象是通過調(diào)用Activity的
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)
方法賦值給Activity的。同時(shí)在該方法中犀斋,初始化了mWindow對(duì)象
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...省略一些代碼
//調(diào)用WindowManagerImpl.createLocalWindowManager返回WindowManagerImpl
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
//如果mParent不為空
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
}
1.4. PhoneWindow和WindowManger
從上面Activity.attach方法中悟泵,我們看到mWindow = new PhoneWindow(this,window)、mWindow.setWindowManager()和mWindowManager = mWindow.getWindowManager()這幾行比較重要的代碼闪水。首先我們來看下PhoneWindow的源碼,看下PhoneWindow.setContentView做了什么蒙具。關(guān)于WindowManager我們在介紹完了PhoneWindow后我們再來講解
1.5. PhoneWindow.setContentView
@Override
public void setContentView(int layoutResID) {
//如果mContentParent==null初始化DecorView
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;
}
乍一看這個(gè)方法看起來也很簡單啊球榆,只是mContentParent這是個(gè)什么?好吧禁筏,先留下這個(gè)問題持钉,接著看installDecor方法
1.6 PhoneWindow.installDecor
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//return new DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
//如果mContentParent==null 初始化mContentParent
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...省略代碼
}
}
}
好吧這里還引出一個(gè)DecorView mDecor對(duì)象。我們看下DecorView的源碼可以看到它是FrameLayout的子類篱昔。關(guān)于mDecor和mContentParent對(duì)象我們可以這樣認(rèn)為每强。mDecor對(duì)象代表的PhoneWindow的根View。而mContentParent對(duì)象是mDecor對(duì)象的子View州刽。而mContentParent是Activity.setContentView的contentView的父View空执。由此我們可以知道。Activity的contentView是依附在PhoneWindow的DecorView上的穗椅。Activity的ActionBar之類的控件也正是PhoneWindow代為操勞的辨绊。
好吧,至此我們應(yīng)該明白一個(gè)道理匹表,Activity里所有用戶肉眼能夠看到的組件门坷,其實(shí)都是依附在PhoneWindow上。你可以把PhoneWindow簡單認(rèn)為是Activity的ContentView袍镀。那么PhoneWindow是如何被添加到Activity上被用戶看到的呢默蚌?答案是通過WindowManager。
1.7 WindowManager和WindowMangerService
WindowMangerService是一個(gè)系統(tǒng)級(jí)的服務(wù)苇羡,在開機(jī)的時(shí)候绸吸,系統(tǒng)會(huì)啟動(dòng)該服務(wù)。至于WindowManagerService的實(shí)現(xiàn)原理不是本文的任務(wù)。WindowManager和WindowMangerService的關(guān)系 與ActivityManager和ActivityManagerService是一樣的惯裕。WMS是系統(tǒng)級(jí)的服務(wù)温数,它是真正將View添加到手機(jī)屏幕上展示給用戶看的。而WindowManger你可以簡單認(rèn)為是WMS運(yùn)行在應(yīng)用程序端的遠(yuǎn)程代理對(duì)象(真正的代理對(duì)象是WindoManagerGlobal的sWindowManagerService對(duì)象)蜻势。因?yàn)槲覀兊膽?yīng)用程序是無法直接直接訪問WMS等系統(tǒng)服務(wù)撑刺,需要通過AIDL來實(shí)現(xiàn)跨進(jìn)程通信。WindowManager和WMS正是AIDL中的遠(yuǎn)程代理對(duì)象和本地服務(wù)對(duì)象(不懂AIDL的同學(xué)需要去補(bǔ)補(bǔ)課喲握玛,這里默認(rèn)大家都懂了)够傍。通過WindowManager.addView(View view, ViewGroup.LayoutParams params)可以把View展示給用戶
1.8 Window.setWindowManager
public 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) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
1.9 WindowManagerImpl.createLocalWindowManager
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
原來是直接new WindowManagerImpl對(duì)象啊。好吧Activity的mWindowManager原來是WindowManagerImpl對(duì)象挠铲,好吧我們來看下WindowManagerImpl源碼
1.10 WindowManagerImpl
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
@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);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
}
原來WindowManagerImpl addView是借助WindowManagerGlobal mGlobal來實(shí)現(xiàn)的冕屯。有經(jīng)驗(yàn)的老司機(jī)一眼就能看出WindowManagerGlobal實(shí)現(xiàn)了單例模式
1.11 WindowManagerGlobal源碼
public final class WindowManagerGlobal {
private static final String TAG = "WindowManager";
//WindowManagerGlobal單例對(duì)象
private static WindowManagerGlobal sDefaultWindowManager;
//WindowManagerService在客戶端的遠(yuǎn)程代理對(duì)象
private static IWindowManager sWindowManagerService;
//單個(gè)App客戶端與WindowManagerService對(duì)應(yīng)的一個(gè)IWindowSession
private static IWindowSession sWindowSession;
private final Object mLock = new Object();
//App進(jìn)程中所有Activity的DecorView
private final ArrayList<View> mViews = new ArrayList<View>();
//App進(jìn)程中所有Activity的DecorView對(duì)應(yīng)的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
//要結(jié)束生命的Views
private final ArraySet<View> mDyingViews = new ArraySet<View>();
private Runnable mSystemPropertyUpdater;
private WindowManagerGlobal() {
}
public static void initialize() {
getWindowManagerService();
}
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
/**獲取WindowMangerService在APP進(jìn)程端的遠(yuǎn)程對(duì)象**/
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
sWindowManagerService = getWindowManagerService();
ValueAnimator.setDurationScale(sWindowManagerService.getCurrentAnimatorScale());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
/**
* 1.如果view已經(jīng)被add了,報(bào)錯(cuò)
* 2.創(chuàng)建ViewRootImpl,調(diào)用ViewRootImpl.setView
**/
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
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.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
}
if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
doTrimForeground();
}
}
private int findViewLocked(View view, boolean required) {
final int index = mViews.indexOf(view);
if (required && index < 0) {
throw new IllegalArgumentException("View=" + view + " not attached to window manager");
}
return index;
}
}
總結(jié)WindowManagerGlobal功能如下
獲取WMS的遠(yuǎn)程代理對(duì)象
獲取WMS的IWindowSession對(duì)象
創(chuàng)建ViewRootImpl對(duì)象
通過ViewRootImpl對(duì)象來addView removeView updateViewLayout
好吧,至此我們引出了View體系中至關(guān)重要的ViewRootImpl對(duì)象臂容,我們放在一講講解种玛,我們回頭再想想,在Activity的onCreate中調(diào)用setContentView,生成的PhoneWindow的DecorView對(duì)象什么時(shí)候被WindowManger調(diào)用addView的呢
2. ActivityThread.handleResumeActivity 中將PhoneWindow的DecorView add到WindowManger上
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
//調(diào)用Activity的onResume方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
if (localLOGV) Slog.v(
TAG, "Resume " + r + " started activity: " +
a.mStartedActivity + ", hideForNow: " + r.hideForNow
+ ", finished: " + a.mFinished);
final int forwardBit = isForward ?
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
// If the window hasn't yet been added to the window manager,
// and this guy didn't finish itself or start another activity,
// then go ahead and add the window.
boolean willBeVisible = !a.mStartedActivity;
//如果不可見說明在啟動(dòng)另一個(gè)Activity
if (!willBeVisible) {
try {
willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
a.getActivityToken());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
if (r.window == null && !a.mFinished && willBeVisible) {
//PhoneWindow 在Activity的attach中初始化的
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//剛開始的時(shí)候 設(shè)置成不可見
decor.setVisibility(View.INVISIBLE);
//返回的是WindowManagerImpl
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;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
// 第一次應(yīng)該是為null
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
//這里會(huì)把decor加載到WindowManagerImpl中
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.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
// Get rid of anything left hanging around.
cleanUpPendingRemoveWindows(r, false /* force */);
// The window is now visible if it has been added, we are not
// simply finishing, and we are not starting another activity.
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
+ r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
r.newConfig = null;
}
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
+ isForward);
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
!= forwardBit) {
l.softInputMode = (l.softInputMode
& (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
| forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);
}
}
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
if (!r.onlyLocalRequest) {
r.nextIdle = mNewActivities;
mNewActivities = r;
if (localLOGV) Slog.v(
TAG, "Scheduling idle handler for " + r);
Looper.myQueue().addIdleHandler(new Idler());
}
r.onlyLocalRequest = false;
// Tell the activity manager we have resumed.
if (reallyResume) {
try {
ActivityManagerNative.getDefault().activityResumed(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
} else {
// If an exception was thrown when trying to resume, then
// just end this activity.
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
從源碼中我們看到在handleResumeActivity中通過wm.add(decor,l)把Activity的DecorView加載到WindowManger上的。從ActivityThread中處理Activity的生命周期順序是 onCreate -> onResume -> wm.add(decor,l)。所以這里能解釋不少問題了念颈,1.可以在onResume中調(diào)用setContentView 2.在onCreate中調(diào)用View.getWidth等方方法是返回不了正確值的。
3. ViewRootImpl.setView
3.1 ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
//如果mView==null 表示是新增加的
if (mView == null) {
mView = view;
...省略
requestLayout();
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- another window of type "
+ mWindowAttributes.type + " already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- permission denied for window type "
+ mWindowAttributes.type);
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified display can not be found");
case WindowManagerGlobal.ADD_INVALID_TYPE:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified window type "
+ mWindowAttributes.type + " is not valid");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
}
}
}
上面我保留了兩行比較重要的方法requestLayout()和 res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel);
requestLayout()主要是讓View經(jīng)歷measure layout draw 三個(gè)階段连霉,mWindowSession.addToDisplay應(yīng)該是交給WMS去請求其他服務(wù)渲染界面榴芳。我們主要來看下requestLayout()
我們來跟蹤下requestLayout源碼
3.2 ViewRootImpl.requestLayout
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
//注意這個(gè)變量
mLayoutRequested = true;
scheduleTraversals();
}
}
注意mLayoutRequested = true,只有mLayoutRequested為true跺撼,measure layout draw三個(gè)階段才會(huì)全部經(jīng)歷窟感,否則只會(huì)經(jīng)歷draw階段,這也就是View的reqeustLayout和invalidate的區(qū)別财边,requestLayout會(huì)經(jīng)歷完整的三個(gè)階段
3.3 ViewRootImpl.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對(duì)象去執(zhí)行mTraversalRunnable對(duì)象
3.4 ViewRootImpl的TraversalRunnable對(duì)象
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
最終調(diào)用了ViewRootImpl.doTraversal()
3.5 ViewRootImpl.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()代碼
3.6 ViewRootImpl.performTraversals()
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}
if (host == null || !mAdded)
return;
//正在遍歷
mIsInTraversal = true;
//馬上要畫
mWillDrawSoon = true;
//windowSize是否改變
boolean windowSizeMayChange = false;
boolean newSurface = false;
boolean surfaceChanged = false;
WindowManager.LayoutParams lp = mWindowAttributes;
//預(yù)期的window的寬度
int desiredWindowWidth;
int desiredWindowHeight;
//當(dāng)前View是否可見
final int viewVisibility = getHostVisibility();
//view的可見性是否改變(1.不是第一次而且和上次的可見性不一樣)
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded);
//用戶肉眼看到的View的可見性是否改變
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
...省略代碼
Rect frame = mWinFrame;
if (mFirst) {
//如果是第一次
mFullRedrawNeeded = true;
//需要請求layout
mLayoutRequested = true;
//如果是狀態(tài)欄或者輸入法 需要使用真實(shí)的size
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = mContext.getResources().getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
...省略代碼
//如果是第一次 會(huì)調(diào)用View的onAttachedToWindow
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
//Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
} else {
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 (viewVisibilityChanged) {//如果hostView的可見性變了 說明window的可見性變了
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
if (viewUserVisibilityChanged) {//如果肉眼可見性變了
host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
}
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
endDragResizing();
destroyHardwareResources();
}
if (viewVisibility == View.GONE) {
// After making a window gone, we will count it as being
// shown for the first time the next time it gets focus.
mHasHadWindowFocus = false;
}
}
// Non-visible windows can't hold accessibility focus.
if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
//如果window不可見把Accessibility clear掉
host.clearAccessibilityFocus();
}
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
//如果是requestLayout
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
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 {
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
insetsChanged = true;
}
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
//1.開始測量
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
...省略代碼
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
//2 開始layout
performLayout(lp, mWidth, mHeight);
... 省略代碼
}
... 省略代碼
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
//3. 開始draw
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
總結(jié)這個(gè)函數(shù)的功能
- 測量View的大小
- 擺放View的位置(layout)
- 畫View(draw)
3.7 ViewRootImpl的performMeasure肌括、performLayout、performDraw
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);
}
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...省略代碼
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...省略代碼
mInLayout = false;
}
由上可知會(huì)分別調(diào)用View的measure酣难、layout谍夭、draw方法
4. View和ViewGroup相關(guān)
4.1 ViewGroup.measureChildWithMargins
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
4.2 ViewGroup.getChildMeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}