View.postはいったい何をしたのか


多かれ少なかれみんながViewpostの一連の方法を使ったことがあると信じて、幅を広くしたり、遅延操作をしたりすることができて、いつからか急に使うだけだと思っていましたが、なぜそうなったのか分からず、鏡の中の花の水の中の月の感じを与えてくれたので、次の文章がありました.
postの疑問点:
  • ビューからpostが始まり、いったい何をしましたか?どうして使うの?
  • なぜビューの幅を得ることができますか?
  • RunnableでUI操作を実行できますか?サブスレッドで呼び出すことができますか?
  • はメモリの漏洩を引き起こすことができますか?
  • Viewには3つのpostメソッドが含まれており,それぞれ以下の3つのメソッドである.
    public boolean post(Runnable action) {}
    public boolean postDelayed(Runnable action, long delayMillis) {}
    public void postOnAnimation(Runnable action) {}
    
    もちろん普段は前の2つが一番多く使われていますが、最後の方法は名前によってアニメーションと関係があるはずですが、最近RecyclerViewのソースコードとViewのanimateのソースコードを分析したとき、この方法を見ました.私は最後にこの方法を話します.これからはあまり話さないで、まずViewを見ます.post()はいったい何をしましたか.
    public boolean post(Runnable action) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                return attachInfo.mHandler.post(action);
            }
            getRunQueue().post(action);
            return true;
        }
    
    この方法でmAttachInfoオブジェクトを取得すると、まずオブジェクトがnullであるかどうかを判断して異なるステップを実行し、mAttachInfoがnullでない場合、mAttachInfoオブジェクトのHandlerを呼び出してRunnable postをメッセージキューに入れ、Handlerメカニズムに詳しい私たちは、ここのHandlerがメインスレッドのHandlerであるかどうかを考えなければならない.HandlerはRunnableをプライマリスレッド実行にコールバックし、Handler/Looperメカニズムによってメモリが漏洩するためです.わからなかったら、Handlerについての説明を探してみてください.解析を続けると、mAttachInfo=nullがgetRunQueueメソッドを呼び出してオブジェクトを取得します.
     private HandlerActionQueue getRunQueue() {
            if (mRunQueue == null) {
                mRunQueue = new HandlerActionQueue();
            }
            return mRunQueue;
        }
    
    名前からこのオブジェクトとキューの使い方は似ているはずで、postメソッドではpostDelayedメソッドが呼び出され、渡されたRunnableとtimeをHandlerActionオブジェクトとしてHandlerActionQueueのmActions配列にカプセル化して実行を待つ.
    次に考慮するのは、mAttachInfoがいつ付与され、mActions配列のオブジェクトがいつ実行されるかということです.ちょうどHandlerActionQueueには、
    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);
                }
    。。。。。。
            }
        }
    
    executeActionsメソッドは、mActions内のオブジェクトを巡回し、渡されたHandlerを呼び出してカプセル化されたRunnableオブジェクトを実行します.ここまで来れば、このメソッドが呼び出された場所を直接確認すればいいのではないでしょうか.コード追跡により,このメソッドが呼び出されたのは,ViewsのdispatchAttachedToWindowメソッドとViewRootImplのperformTraversalsメソッドの2箇所のみであることが分かった.
    言うまでもなく、次の分析は、見つかった2つの呼び出しに基づいてそれぞれ検索を続けます.
  • dispatchAttachedToWindowメソッドの呼び出し.
  • performTraversalsメソッドの呼び出し.
  • まずはdispatchAttachedToWindowメソッドから
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
         mAttachInfo = info;
         ......
         if (mRunQueue != null) {
                mRunQueue.executeActions(info.mHandler);
                mRunQueue = null;
            }
         ......
     }
    
    void dispatchDetachedFromWindow() {
          mAttachInfo = null;
        }
    
    最初にmAttachInfoがnullでない場合に内部のHandlerオブジェクトを直接呼び出してRunnableオブジェクトを実行することを解析したが,Viewではグローバル検索を経て,mAttachInfoの付与値のみが上の2箇所で調べられた.dispatchAttachedToWindowメソッドで値を割り当て、dispatchDetachedFromWindowメソッドで空にします.ここではコードの大部分を省略し、関連する部分だけを残しておくと、dispatchAttachedToWindowメソッドではまずmAttachInfoに値を割り当て、executeActionsメソッドを呼び出してAttachInfoのHandlerオブジェクトを渡し、どこで呼び出されたdispatchAttachedToWindowメソッドではコードを追跡し続け、ViewGroupでは3つの場所でViewのdispatchAttachedToWindowメソッドが呼び出されていることがわかります.
  • ViewGroupが書き換えたdispatchAttachedToWindowにあります.
  • ViewGroupのaddViewInnerメソッド.
  • ViewGroupのaddTransientViewメソッド.
  • 実際には、前に述べた2番目の方向performTraversalsに置いておきます.ここでは、まずView Groupの呼び出しを見てみましょう.
    @Override
        void dispatchAttachedToWindow(AttachInfo info, int visibility) {
          ......
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                child.dispatchAttachedToWindow(info,
                        combineVisibility(visibility, child.getVisibility()));
          ......
        }
    
    View Group自体はViewから継承されます.ここでは、ViewのdispatchAttachedToWindowメソッドを実装し、内部View呼び出しサブViewのdispatchAttachedToWindowメソッドを巡回してAttachInfoオブジェクトを渡す.一方、addViewInnerとaddTransientViewは、サブViewを追加する操作に属します.内部はやはりAttachInfoオブジェクトをサブViewに渡します.dispatchAttachedToWindowのAttachInfoオブジェクトがどのように伝わってきたのかは、ここではないに違いない.では、最終的な呼び出しは、ViewRootImplのperformTraversalsメソッドから来ていることは間違いありません.
    次に見るのは、2番目の方向ViewRootImplのperformTraversalsメソッドです.
    private void performTraversals() {
       final View host = mView;
       ......
       host.dispatchAttachedToWindow(mAttachInfo, 0);
       getRunQueue().executeActions(mAttachInfo.mHandler);
       ......
       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
       performLayout(lp, mWidth, mHeight);
       performDraw();
       ......
     }
    
    ここでは多くのコードを省略して見やすく、最初のmViewは、私たちがよく知っているDecordViewであり、viewのdispatchAttachedToWindowメソッドを呼び出してmAttachInfoオブジェクトを渡し、ここまではあまり通じません.mAttachInfoオブジェクトは、DecordViewによってすべてのサブViewに渡され、サブViewのdispatchAttachedToWindowメソッドでexecuteActionsメソッドを呼び出してHandlerメカニズムによって以前に保存されていたRunnableオブジェクトを実行します.果たしてここのHandlerはメインプログラムのHandlerなのか、コードを追跡し続けます.
     mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                    context);
    
    ViewRootImplのコンストラクション関数でmAttachInfo初期化の情報が見つかりましたが、内部のmHandlerが見えましたか.私たちは下を見続けた.
    final ViewRootHandler mHandler = new ViewRootHandler();
    
    ViewRootImplで直接作成されたfinal修飾のmHandlerオブジェクトであることがわかります.Looperがあることを発見していないので、Handlerのデフォルトの構造方法を呼び出して、sThreadLocal.get()は現在のスレッドのLooperを取得し、ViewRootImplはプライマリスレッドで実行されると、次の結論が得られます.
     mAttachInfo    Handler      handler
    
    最初の疑問の3つ目に答えることができます.サブスレッドでpostメソッドを呼び出すことができます.
    次にperformTraversals法を分析し続け、興味深い点に気づいたかもしれません.dispatchAttachedToWindow法はmeasure、layout、drawの前に呼び出されています.これは疑問です.Viewはまだ測定を呼び出していません.私たちはどうして幅を取ることができますか.Androidはメッセージ駆動に基づいているため、イベントがメッセージキューに追加されると、次のイベントが実行されるのは、現在のメッセージの実行が完了してからである.ここではperformTraversals自体がイベントにあることを知っている人もいるかもしれません.したがって,HandlerActionQueueでキャッシュされたRunnableがメッセージキューに追加されるとすぐには実行されない.Runnableオブジェクトは、現在実行中のイベントが終了した場合、つまりViewがmeasure、layout、drawされた場合にのみ実行されます.ここでは、最初に述べた2つ目の疑問にも答えることができます.なぜViewの幅を得ることができますか.
    まとめ:
    1.View.post()内部はmAttachInfoがnullであるかどうかによって2つの場合があります.mAttachInfo!=nullの場合、Runnableオブジェクトのメソッドが直接実行されます.nullに等しい場合、Runnableオブジェクトは配列にキャッシュされ、dispatchAttachedToWindowが呼び出されるのを待って実行されます.
    2.すべてのView内のmAttachInfoオブジェクトは、dispatchAttachedToWindowを呼び出すことによって伝達されるmAttachInfoオブジェクトによって参照されます.
    3.Runnableの実行は,AndroidのHandlerメカニズムによりメッセージキューにイベントを追加して実行し,最後にメインスレッドにコミットして実行する.
    最後に、私たちが最初に列挙したpostOnAnimation方法です.この方法は、Viewのanimaterソースコードを見たとき、postOnAnimation方法でViewをリフレッシュし、Viewのプロパティアニメーションを実現していることに気づきました.ネット上では、次の画面のリフレッシュ信号が来るのを待って、Viewをリフレッシュすると信じられています.