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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章