Androidにおける非同期メッセージ処理メカニズム

15706 ワード

メッセージキュー
  • Looperは、Message Queueにメッセージがあることを発見すると、メッセージを取り出し、Handlerオブジェクトにメッセージを投げつけ、Handlerは自分のhandleMessageメソッドを呼び出してこのメッセージを処理する
  • handleMessageメソッドは、メインスレッド
  • で実行される.
  • プライマリ・スレッドが作成されると、メッセージ・キューとポーリング・オブジェクトが作成されますが、メッセージ・プロセッサ・オブジェクトは、使用する必要がある場合は、自分で
  • を作成します.
         //    ,    
            Handler handler = new Handler(){
    
           //            looper,               ,        ,       ,              
                public void handleMessage(android.os.Message msg) {
    
              //  switch         
                switch (msg.what) {
                //   1,           
                case 1:
                    ImageView iv = (ImageView) findViewById(R.id.iv);
                    Bitmap bm = (Bitmap) msg.obj;
                    iv.setImageBitmap(bm);
                    break;
                case 2:
                    Toast.makeText(MainActivity.this, "    ", 0).show();
                    break;
                }        
            }
    
     //    ,         
            //      
            Message msg = new Message();
            //   obj          ,            
            msg.obj = bm;
            //what         ,          ,         
            msg.what = 1;
            //    
            handler.sendMessage(msg);

    ソース分析
    1、LooperはLooperに対して主にprepare()とloop()の2つの方法である.
  • prepare()メソッド
  • public static final void prepare() {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(true));
    }

    sThreadLocalはスレッドに変数を格納できるThreadLocalオブジェクトです.5行目において、1つのLooperのインスタンスがThreadLocalに入れられ、2〜4行がsThreadLocalがnullであるか否かを判断し、そうでなければ異常が放出されることがわかる.これはLooper.prepare()メソッドは2回呼び出すことはできません.また、1つのスレッドに1つのLooperインスタンスしかないことを保証します.
  • Looperの構造方法:
  •  private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mRun = true;
            mThread = Thread.currentThread();
    }
          ,     MessageQueue(    )。
    
  • loopメソッド:
  •   public static void loop() {  
            final Looper me = myLooper(); 
    //       sThreadLocal   Looper  ,  me null     ,    looper     prepare      。 
           if (me == null) {  
               throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
            }  
          //   looper    mQueue(    )
            final MessageQueue queue = me.mQueue;  
    
            // Make sure the identity of this thread is that of the local process, 
            // and keep track of what that identity token actually is. 
            Binder.clearCallingIdentity();  
            final long ident = Binder.clearCallingIdentity();  
    
          //             。
            for (;;) {  
       //      ,         。
                Message msg = queue.next(); // might block 
                if (msg == null) {  
                    // No message indicates that the message queue is quitting. 
                    return;  
                }  
    
                // This must be in a local variable, in case a UI event sets the logger 
                Printer logging = me.mLogging;  
                if (logging != null) {  
                    logging.println(">>>>> Dispatching to " + msg.target + " " +  
                            msg.callback + ": " + msg.what);  
               }  
    
       //:     msg.target.dispatchMessage(msg);     msg target dispatchMessage     。Msg target    ?    handler  ,       。
                msg.target.dispatchMessage(msg);  
    
                if (logging != null) {  
                   logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);  
                }  
    
                // Make sure that during the course of dispatching the 
               // identity of the thread wasn't corrupted. 
               final long newIdent = Binder.clearCallingIdentity();  
                if (ident != newIdent) {  
                    Log.wtf(TAG, "Thread identity changed from 0x"  
                           + Long.toHexString(ident) + " to 0x"  
                            + Long.toHexString(newIdent) + " while dispatching to "  
                            + msg.target.getClass().getName() + " "  
                            + msg.callback + " what=" + msg.what);  
                }  
    
        //         。
                msg.recycle();  
            }  
    }  

    Looperの主な役割:
  • は現在のスレッドにバインドされ、1つのスレッドに1つのLooperインスタンスしか存在しないことを保証し、同時に1つのLooperインスタンスにも1つのMessageQueueしか存在しないことを保証します.
  • loop()メソッドは、MessageQueueからメッセージを取り出し続け、メッセージのtarget属性のdispatchMessageに渡して処理する.さて,我々の非同期メッセージ処理スレッドにはすでにメッセージキュー(MessageQueue)があり,無限ループ体からメッセージを取り出す兄弟たちもいるが,現在欠けているのはメッセージを送信する対象であり,Handlerが登場した.

  • 2、Handler
        Handler  ,           ,      UI  ,              ,   onCreate    Handler  。       Handler     ,     MessageQueue    ,           (         UI  )     MessageQueue  。
    
    public Handler() {  
            this(null, false);  
    }  
    public Handler(Callback callback, boolean async) {  
            if (FIND_POTENTIAL_LEAKS) {  
                final Class<? extends Handler> klass = getClass();  
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  (klass.getModifiers() & Modifier.STATIC) == 0) {  
                   Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
                        klass.getCanonicalName());  
                }  
            }  
    
           //  Looper.myLooper()          Looper  ,
            mLooper = Looper.myLooper();  
           if (mLooper == null) {  
                throw new RuntimeException(  
                    "Can't create handler inside thread that has not called Looper.prepare()");  
            }    
        //      Looper      MessageQueue(    ),      handler      Looper   MessageQueue    。
    
            mQueue = mLooper.mQueue;  
            mCallback = callback;  
            mAsynchronous = async;  
        }  
    
    

    そして私たちが最もよく使うsendMessageの方法を見てみましょう
     public final boolean sendMessage(Message msg)  
     {  
         return sendMessageDelayed(msg, 0);  
     }  
    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {  
         Message msg = Message.obtain();  
         msg.what = what;  
         return sendMessageDelayed(msg, delayMillis);  
    }  
    public final boolean sendMessageDelayed(Message msg, long delayMillis)  
       {  
           if (delayMillis < 0) {  
               delayMillis = 0;  
           }  
           return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
       }  
    
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {  
           MessageQueue queue = mQueue;  
           if (queue == null) {  
              RuntimeException e = new RuntimeException(  
                       this + " sendMessageAtTime() called with no mQueue");  
              Log.w("Looper", e.getMessage(), e);  
               return false;  
           }  
          return enqueueMessage(queue, msg, uptimeMillis);  
       }  
    

    最後にsendMessageAtTimeが呼び出され、このメソッドの内部にMessageQueueが直接取得され、enqueueMessageメソッドが呼び出されます.このメソッドを見てみましょう.
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {  
           msg.target = this;  
           if (mAsynchronous) {  
               msg.setAsynchronous(true);  
          }  
           return queue.enqueueMessage(msg, uptimeMillis);  
       }  
    

    EnqueueMessageではまずmeg.targetはthisに割り当てられ、「Looperのloopメソッドが各msgを取り出してmsgに渡し、target.dispatchMessage(msg)がメッセージを処理することを覚えている場合」、つまり現在のhandlerをmsgのtarget属性とする.最終的にqueueのenqueueMessageを呼び出す方法、すなわちhandlerが発行したメッセージは、最終的にメッセージキューに保存されます.
    Looperはprepare()とloop()メソッドを呼び出し、現在実行されているスレッドにLooperインスタンスを保存します.このインスタンスはMessageQueueオブジェクトを保存し、現在のスレッドは無限ループに入り、MessageQueueからHandlerからのメッセージを読み続けます.次に、このメッセージを作成するhandlerのdispathMessageメソッドにコールバックします.次に、このメソッドを見てみましょう.
    public void dispatchMessage(Message msg) {  
            if (msg.callback != null) {  
                handleCallback(msg);  
            } else {  
                if (mCallback != null) {  
                    if (mCallback.handleMessage(msg)) {  
                        return;  
                    }  
                }  
                handleMessage(msg);  
           }  
        }  

    10行目にhandleMessageメソッドが呼び出されていることがわかります.次に、このメソッドを見てみましょう.
    /** * Subclasses must implement this to receive messages. */  
      public void handleMessage(Message msg) {  
      }  
    
    

    メッセージの最終コールバックは私たちが制御しているので、handlerを作成するときにhandleMessageメソッドを複写し、msgに基づいています.whatはメッセージ処理を行います.
    例:
    private Handler mHandler = new Handler()  
        {  
            public void handleMessage(android.os.Message msg)  
           {  
                switch (msg.what)  
                {  
                case value:  
    
                    break;  
    
               default:  
                    break;  
               }  
            };  
        };  
    

    ここまで、この流れは説明済みですので、まずまとめてみましょう
    1、まずLooper.prepare()このスレッドにLooperインスタンスを保存し、そのインスタンスにMessageQueueオブジェクトを保存します.なぜならLooper.prepare()は1つのスレッドで1回しか呼び出せないので、MessageQueueは1つのスレッドに1つしか存在しません.
    2、Looper.loop()は、現在のスレッドを無限ループにし、MessageQueueのインスタンスからメッセージを読み出し、msgをコールバックする.target.dispatchMessage(msg)法.
    3.Handlerの構築方法は、まず現在のスレッドに保存されているLooperインスタンスを取得し、さらにLooperインスタンスのMessageQueueに関連付ける.
    4、HandlerのsendMessageメソッドは、msgのtargetにhandler自身に割り当てられ、MessageQueueに追加されます.
    5.Handlerインスタンスを構築する際、handleMessageメソッド、すなわちmsgを書き換える.target.dispatchMessage(msg)が最終的に呼び出す方法.
    さて、まとめが完了したら、ActivityではLooperの呼び出しは表示されません.prepare()とLooper.loop()メソッドは、Activityの起動コードで現在のUIスレッドでLooperが呼び出されているため、なぜHandlerが正常に作成できるのか.prepare()とLooper.loop()メソッド.