view post(runnable)分析

首先来看下这些疑问:

  1. handler post和view post有什么区别?
  2. 在Activity生命周期onCreate中直接获取view的大小为0,这时通常使用view.post(runnable)方式来获取就能成功。为什么通过这种方式就可以哪?
  3. 在App启动速度一文中提到,为了不影响Activity启动速度,可以把一些耗时的操作通过view.post(runnable)放在第一帧绘制完成后进行。怎么确定这样就是在第一帧绘制完成后执行的哪?

handler post会把Runnable封装成Message,然后走消息机制那些流程。如果在获取view大小时通过handler post可以吗?来看下view post的实现。

源码分析

view post(runnable)

    /**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     */
    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;
    }
复制代码

注释可以看到这里的Runnable和Hanlder post中的一样被加入到消息队列中,并且将会在UI线程中执行。具体到代码时跟view的mAttachInfo有关:

  • 不为空时,runnable会通过mHandler对象发送到UI线程的MessageQueue中,这样的话就跟普通handler post没什么区别了。(为什么是ui线程可以通过mHandler的注释得到验证)
  • 为空时,将runnable放入RunQueue中。 那么post到RunQueue里的runnable什么时候执行呢,又是为何当View还没attach到window的时候,需要post到RunQueue中。

mAttachInfo 什么时候初始化的哪?RunQueue又是什么哪?

mAttachInfo的初始化

此对象的初始化在 View的dispatchAttachedToWindow()里

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mAttachInfo = info;
   ...
    //  在此会执行处理RunQueue里的runnable,稍后再介绍
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);
        mRunQueue = null;
    }
  ...
    onAttachedToWindow();
}
复制代码

可以看到只有当view attch到Window后,才会给mAttachInfo赋值,并且接下来会直接执行 getRunQueue().post(action) 。接下来我们看下RunQueue到底是什么以及executeActions()的实现。

RunQueue

private HandlerActionQueue getRunQueue() {
    if (mRunQueue == null) {
        mRunQueue = new HandlerActionQueue();
    }
    return mRunQueue;
}

public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }
    ...
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                //最终也是发送到UI线程中
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

    private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null || action != null && action.equals(otherAction);
        }
    }
}
复制代码

在这里可以看到,之前mAttachInfo为空时,调用 "getRunQueue().post(action)" 会将 runnable 保存在 HandlerActionQueue对象里的mActions数组中。然后在dispatchAttachedToWindow()里,mAttachInfo初始化后会调用 executeActions()方法,最终将这些runnable发送到ui线程消息队列中。

通过上面的分析我们了解到view post大概处理流程,但还是完全解决文章开头2、3问题。这可以溯源到 dispatchAttachedToWindow() 调用时机。

dispatchAttachedToWindow

此方法的调用会追溯到 ViewRootImpl.performTraversals()方法

public ViewRootImpl(Context context, Display display) {
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
}

private void performTraversals() {
    final View host = mView;
    ...
    if (mFirst) {
        host.dispatchAttachedToWindow(mAttachInfo, 0);
    } else {
        ...
    }
    mFirst = false;
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    performLayout(lp, mWidth, mHeight);
    performDraw();
}
复制代码

整个Activity布局View的 measure/layout/draw 都是在 performTraversals()里调用的,可以看到AttachInfo 是在此开始传递的,而且是第一次遍历执行的。这里的host就是DecorView,我们看下DecorView(ViewGroup)的dispatchAttachedToWindow的实现,如下:

// ViewGroup中的实现
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    super.dispatchAttachedToWindow(info, visibility);
    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility()));
    }
    final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
    for (int i = 0; i < transientCount; ++i) {
        View view = mTransientViews.get(i);
        view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility()));
    }
}
复制代码

能够看到ViewGroup先调用自己的 dispatchAttachedToWindow()方法,再调用每个child的dispatchAttachedToWindow()方法,这样整个view树中就初始化了各自的mAttachInfo。但是注意一下细节,在performTraversals()中,dispatchAttachedToWindow()是在performMeasure()之前,这样的话,依旧没法保证post(runnable)时获取到尺寸啊。我们继续往上看performTraversals的调用,最终定位到 doTraversal()

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ...
    }
}
复制代码

可以看到performTraversals()是由handler发送TraversalRunnable消息执行的,view post(runable)也是由handler发送消息执行的。消息的执行顺序跟他们加入消息队列的顺序有关,由于TraversalRunnable先加入的,所以performTraversals在view post的Runnable之前执行,也就是measure/layout/draw流程先执行。也就是说等到执行view post的Runnable时,view已经被measure过,此时可以获取view的大小。

直接在Activity的onCreate/onResume无法获取view的尺寸,也就是说performTraversals还未执行,那performTraversals是在Activity哪个阶段调用的哪?

AttachedToWindow 与 Activity的生命周期

要查看 performTraversals()的调用,就离不开ViewRootImpl对象的创建。

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    ViewRootImpl root;
    View panelParentView = null;
    //ViewRootImpl 创建
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);

    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    //在 setView 里最终会调用performTraversals()
    root.setView(view, wparams, panelParentView);
}

//addView 会被 WindowManagerImpl类中的addView调用
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

//WindowManagerImpl的addView 是被ActivityThread类中的 handleResumeActivity调用的
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
    //首先调用performResumeActivity()处理Activity的resume状态,在这里面会调用onResume
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
   
    final Activity a = r.activity;
    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;
        
        ...
        wm.addView(decor, l);
    }
}
复制代码

通过上面调用链的分析,我们最终可以确定在onResume()后,会调用performTraversals()处理布局的measure/layout/draw并调用各view的dispatchAttachedToWindow。所以在onResume时并不能获取view的尺寸。

上面代码分析都是基本api28 分析的,其实在api 24(android 7.0)以下view post实现是有些许区别的。

view post低版本实现

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    //不同处
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
    ...
    static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();

    static RunQueue getRunQueue() {
        RunQueue rq = sRunQueues.get();
        if (rq != null) {
            return rq;
        }
        rq = new RunQueue();
        sRunQueues.set(rq);
        return rq;
    }
}
复制代码

可以看到,7.0以下的版本,不同的线程会获取到不同的RunQueue对象。这样的话,如果是在子线程调用的view post,Runnable是往子线程的RunQueue里存的,而performTraversals是在主线程中被执行的,getRunQueue()取的是主线程对应的队列,这是两个不同的队列,因此Runnable不会被执行。

view/activity的生命周期关系

上面已经通过源码分析了view 和 activity的一些关系,下面代来看下各个回调。在MyActivity中布局添加一个CustomView,然后在各个回调中添加log,会有如下调用顺序:

E/MyActivity: onCreate: 
E/CustomView: onFinishInflate: 
E/MyActivity: onResume: 
E/MyActivity: onAttachedToWindow: 
E/CustomView: onAttachedToWindow: 
E/CustomView: onMeasure: 
E/CustomView: onMeasure: 
E/CustomView: onMeasure: 
E/CustomView: onSizeChanged: 
E/CustomView: onLayout: 
E/CustomView: onDraw: 
E/MainActivity: onWindowFocusChanged: true
E/CustomView: onWindowFocusChanged: true

//接下来退出Activity
E/MainActivity: onWindowFocusChanged: false
E/CustomView: onWindowFocusChanged: false
E/MyActivity: onDestroy: 
E/CustomView: onDetachedFromWindow: 
E/MyActivity: onDetachedFromWindow: 
复制代码

经过上述分析,文章开头的问题已清晰~

转载于:https://juejin.im/post/5cc8120e6fb9a032297b0ae2

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值