View.post分析

来源:互联网 发布:ed2k for mac下载工具 编辑:程序博客网 时间:2024/05/21 08:56

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被制空了

原创粉丝点击