Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 消息機制

Android 消息機制

編輯:關於Android編程

從一接觸Android開始,就聽說了消息機制,那時候對消息機制的理解也只是看看別人博客,了解一些概念和相關的類而已,並沒有深入的去了解和探究。直到最近,重新過了一遍消息機制和相關源碼,這裡做簡單的整理和記錄,畢竟好記性不如爛筆頭。如果有什麼問題,還請大家指出。(注:源碼版本 4.0)

基礎使用

  • Android的消息機制,主要是指Handler的運行機制,通常情況下,我們使用Handler來更新UI,詳細的說就是一些耗時操作(IO讀取,網絡請求等)運行結束後,對UI的狀態進行更新,為了避免頁面卡頓,耗時操作通常在子線程中處理,然而UI的更新操作必須在UI線程中進行更新,這個時候就需要使用Handler來進行線程間消息的傳遞
  • 基礎示例:

    public class MainActivity extends AppCompatActivity {
    
        public static final String TAG = MainActivity.class.getSimpleName();
    
        //報內存洩露警告的handler
        private Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {//處理發送過來的消息
                if (msg.arg1 == 1) {
                    Log.i(TAG, "1消息已被接收");
                }
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            sendMessageToHandler();
        }
    
        /**
         * 向Handler發送消息
         */
        private void sendMessageToHandler() {
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Message message = Message.obtain();
                    message.arg1 = 1;
                    handler.sendMessage(message);
                }
            }).start();
        }
    }
    
  • 使用過Handler的朋友都知道,上面的使用方式Android Lint會給出內存洩露警告,具體內容如下:

    This Handler class should be static or leaks might occur (anonymous android.os.Handler) less...

    Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

    • 警告內容說的很詳細,並且給出解決方案。此處先放一放,我們了解源碼後,再回頭解釋此問題

      對消息機制相關類的說明(源碼細節,直接在源碼中加注釋說明)

      • Handler該類負責Message的發送和處理

        • 發送消息的核心源碼如下:

          public boolean sendMessageAtTime(Message msg, long uptimeMillis)
          {   
              boolean sent = false;
              MessageQueue queue = mQueue;
              if (queue != null) {
                  msg.target = this;
                  //關鍵代碼。發送消息的實質,其實是向消息隊列中插入一條消息
                  sent = queue.enqueueMessage(msg, uptimeMillis);
              }
              else {
                  RuntimeException e = new RuntimeException(
                      this + " sendMessageAtTime() called with no mQueue");
                  Log.w("Looper", e.getMessage(), e);
              }
              return sent;
          }
          
        • 處理消息的核心源碼如下:

          //該方法對Message進行了分發處理
          public void dispatchMessage(Message msg) {
              if (msg.callback != null) {
                  handleCallback(msg);
              } else {
                  if (mCallback != null) {
                      if (mCallback.handleMessage(msg)) {
                          return;
                      }
                  }
                  handleMessage(msg);
              }
          }
          
          ...
          
          private final void handleCallback(Message message) {
              message.callback.run();
          }
          
          ...
          
          public interface Callback {
              public boolean handleMessage(Message msg);
          }
          
          ...
          
          public void handleMessage(Message msg) {
          }
          
        • Message該類負責Message對象的創建和釋放

          • Message的創建obtain()

            //該方法有很多的重載方法,都是在此方法的基礎上擴展而來,其他方法不再列舉
            public static Message obtain() {
                synchronized (sPoolSync) {
                    if (sPool != null) {
                        Message m = sPool;
                        sPool = m.next;
                        m.next = null;
                        sPoolSize--;
                        return m;
                    }
                }
                return new Message();
            }
            
          • Message的釋放

            public void recycle() {
                clearForRecycle();
            
                synchronized (sPoolSync) {
                    if (sPoolSize < MAX_POOL_SIZE) {
                        next = sPool;
                        sPool = this;
                        sPoolSize++;
                    }
                }
            }
            
            void clearForRecycle() {
                flags = 0;
                what = 0;
                arg1 = 0;
                arg2 = 0;
                obj = null;
                replyTo = null;
                when = 0;
                target = null;
                callback = null;
                data = null;
            }
            
          • ThreadLocalThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命周期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的復雜度。這裡給出參考博客鏈接:http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/

            • 該類中定義了一個靜態內部類Values,Values中有一個對象數組table,不同的線程訪問著同一個ThreadLocal的set()和get()方法,本質是操作table數組,他們對ThreadLocal的操作是在各自線程的內部。結合源碼我們能更好的理解ThreadLocal的實現
            • ThreadLocal中的set()

              //獲取當前線程的Values對象,如果當前線程的Values不存在,則重新實例化一個
              //否則將value添加到table數組中
              public void set(T value) {
                  Thread currentThread = Thread.currentThread();
                  Values values = values(currentThread);
                  if (values == null) {
                      values = initializeValues(currentThread);
                  }
                  values.put(this, value);
              }
              
            • ThreadLocal中的get()

              public T get() {
                  // Optimized for the fast path.
                  Thread currentThread = Thread.currentThread();
                  Values values = values(currentThread);
                  if (values != null) {
                      Object[] table = values.table;
                      int index = hash & values.mask;
                      if (this.reference == table[index]) {
                          //Values中的put()方法是將索引和值在相鄰位置進行存儲的,具體實現查看put()源碼
                          return (T) table[index + 1];
                      }
                  } else {
                      values = initializeValues(currentThread);
                  }
              
                  return (T) values.getAfterMiss(this);
              }
              
            • Values 中的put()

              void put(ThreadLocal key, Object value) {
                  cleanUp();
              
                  // Keep track of first tombstone. That's where we want to go back
                  // and add an entry if necessary.
                  int firstTombstone = -1;
              
                  for (int index = key.hash & mask;; index = next(index)) {
                      Object k = table[index];
              
                      if (k == key.reference) {
                          // Replace existing entry.
                          table[index + 1] = value;
                          return;
                      }
              
                      if (k == null) {
                          if (firstTombstone == -1) {
                              // Fill in null slot.
                              table[index] = key.reference;
                              table[index + 1] = value;
                              size++;
                              return;
                          }
                          //數組中相鄰的位置存儲索引和值
                          // Go back and replace first tombstone.
                          table[firstTombstone] = key.reference;
                          table[firstTombstone + 1] = value;
                          tombstones--;
                          size++;
                          return;
                      }
              
                      // Remember first tombstone.
                      if (firstTombstone == -1 && k == TOMBSTONE) {
                          firstTombstone = index;
                      }
                  }
              }
              
            • Looper該類是一個循環控制器

              • 該類是消息機制的重要組成部分,本類包含消息機制的准備工作,循環控制器的啟動以及退出循環控制器功能
              • Looper的准備

                static final ThreadLocal sThreadLocal = new ThreadLocal();
                ...
                
                private Looper() {
                    mQueue = new MessageQueue();//創建消息隊列對象
                    mRun = true;
                    mThread = Thread.currentThread();//獲取當前線程
                }
                ...
                
                public static void prepare() {
                    if (sThreadLocal.get() != null) {
                        throw new RuntimeException("Only one Looper may be created per thread");
                    }
                    //將當前線程的Looper對象傳遞給ThreadLocal
                    sThreadLocal.set(new Looper());
                }
                
              • Looper循環控制器的啟動

                public static Looper myLooper() {
                    //從ThreadLocal中獲取Looper對象,這裡可看出他們操作的是同一個ThreadLocal對象
                    return sThreadLocal.get();
                }
                
                ...
                
                public static void loop() {
                    //啟動loop方法必須有looper對象,否則會報下面的異常
                    Looper me = myLooper();
                    if (me == null) {
                        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
                    }
                    //獲取Looper中的消息隊列
                    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();
                
                    //無限循環,從MessageQueue中不斷的獲取Message
                    while (true) {
                        Message msg = queue.next(); // might block
                        if (msg != null) {
                            //唯一退出循環的條件
                            if (msg.target == null) {
                                // No target is a magic identifier for the quit message.
                                return;
                            }
                
                            long wallStart = 0;
                            long threadStart = 0;
                
                            // 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);
                                wallStart = SystemClock.currentTimeMicro();
                                threadStart = SystemClock.currentThreadTimeMicro();
                            }
                
                            //分發Message到Handler中進行處理
                            msg.target.dispatchMessage(msg);
                
                            if (logging != null) {
                                long wallTime = SystemClock.currentTimeMicro() - wallStart;
                                long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;
                
                                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                                if (logging instanceof Profiler) {
                                    ((Profiler) logging).profile(msg, wallStart, wallTime,
                                            threadStart, threadTime);
                                }
                            }
                
                            // 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);
                            }
                            //釋放Message
                            msg.recycle();
                        }
                    }
                }
                
              • 退出Looper quite()

                //向MessageQueue中插入一個target為null的Message,會退出Looper
                //退出loop()的唯一條件 if (msg.target == null){return;}
                public void quit() {
                    Message msg = Message.obtain();
                    // NOTE: By enqueueing directly into the message queue, the
                    // message is left with a null target.  This is how we know it is
                    // a quit message.
                    mQueue.enqueueMessage(msg, 0);
                }
                
              • MessageQueue該類負責Message的插入,取出,以及移除

                • 插入Message方法enqueueMessage()源碼

                  //Handler中的sendMessage()方法最終會調用此方法,所以說發送消息其實是向MessageQueue中插入一條消息
                  final boolean enqueueMessage(Message msg, long when) {
                      if (msg.isInUse()) {
                          throw new AndroidRuntimeException(msg
                                  + " This message is already in use.");
                      }
                      if (msg.target == null && !mQuitAllowed) {
                          throw new RuntimeException("Main thread not allowed to quit");
                      }
                      final boolean needWake;
                      synchronized (this) {
                          if (mQuiting) {
                              RuntimeException e = new RuntimeException(
                                  msg.target + " sending message to a Handler on a dead thread");
                              Log.w("MessageQueue", e.getMessage(), e);
                              return false;
                          } else if (msg.target == null) {
                              mQuiting = true;
                          }
                  
                          msg.when = when;
                          //Log.d("MessageQueue", "Enqueing: " + msg);
                          Message p = mMessages;
                          if (p == null || when == 0 || when < p.when) {
                              msg.next = p;
                              mMessages = msg;
                              needWake = mBlocked; // new head, might need to wake up
                          } else {
                              Message prev = null;
                              while (p != null && p.when <= when) {
                                  prev = p;
                                  p = p.next;
                              }
                              msg.next = prev.next;
                              prev.next = msg;
                              needWake = false; // still waiting on head, no need to wake up
                          }
                      }
                      if (needWake) {
                          nativeWake(mPtr);
                      }
                      return true;
                  }
                  
                • 取出和移除

                  final Message next() {
                      int pendingIdleHandlerCount = -1; // -1 only during first iteration
                      int nextPollTimeoutMillis = 0;
                  
                      //又一個無限循環
                      for (;;) {
                          if (nextPollTimeoutMillis != 0) {
                              Binder.flushPendingCommands();
                          }
                          nativePollOnce(mPtr, nextPollTimeoutMillis);
                  
                          //當有Message時,取出Message,否則該方法將會一直堵塞
                          synchronized (this) {
                              // Try to retrieve the next message.  Return if found.
                              final long now = SystemClock.uptimeMillis();
                              final Message msg = mMessages;
                              if (msg != null) {
                                  final long when = msg.when;
                                  if (now >= when) {
                                      mBlocked = false;
                                      mMessages = msg.next;
                                      msg.next = null;
                                      if (false) Log.v("MessageQueue", "Returning message: " + msg);
                                      msg.markInUse();
                                      return msg;
                                  } else {
                                      nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
                                  }
                              } else {
                                  nextPollTimeoutMillis = -1;
                              }
                  
                              // If first time, then get the number of idlers to run.
                              if (pendingIdleHandlerCount < 0) {
                                  pendingIdleHandlerCount = mIdleHandlers.size();
                              }
                              if (pendingIdleHandlerCount == 0) {
                                  // No idle handlers to run.  Loop and wait some more.
                                  mBlocked = true;
                                  continue;
                              }
                  
                              if (mPendingIdleHandlers == null) {
                                  mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                              }
                              mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                          }
                  
                          // Run the idle handlers.
                          // We only ever reach this code block during the first iteration.
                          for (int i = 0; i < pendingIdleHandlerCount; i++) {
                              final IdleHandler idler = mPendingIdleHandlers[i];
                              mPendingIdleHandlers[i] = null; // release the reference to the handler
                  
                              boolean keep = false;
                              try {
                                  keep = idler.queueIdle();
                              } catch (Throwable t) {
                                  Log.wtf("MessageQueue", "IdleHandler threw exception", t);
                              }
                  
                              //當有Message時,返回Message後,刪除Message
                              if (!keep) {
                                  synchronized (this) {
                                      mIdleHandlers.remove(idler);
                                  }
                              }
                          }
                  
                          // Reset the idle handler count to 0 so we do not run them again.
                          pendingIdleHandlerCount = 0;
                  
                          // While calling an idle handler, a new message could have been delivered
                          // so go back and look again for a pending message without waiting.
                          nextPollTimeoutMillis = 0;
                      }
                  }
                  

                  消息機制關鍵類與核心方法圖

                  \

                  根據Handler的使用流程,逐步分析

                  • 首先,使用Handler必須保證其所在的線程中具有Looper對象,但是有的朋友會說,我在Activity中並沒有創建Looper對象,但是我也可以使用Handler啊。其實Activity所在的主線程(ActivityThread)也就是UI線程是特殊的線程,該線程在創建的時候就已經創建了Looper對象,看源碼

                    • ActivityThread的main()方法

                      public static void main(String[] args) {
                          SamplingProfilerIntegration.start();
                      
                          // CloseGuard defaults to true and can be quite spammy.  We
                          // disable it here, but selectively enable it later (via
                          // StrictMode) on debug builds, but using DropBox, not logs.
                          CloseGuard.setEnabled(false);
                      
                          Process.setArgV0("");
                      
                          Looper.prepareMainLooper();//准備工作
                          if (sMainThreadHandler == null) {
                              sMainThreadHandler = new Handler();
                          }
                      
                          ActivityThread thread = new ActivityThread();
                          thread.attach(false);
                      
                          if (false) {
                              Looper.myLooper().setMessageLogging(new
                                      LogPrinter(Log.DEBUG, "ActivityThread"));
                          }
                      
                          Looper.loop();//啟動循環控制器
                      
                          throw new RuntimeException("Main thread loop unexpectedly exited");
                      }
                      
                    • Looper中的prepareMainLooper()方法是專門用與主線程的

                      public static void prepareMainLooper() {
                          prepare();//最後還是調用了prepare(),從而創建了Looper對象,並設置給了ThreadLocal
                          setMainLooper(myLooper());
                          myLooper().mQueue.mQuitAllowed = false;
                      }
                      private synchronized static void setMainLooper(Looper looper) {
                          mMainLooper = looper;
                      }
                      
                    • 以上工作准備完成後,Looper,ThreadLocal已經創建完畢,並且建立了關聯關系。接下來創建Handler對象,獲取Message對象(Message.obtain()),使用Handler中的sendMessage()方法向MessageQueue中插入Message(enqueueMessage()),這時候Looper對象中處於堵塞狀態的next()方法檢測到Message後,將其從MessageQueue中取出,傳遞給Handler的dispatchMessage()方法後,清除該Message(msg.recycle();),最後dispatchMessage()方法,將Message分發到run()方法或者handlerMessage(Message msg)中進行處理。

                    • 最後來一張消息機制的示意圖

                      \

                      分析Handler內存洩露問題

                      1. App啟動時,Android Framework 為主線程(ActivityThread)創建了一個Looper對象(main方法中),這個Looper將貫穿整個app的生命周期,它內部持有消息隊列(MessageQueue),並且開啟一個循環(loop())來處理Message,而Framework的主要事件都包含著內部Message對象,當這些事件被觸發的時候,Message對象會被加到消息隊列中執行。
                      2. 當一個Handler被實例化時(如上面那樣),它將和主線程Looper對象的消息隊列相關聯,被推到消息隊列中的Message對象將持有一個Handler的引用以便於當Looper處理到這個Message的時候,Framework執行Handler的handleMessage(Message)方法。
                      3. 在 Java 語言中,非靜態匿名內部類將持有一個對外部類的隱式引用,而靜態內部類則不會。
                      4. 內存洩露會在什麼情況下產生呢?這裡給出一個示例

                        @Override
                        protected void onCreate(Bundle savedInstanceState) {
                            super.onCreate(savedInstanceState);
                            setContentView(R.layout.activity_main);
                        
                            mHandler.postDelayed(new Runnable() {
                                @Override
                                public void run() {
                                    Log.i(TAG,"post delay");
                                }
                            },10*60*1000);
                        
                            finish();
                        }
                        

                        示例中,當Activity被finish()掉,Message 將存在於消息隊列中長達10分鐘的時間才會被執行到。這個Message持有一個對Handler的引用,Handler也會持有一個對於外部類(SampleActivity)的隱式引用,這些引用在Message被執行前將一直保持,這樣會保證Activity的上下文不被垃圾回收機制回收,同時也會洩露應用程序的資源(views and resources)。

                        解決辦法

                        Handler的警告提示中已經說明,因為靜態匿名內部類不會持有外部類的隱式引用,因此我們創建一個靜態的Handler子類。如果需要調用外部類(例如:Activity)的方法,就讓Handler持有一個Activity的弱引用(WeakReference),這樣就不會洩露Activity的上下文了。示例:

                        public class MainActivity extends AppCompatActivity {
                        
                            public static final String TAG = MainActivity.class.getSimpleName();
                        
                            private MyHandler myHandler = new MyHandler(this);
                        
                            @Override
                            protected void onCreate(Bundle savedInstanceState) {
                                super.onCreate(savedInstanceState);
                                setContentView(R.layout.activity_main);
                        
                                sendMessageToHandler();
                        
                            }
                        
                            /**
                             * 向Handler發送消息
                             */
                            private void sendMessageToHandler() {
                        
                                new Thread(new Runnable() {
                                    @Override
                                    public void run() {
                                        Message message = Message.obtain();
                                        message.arg2 = 2;
                                        myHandler.sendMessage(message);
                                    }
                                }).start();
                        
                            }
                        
                            /**
                             * MyHandler的處理方法
                             *
                             * @param msg 消息
                             */
                            private void handlerMessage(Message msg) {
                                if (msg.arg2 == 2) {
                                    Log.i(MainActivity.TAG, "2已接收到消息");
                                }
                            }
                        
                        
                            private static class MyHandler extends Handler {
                        
                                //弱引用,避免Handler持有外部類的引用,即MainActivity的引用,
                                // 這樣會導致MainActivity的上下文及資源無法被回收,引發內存洩露的情況發生
                                private WeakReference weakReference;
                        
                                public MyHandler(MainActivity mainActivity) {
                                    weakReference = new WeakReference<>(mainActivity);
                                }
                        
                                @Override
                                public void handleMessage(Message msg) {
                        
                                    MainActivity mainActivity = weakReference.get();
                                    mainActivity.handlerMessage(msg);
                        
                                }
                            }
                        
                        }
                        
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved