面试题
下面的输出分别为多少 为什么
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv"
android:layout_width="100dp"
android:layout_height="100dp"
android:text="abc"
android:background="#000"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
MyProgressView myProgressView;
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.tv);
Log.d(TAG, "onCreate: "+textView.getWidth());//1
textView.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "onCreate: post"+textView.getWidth());//2
}
});
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume: "+textView.getWidth());//3
}
}
运行结果:
12-10 12:27:10.392 4222-4222/? D/MainActivity: onCreate: 0
12-10 12:27:10.397 4222-4222/? D/MainActivity: onResume: 0
12-10 12:27:10.524 4222-4222/? D/MainActivity: onCreate: post100
原因分析
要想正真获取控件的宽高 必须要等到控件的onMesure方法调用之后才能得到。也就是说在onCreate和onResume中 控件的onMesure都还没有执行。那么为什么textView.post却能得到正确的宽高呢?
下面进入分析为什么onCreate 和onResume不行
首先我们看看activity在onCreate和onResume前都做了什么
ActivityThread
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
Activity a = performLaunchActivity(r, customIntent);//观察该方法 最终能够调用到activity的onCreate方法
if (a != null) {
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);//观察该方法 最终能够调用到activity的onResume方法
if (!r.activity.mFinished && r.startsNotResumed) {
// The activity manager actually wants this one to start out paused, because it
// needs to be visible but isn't in the foreground. We accomplish this by going
// through the normal startup (because activities expect to go through onResume()
// the first time they run, before their window is displayed), and then pausing it.
// However, in this case we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just retain the current
// state it has.
performPauseActivityIfNeeded(r, reason);
// We need to keep around the original state, in case we need to be created again.
// But we only do this for pre-Honeycomb apps, which always save their state when
// pausing, so we can not have them save their state when restarting from a paused
// state. For HC and later, we want to (and can) let the state be saved as the
// normal part of stopping the activity.
if (r.isPreHoneycomb()) {
r.state = oldState;
}
}
} else {
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
...
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
return activity;
}
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
...
r = performResumeActivity(token, clearHide, reason);
...
}
public final ActivityClientRecord performResumeActivity(IBinder token,
boolean clearHide, String reason) {
...
r.activity.performResume();
...
return r;
}
Instrumentation
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
/**
* Perform calling of an activity's {@link Activity#onResume} method. The
* default implementation simply calls through to that method.
*
* @param activity The activity being resumed.
*/
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
activity.onResume();//activity的onResume被调用
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
am.match(activity, activity, activity.getIntent());
}
}
}
}
Activity
@UnsupportedAppUsage
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
dispatchActivityPreCreated(icicle);
mCanEnterPictureInPicture = true;
restoreHasCurrentPermissionRequest(icicle);
//onCreate方法被调用
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate");
mActivityTransitionState.readState(icicle);
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
dispatchActivityPostCreated(icicle);
}
final void performResume(boolean followedByPause, String reason) {
...
// mResumed is set by the instrumentation
mInstrumentation.callActivityOnResume(this);//关键
...
// Now really resume, and install the current status bar and menu.
mCalled = false;
mFragments.dispatchResume();
mFragments.execPendingActions();
onPostResume();
if (!mCalled) {
throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onPostResume()");
}
dispatchActivityPostResumed();
}
小结
ActivityThread handleLaunchActivity
–>performLaunchActivity–>Instrumentation callActivityOnCreate–>Activity performCreate–>onCreate
–>handleResumeActivity–>performResumeActivity–>Activity performResume–>Instrumentation callActivityOnResume–>Activity onResume
到目前为止 没有调用测量方法 那么是什么时候开始测量的呢
再看看
ActivityThread handleResumeActivity调用 performResumeActivity的后面部分
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
r = performResumeActivity(token, clearHide, reason);
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);//这里更新view
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(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;
}
...
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);这里更新view
}
}
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 {
ActivityManager.getService().activityResumed(token);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
} else {
...
}
}
要想view测量出真正的数据 至少要在addview之后才行 根据我们的分析 在调用onResume的时候 还没有addview
至于setContentView中做了什么 可以参考这一文章
https://blog.csdn.net/u011109881/article/details/111085949
下面进入分析为什么view.post可以
View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();//action没有立即执行 而是放在HandlerActionQueue里面了
}
return mRunQueue;
}
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);//action执行时间
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
if (isShown()) {
// Calling onVisibilityAggregated directly here since the subtree will also
// receive dispatchAttachedToWindow and this same call
onVisibilityAggregated(vis == VISIBLE);
}
}
}
从上面看 我们实际post的测量在dispatchAttachedToWindow中进行 实际上此时已经测量完毕。之后我在记录view的绘制流程时在详述为什么测量是在dispatchAttachedToWindow之前进行的(立个flag。。。)