android onCreate onResume中获取 View 宽高为0分析

1、问题测试



    

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private Button mBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtn = (Button) findViewById(R.id.btn);
        Log.d("TAG", "onCreate() button width=" + mBtn.getWidth());
    }

    @Override
    protected void onResume() {
        super.onResume();
        mBtn.post(new Runnable() {
            @Override
            public void run() {
                Log.d("TAG", "onResume() mBtn post button width=" + mBtn.getWidth());
            }
        });
        new Handler().post(new Runnable() {
            @Override
            public void run() {
                Log.d("TAG", "onResume() Handler button width=" + mBtn.getWidth());
            }
        });
    }
}

log 结果:


image.png

根据上面的结果回产生4个疑问:
1、setContentView后获取控件的宽高为什么是0;
2、在 onResumehandler.post 中获取控件的宽高为什么是0;
3、在 onResume中的 view.post 中为什么能获取控件宽高;
4、在 onResumehandler.postView.post 后面为什么执行反而在前面;

针对以上4个疑问进行解答

1、setContentView后获取控件的宽高为什么为0;

这个很好理解, setContentView只是解析了 xml 文件并创建了对应的控件,并没有进行控件的测量等工作;

2、在 onResume中 handler.post 中获取控件的宽高为什么是0;

ActivityThread.java类中handleResumeActivity函数

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        ............

        // TODO Push resumeArgs into the activity for consideration
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        if (r == null) {
            // We didn't actually resume the activity, so skipping any follow-up actions.
            return;
        }
        ............
       
        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 (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();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } 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;
        }

     ..............
    }

Activity.onResume是在performResumeActivity中调用,此时还没有执行wm.addView(decor, l)创建 ViewRootImpl ,同时在 ViewRootImpl中的doTraversal()是通过mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)发送异步消息等待下一个刷新消息才执行。所以 handler.post 消息回先执行导致获取 view 宽高失败。

ViewRootImpl.java中

   @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

3、在 onResume中的 view.post 中为什么能获取控件宽高;

View.java 中的 post()

public boolean post(Runnable action) {
    //mAttachInfo 是在 ViewRootImpl 的构造函数中初始化的
    //而 ViewRootmpl 的初始化是在 addView() 中调用
    //所以此处的 mAttachInfo 为空,所以不会执行该 if 语句
    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.
    //保存消息到 RunQueue 中,等到在 performTraversals() 方法中被执行
    getRunQueue().post(action);
    return true;
}

通过第2点可以知道ViewRootImpl 的创建是在onResume 之后,所以此时attachInfo == null,从而消息被保存到RunQueue中,而RunQueueViewRootImplperformTraversals被中执行,所以可以获取到控件宽高。

private void performTraversals(){
...
// Execute enqueued actions on every traversal in case a detached view enqueued an action
        getRunQueue().executeActions(mAttachInfo.mHandler);
...
}
public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

4、在 onResume 中handler.post 在 View.post 后面为什么执行反而在前面;

通过上面第2点和点3点分析可以知道View.post的在后面performTraversals中被执行,而handler.post在performTraversals之前就被执行

image.png

你可能感兴趣的:(android onCreate onResume中获取 View 宽高为0分析)