View.post分析

View.post分析

我们在使用View的时候可以直接使用View对象进行post(runnable),难道View里面有主线程Handler对象?是每个View都有一个Handler,还是公用的?为何View 没有 AttachedToWindow的时候View.post无效呢,后面还会执行么?

本文所有的源码都是基于API19,也就是4.4KitKat版本,不同版本源码不同,思路雷同


View.post背后究竟是谁?

所有的View的post方法都是直接继承于View类的post(Runnable action)方法:

      public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            // 1. AttachInfo.mHandler 这里是Handler
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        // 2.ViewRootImpl.getRunQueue() 返回的RunQueue
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

I. AttachInfo.mHandler支线

  1. AttachInfo中的mHandler 是创建的时候从外部传进的
    AttachInfo(IWindowSession session, IWindow window, Display display,
               ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
        mSession = session;
        mWindow = window;
        mWindowToken = window.asBinder();
        mDisplay = display;
        mViewRootImpl = viewRootImpl;
        // handler是从外部传进来的,那么是公用的,还是每个View对应一个呢?
        mHandler = handler;
        mRootCallbacks = effectPlayer;
    }
  1. ViewRootImpl的构造方法,一起创建了AttachInfo
    public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        // 记住这一行,mThread为当前线程
        mThread = Thread.currentThread();
        // .....
        // 创建了AttachInfo,并将自己的mHandler (ViewRootHandler extends Handler)赋给了AttachInfo的mHandler
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
        // .....
    }

Question:
1.已知Handler的创建是与线程相关的,那么此Handler一定是在UI线程上创建么?
2.AttachInfo是所有View共用的么,好像不是,如何证明?

问题一解答:此Handler一定是在UI线程上创建的:
我们知道Android控件需要在UI线程显示更新,如果不然会报以下错误:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks{
    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
    // .....
}

上面ViewRootImpl 的初始化中有mThread 的创建,就是当前线程对象,根据这里的异常判断,ViewRootImpl 的初始化一定是在UI线程上的,那么成员变量Handler也一定是在主线程上创建的。

问题二解答:AttachInfo每个View对应一个不同对象,且与View的Attach状态相关,创建于addView时:

public final class WindowManagerGlobal {
    public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }
        ViewRootImpl root;
        View panelParentView = null;
        // 每次addView的时候都会创建ViewRootImpl对象
        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) {
        // .....
        }
    }

    // ......
}

很显然,每个View都是通过addView添加到父View中去的。所以每个View都会有其对应的ViewRootImpl,有其对应的mHandler。

II.ViewRootImpl.getRunQueue()支线

先看看getRunQueue:

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

再看看RunQueue :

// 注意这里是静态的
static final class RunQueue {
        private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();

        void postDelayed(Runnable action, long delayMillis) {
            // runnable 存到 HandlerAction对象中
            HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;
            handlerAction.delay = delayMillis;
            synchronized (mActions) {
                mActions.add(handlerAction);
            }
        }

        void executeActions(Handler handler) {
            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;
                final int count = actions.size();

                for (int i = 0; i < count; i++) {
                    final HandlerAction handlerAction = actions.get(i);
                    // 最终通过handler来执行储存的HandlerAction
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }
                actions.clear();
            }
        }
    }

最后来看看谁执行了executeActions( 竟然是传说中的performTraversals() ):

    private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;

        WindowManager.LayoutParams lp = mWindowAttributes;

        final View.AttachInfo attachInfo = mAttachInfo;
        // ......
        // 这里,没错,就是这里
        // Execute enqueued actions on every traversal in case a detached view enqueued an action
        getRunQueue().executeActions(attachInfo.mHandler);

        // ......
    }

我们知道performTraversals是View绘制流程的总入口,当有新的View测量、位置变化、绘制的时候会触发此方法,这是会调用新View的mHandler将之前储存的runnable执行掉。

View.post不是任何时候都能用

之所以存在第二种逻辑,是因为某些情况下AttachInfo为null

第一种情况:View对象被创建,却没有被addView进父View:

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                View inflateView = inflater.inflate(R.layout.layout_inflate_view, null);
                inflateView.post(new Runnable() {
                    @Override
                    public void run() {
                        // 若布局不发生变化,此处不会执行
                        Toast.makeText(mainActivity.getApplicationContext(),"layout_inflate_view 没有Attach 竟然显示了",Toast.LENGTH_LONG).show();
                    }
                });
            }
        },2000);

第二种情况:onDetachedFromWindow 之后post

        // 跳转的同时remove掉自己,再延时post
        text_view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                startActivity(intent);
                removeView();
            }
        });

 private void removeView(){

        text_view.setOnDetachedFromWindowListener(new MineTextView.OnDetachedFromWindowListener() {
            @Override
            public void onDetached() {
                // 会被调用
                Toast.makeText(mainActivity.getApplicationContext(),"text_view 被 Detached 了",Toast.LENGTH_LONG).show();
            }
        });

        ((ViewGroup) text_view.getParent()).removeView(text_view);
        getWindow().getDecorView().postInvalidate();

        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                text_view.post(new Runnable() {
                    @Override
                    public void run() {
                        // 不会执行
                        Toast.makeText(mainActivity.getApplicationContext(),"text_view 被 Detached 了 竟然还能显示",Toast.LENGTH_LONG).show();
                    }
                });
            }
        },2000);
    }

这里可以看到onDetachedFromWindow 之后的View中的AttachInfo被制空了,我们来看看:

   void dispatchDetachedFromWindow() {
        AttachInfo info = mAttachInfo;
        if (info != null) {
            int vis = info.mWindowVisibility;
            if (vis != GONE) {
                onWindowVisibilityChanged(GONE);
            }
        }

        onDetachedFromWindow();
        // ......
        // 果然被制空了
        mAttachInfo = null;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchDetachedFromWindow();
        }
    }

代码验证View中的AttachInfo被制空了

猜你喜欢

转载自blog.csdn.net/yuanyang5917/article/details/78054640
今日推荐