先來(lái)看一段很熟悉的代碼,可能在最開始接觸安卓的時(shí)候,大部分人都寫過(guò)的一段代碼;即嘗試在 onCreate() 和 onResume() 方法中去獲取某個(gè) View 的寬高信息:
但是打印輸出后,我們會(huì)發(fā)現(xiàn),在這兩個(gè)方法中根本獲取不到 View 的寬高信息。
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_test)
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv.getHeight(); // 0
}
@Override
protected void onResume() {
super.onResume();
mTv.getHeight(); // 0
}
}
要弄清這個(gè)問(wèn)題,首先需要知道代碼中涉及到的方法具體做了什么工作,以及具體 View 是在什么時(shí)候完成測(cè)量的。
setContentView()
很明顯,我們?cè)?onCreate() 方法中調(diào)用了 setContentView() 方法,而設(shè)置布局這個(gè)動(dòng)作會(huì)給你一種可以獲取到寬高的錯(cuò)覺(jué);那么我們從源碼的角度來(lái)看看,setContentView() 到底干了點(diǎn)什么。
// 1. AppCompatDelegate 的抽象方法,根據(jù)注釋,會(huì)調(diào)用到 Activity 的實(shí)現(xiàn)方法中
public abstract void setContentView(@LayoutRes int resId);
// 2. Activity 的實(shí)現(xiàn)方法
public void setContentView(@LayoutRes int layoutResID) {
// Window 是一個(gè)抽象類,其唯一實(shí)現(xiàn)類是 PhoneWindow
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 3. 初始化 DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
... ...
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 4. 第一次加載窗口,mDecor 為空時(shí),生成一個(gè) DecorView 對(duì)象
// generateDecor(-1) : return new DecorView()
mDecor = generateDecor(-1);
... ...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 5. 初始化父布局
mContentParent = generateLayout(mDecor);
}
}
// 繼續(xù)跟蹤到 generateLayout(mDecor) 方法內(nèi)部
protected ViewGroup generateLayout(DecorView decor) {
// 此處根據(jù)設(shè)置的主題進(jìn)行一些基礎(chǔ)設(shè)置,沒(méi)什么決定性作用
TypedArray a = getWindowStyle();
... ...
// 接下來(lái)的一大段代碼是根據(jù)各種主題設(shè)置默認(rèn)布局,篇幅原因,此處有大量源碼刪減
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
} else {
// 默認(rèn)布局樣式
layoutResource = R.layout.screen_simple;
}
// 6. 重點(diǎn)來(lái)了:將對(duì)應(yīng)的布局加載到 DecorView 中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
return contentParent;
}
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
// 加載資源文件
final View root = inflater.inflate(layoutResource, null);
... ...
// 7. 將 View 加載到當(dāng)前 DecorView 中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
public void addView(View child, int index, LayoutParams params) {
// 頁(yè)面發(fā)生變化的話,請(qǐng)求重新擺放布局以及重新繪制
// 注意,此處的 requestLayout 是 View 的方法
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
說(shuō)出來(lái)你可能不信,但是 setContentView() 到這里就差不多結(jié)束了。
很明顯,我們并沒(méi)有發(fā)現(xiàn)任何關(guān)于 View 的測(cè)量的代碼,最后的 requestLayout() 和 invalidate() 也和 View 的 measure() 關(guān)系不大,畢竟還沒(méi)測(cè)量,哪里談得上 layout 和 draw 呢?
所以, setContentView() 和 View 的測(cè)量沒(méi)啥關(guān)系,那么在其之后也就自然獲取不到 View 寬高的值了。
測(cè)量流程到底是從哪里開始的
有了上面的經(jīng)驗(yàn),我們已經(jīng)知道,setContentView() 并不會(huì)觸發(fā) View 的測(cè)量,而只是為 DecorView 指定了布局;那么接下來(lái)的問(wèn)題就是,測(cè)量流程到底是從哪里開始的呢?
我們簡(jiǎn)單回顧一下 Activity 的啟動(dòng)流程,然后來(lái)找到這個(gè)答案。
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
// 1. ActivityThread 內(nèi)部類 H,處理 LAUNCH_ACTIVITY 的消息
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
} break;
}
// 2. 直接從 ActivityThread 的 handleLaunchActivity() 開始了
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// 3. 執(zhí)行 performLaunchActivity() 方法
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
// 4. 執(zhí)行 handleResumeActivity() 方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed,
r.lastProcessedSeq, reason);
}
}
// 3. performLaunchActivity()
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// 基于反射,利用 Instrumentation 對(duì)象創(chuàng)建當(dāng)前 Activity 的實(shí)例
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
}
try {
if (activity != null) {
// attach() 方法做了一系列最基本的初始化
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, r.configCallback);
activity.mCalled = false;
// 3.1 依然使用 Instrumentation 對(duì)象調(diào)用 Activity 的 onCreate() 方法
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
// 強(qiáng)制校驗(yàn) super 調(diào)用
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
}
}
return activity;
}
public void callActivityOnCreate(Activity activity, Bundle icicle,PersistableBundle persistentState) {
prePerformCreate(activity);
// 3.2 調(diào)用 Activity 的 performCreate() 方法
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
// 3.3 最終得以調(diào)用到實(shí)際實(shí)現(xiàn)的 onCreate()
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
restoreHasCurrentPermissionRequest(icicle);
onCreate(icicle, persistentState);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
// 4 performLaunchActivity() 執(zhí)行完畢后,根據(jù)代碼來(lái)看,會(huì)繼續(xù)執(zhí)行 handleResumeActivity()
// 同樣的,這個(gè)方法會(huì)調(diào)用到一個(gè) performResumeActivity(),在該方法內(nèi)部也會(huì)最終執(zhí)行到 onResume()
final void handleResumeActivity( ... ... ) {
// 最終會(huì)執(zhí)行到 onResume(),不是重點(diǎn)
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
ViewManager wm = a.getWindowManager();
// 5. 執(zhí)行到 WindowManagerImpl 的 addView()
// 然后會(huì)跳轉(zhuǎn)到 WindowManagerGlobal 的 addView()
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
}
}
}
public void addView( ... ... ) {
ViewRootImpl root;
synchronized (mLock) {
// 初始化一個(gè) ViewRootImpl 的實(shí)例
root = new ViewRootImpl(view.getContext(), display);
try {
// 調(diào)用 setView,為 root 布局 setView
// 其中 view 為傳下來(lái)的 DecorView 對(duì)象
// 也就是說(shuō),實(shí)際上根布局并不是我們認(rèn)為的 DecorView,而是 ViewRootImpl
root.setView(view, wparams, panelParentView);
}
}
}
// 6. 將 DecorView 加載到 WindowManager, View 的繪制流程從此刻才開始
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// 請(qǐng)求對(duì) View 進(jìn)行測(cè)量和繪制
// 與 setContentView() 不同,此處的方法是 ViewRootImpl 的方法
requestLayout();
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// 7. 此方法內(nèi)部有一個(gè) post 了一個(gè) Runnable 對(duì)象
// 在其中又調(diào)用一個(gè) doTraversal() 方法;
// 再之后又會(huì)調(diào)用到 performTraversals() 方法,然后 View 的測(cè)繪流程就從此處開始了
scheduleTraversals();
}
}
private void performTraversals() {
... ...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
... ...
performLayout(lp, mWidth, mHeight);
... ...
performDraw();
... ...
}
問(wèn)題到這里就差不多得到了解答,View 的測(cè)繪流程是在 performTraversals() 才開始的;而這個(gè)方法的調(diào)用是在 onResume() 方法之后,所以在 onCreate() 和 onResume() 方法中拿不到 View 的寬高信息也就很容易理解了。