Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android面試題(五)—— Android的消息機制

Android面試題(五)—— Android的消息機制

編輯:關於Android編程

前言

Handler是Android消息機制的上層接口,平時使用起來很方便,我們可以通過它把一個任務切換到Handler所在的線程中去運行。而最常用的就是拿來從子線程切換到主線程以便更新UI。關於Android的消息機制無法以題目為導向來進行講解,面試中可能會問關於Handler、Looper、MessageQueue、Message之間的關系,要完整回答,我們需要了解Handler內部是如何工作的,而這一部分的源碼並不復雜。所以先整體分析得出結論,再從源碼中驗證結論。

Android的消息機制整體剖析

Android的消息機制工作原理大致如下:

MessageQueue:它的內部存儲了一組數據,以隊列的形式向外提供了插入和刪除的工作。但是它的內部實現並不是隊列,而是單鏈表。對應圖中長方形格子

Looper:在Handler的內部,會不停檢查是否有新的消息,如果有就調用最終消息中的Runnable或者Handler的handleMessage方法。對應提取並處理消息

Handler:Handler的工作主要包含消息的發送和接收過程。消息的發送可以通過post的一系列方法以及send的一系列方法來實現,不過最後都是通過send的一系列方法實現的。對應添加消息處理線程

Message:封裝了需要傳遞的消息,並且本身可以作為鏈表的一個節點,方便MessageQueue的存儲。

ThreadLocal:一個線程內部的數據存儲類,通過它可以在指定的線程中儲存數據,而其它線程無法獲取到。在Looper、AMS中都有使用。

Thread:Android的線程類

Android消息機制的類的關系總結如下:

由上圖總結出以下結論:
1. MessageQueue持有一個mMessages,作為消息隊列內部存儲數據的鏈表頭。它具有兩個重要的操作:對消息的插入和讀取,對應的方法分別是enqueueMessage和next。其中enqueueMessage是往消息隊列中插入一條信息,而next的作用是從消息隊列中取出一條信息並將其從消息隊列中移除。

Message內部除了obj,what,arg1,arg2等存儲數據的成員,還有一個可以指向其他Message的指針,所以MessageQueue可以使用它來作為鏈表的節點。

Looper內部持有一個消息隊列、線程、主線程、ThreadLocal。主要的方法有:

prepare:為當前線程創建一個Looper。 quit:退出Looper,Looper退出後,Handler的send方法會返回false,在子線程手動創建的Looper最好在不需要的時候終止掉。 quitSafely:把消息隊列中已有的消息處理完畢後退出。 getMainLooper:在任何地方獲取主線程的Looper。 getLooper:獲取當前線程的Looper。 loop:最重要的一個方法,只有調用了loop方法後,消息循環系統才能起作用。(後面再做詳細解釋)

一個Thread只能持有一個Looper。

Handler持有一個消息隊列、Looper、Callback。提供多種創建方法,默認的Handler()將使用當前線程的Looper,如果當前線程沒有Looper會拋出異常,也可以通過傳參指定Looper。sendMessage方法可以往消息隊列添加消息。handleMessage方法在創建Handler的線程中或者指定的Looper持有的線程中處理消息。

一個Looper可以被多個Handler持有

ThreadLocal的get和set方法操作的數據,在每個線程中是相互獨立,互不干擾的。

源碼分析


1. ThreadLocal的工作原理

ThreadLocal是什麼?
ThreadLocal是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,而其它線程無法獲取到數據。在Looper、ActiivtyThread和AMS中都用到了ThreadLocal。

ThreadLocal的使用場景

當某些數據是以線程為作用域,並且不同線程有不同的數據副本的時候 復雜邏輯下的對象傳遞,比如監聽器的傳遞。使用參數傳遞的話:當函數調用棧過深時,設計會很糟糕。為每一個線程定義一個靜態變量存儲監聽器,如果是多線程的話,一個線程就需要定義一個靜態變量,無法擴展,這時候使用ThreadLocal可以解決問題。

從ThreadLocal的set和get方法可以看出,他們所操作的對象都是當前線程的localValues對象的table數組,因此在不同的線程訪問ThreadLocal的set和get方法,他們對ThreadLocal的讀寫操作都是僅限於各自線程的內部。這就是ThreadLocal可以在多個線程中互不干擾地存儲和修改數據的原因。
簡單來講:就是每個線程都可以操作ThreadLocal,但他們操作的數據是分隔開的,互不干擾的,代碼如下:

        private ThreadLocal mBooleanThreadLocal = new ThreadLocal();

        mBooleanThreadLocal.set(true);
        Log.d(TAG, "[Thread#main]mBooleanThreadLocal="+ mBooleanThreadLocal.get());

        new Thread("Thread#1") {
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.d(TAG, "[Thread#1]mBooleanThreadLocal="+ mBooleanThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                Log.d(TAG, "[Thread#2]mBooleanThreadLocal="+ mBooleanThreadLocal.get());
            }
        }.start();

運行結果:

07-17 16:23:23.222 23286-23286/com.ryg.chapter_15 D/MainActivity: [Thread#main]mBooleanThreadLocal=true
07-17 16:23:23.222 23286-23312/com.ryg.chapter_15 D/MainActivity: [Thread#1]mBooleanThreadLocal=false
07-17 16:23:23.222 23286-23313/com.ryg.chapter_15 D/MainActivity: [Thread#2]mBooleanThreadLocal=null

由此可以得出結論7是正確的。

2. 消息隊列的工作原理

消息隊列在Android中指的是MessageQueue,它主要包含兩個操作:插入和讀取。讀取操作本身會伴隨著刪除操作。插入和刪除對應的方法分別為enqueueMessage和next。代碼如下:

    boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

從enqueueMessage的實現中,我們可以明顯看出這是一個單鏈表的插入操作,不多解釋,接著看next方法:

    Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // We can assume mPtr != 0 because the loop is obviously still running.
            // The looper will not call this method after the loop quits.
            nativePollOnce(mPtr, nextPollTimeoutMillis);

            synchronized (this) {
                // 嘗試獲取一個消息,如果找到就返回它。
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
               ...
            }
            ...
        }
    }

next方法是一個無限循環的方法,如果消息隊列中沒有消息,那麼next方法會一直阻塞在這裡。當有新消息到來時,next方法會返回這條消息並將其從單鏈表中移除。

3. Looper的工作原理

Looper在Android消息機制中扮演消息循環的角色,它會不停地從MessageQueue中查看是否有新消息,如果有就立即處理,沒有就阻塞在那裡。現在,我們從Looper的使用的一個常見例子來分析這個Looper類。代碼如下:

    class LooperThread extends Thread {
        public Handler h;
        public void run() {
            // 1. 調用prepare
            Looper.prepare();
            // 2.進入消息循環 
            Looper.loop();
            ...
        }
    }
    // 應用程序使用LooperThread
    {
        ...
        new LooperThread().start(); //啟動新線程,線程函數是run
    }

在上面代碼中,Looper有兩處關鍵調用,分別是1. 調用prepare 2.進入消息循環。接下來我們重點分析這兩個函數。

第一個調用的函數是Looper的prepare函數。代碼如下:
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        // 一個線程中只能有一個Looper。只能調用一次prepare
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 構造一個Looper對象,設置到調用線程的局部變量中。
        sThreadLocal.set(new Looper(quitAllowed));
    }
    // sThreadLocal的定義
    static final ThreadLocal sThreadLocal = new ThreadLocal();

根據上面對ThreadLocal的分析,我們知道使用局部變量sThreadLocal存儲的變量作用域是針對線程的。即通過prepare為調用的線程的設置了一個Looper對象。在看一看Looper的構造方法。

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

在構造方法中,創建了一個新的消息隊列,和持有當前線程的引用。
所以prepare函數主要完成的工作是:在調用prepare的線程中,設置一個Looper對象,這個Looper對象保存在Thread的localValues中,而Looper對象內部封裝了一個消息隊列。
prepare通過ThreadLocal機制,巧妙地將Looper和調用線程關聯在一塊。接著看第二個重要函數loop。

Looper的循環
代碼如下:
    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper(); //myLooper返回當前線程的Looper對象
        final MessageQueue queue = me.mQueue; // 取出Looper中的消息隊列
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            // 調用該消息的Handler,交給它的dispatchMessage處理
            msg.target.dispatchMessage(msg);
            ...
            // 消息的回收,回收到消息池中。
            msg.recycle();
        }
    }

從代碼中可以看出,loop是一個死循環,唯一跳出循環的條件是MessageQueue的next方法返回null。而只有當Looper調用quit或者quitSafely方法,方法內部再調用MessageQueue的quit或者quitSafely方法通知消息隊列退出,當消息隊列退出後,next方法才會返回null。從上面對MessageQueue的分析中,我們知道next方法是一個阻塞操作,當消息隊列中沒有消息時,next方法就會一直阻塞在那裡,這也導致了loop方法一直阻塞在那裡,直到next方法返回新的消息,才會調用msg.target.dispatchMessage(msg)來處理消息。這裡的msg.target就是發送這條消息的Handler對象。需要注意的是:通過這一過程。因為Handler的dispatchMessage方法在loop中執行,所以發送消息所在的線程成功地把代碼邏輯切換到了Looper所在的線程中執行,完成了線程間的切換。

4. Handler的工作原理

首先看一下Handler所包括的成員:

    final MessageQueue mQueue; // Handler中也有一個消息隊列,從mLooper中取出的
    final Looper mLooper; // 當前線程的Looper或者指定的Looper
    final Callback mCallback; //有一個回調用的類

這幾個變量是如何被使用的?首先分析Handler的構造方法,代碼如下:

    public Handler()

    public Handler(Callback callback)

    public Handler(Looper looper) 

    public Handler(Looper looper, Callback callback)

在Handler中我們常用的構造方法有上面4個,如果沒有指定Callback,默認mCallback為null。如果沒有指定Looper,默認使用當前線程的Looper,當前線程Looper為null時,拋出異常。mQueue是通過mLooper的myLooper方法獲取的。
Handler的工作主要包含消息的發送和接收過程。消息的發送可以通過post的一系列方法以及send的一系列方法來實現,不過最後都是通過send的一系列方法實現的。代碼如下:

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

    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);
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以發現,Handler發送消息的過程不過是向消息隊列插入一條消息,MessageQueue的next方法會返回這條消息給Looper,Looper收到消息會交給Handler處理,Handler的dispatchMessage方法就會被調用DispatchMessage的實現:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

Handler消息處理流程圖
dispatchMessage會先調用Message的callback接口,在調用Handler的Callback,最後調用handlerMessage方法。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMiBpZD0="handler的實際應用">Handler的實際應用


1. Looper和Handler的同步關系

Looper和Handler會有什麼同步關系呢?它們之間存在的同步關系跟多線程有關,如果不注意,就容易引起錯誤。
下面看一個例子:

    class LooperThread extends Thread {
        public Looper myLooper = null;

        @Override
        public void run() { // 假使run在線程2中執行
            Looper.prepare();
            myLooper = Looper.myLooper();
            Looper.loop();
        }
    }
    // 假使下面代碼在線程1中運行
    {
        LooperThread thread2 = new LooperThread();
        thread2.start();
        Looper looper = thread2.myLooper;
        Handler thread1Hanlder = new Handler(looper);
        thread1Hanlder.sendEmptyMessage(0);
    }

以上代碼的作用:
- 在線程1中創建線程2,並且線程2通過Looper處理消息。
- 線程1中得到線程2的Looper,並且根據這個Looper創建一個Handler,這樣發送給該Handler的消息將由線程2處理。
理想是美好的,現實是殘酷的。如果我們熟悉多線程,就很容易發現這段代碼中存在巨大漏洞,需要注意的是:myLooper的賦值是在線程2的run方法中,而looper的賦值又是在線程1中,這樣就可能導致線程2的run函數還沒來得及給myLooper賦值,線程1中的looper就取得了myLooper的初值,即looper等於null。
解決這個問題,只需要在其中加入線程鎖就可以了。不過不用我們自己動手,Android已經為這個問題提供了解決方案,那就是HandlerThread。
HandlerThread可以完美解決myLooper可能為空的問題。直接上代碼:

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

HanderThread很簡單,使用wait/notifyAll就解決了這個問題。

2. 小心內存洩露

Handler的使用是有可能引起內存洩露的,先看一個例子

public class MainActivity extends Activity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mTextView = new TextView(this);
        mTextView.setText("內存洩露?");
        setContentView(mTextView);
        MyHandler handler = new MyHandler(mTextView);
        handler.sendEmptyMessage(0x11);
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mTextView.setText("有內存洩露");
        }
    };
}

上面的代碼如果是在Android Studio中編寫,Android Lint會提示可能存在內存洩露,並提供相應的解決方案:

This Handler class should be static or leaks might occur (anonymous android.os.Handler) less… (Ctrl+F1)
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應該聲明為靜態的,否則可能導致內存洩露。當Handler聲明為內部類時,他可能持有外部類的引用。如果這時Handler使用一個並非來自主線程的Looper或者MessageQueue時,那就沒有問題。否則你需要修改你的Handler。具體步驟:
1. 將Handler聲明為靜態類,
2. 當你的Handler類需要引用外部類的成員時,使用WeakReference弱引用來獲得它們。

具體改造後,就變成了:

    public class MainActivity extends Activity {

        private TextView mTextView;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mTextView = new TextView(this);
            mTextView.setText("有內存洩露");
            setContentView(mTextView);
            UiHandler handler = new UiHandler(mTextView);
            handler.sendEmptyMessage(0x11);
        }

        static class UiHandler extends Handler {
            WeakReference mActivity;

            UiHandler(MainActivity activity) {
                mActivity = new WeakReference(activity);
            }

            @Override
            public void handleMessage(Message msg) {
                TextView textView = mActivity.get().mTextView;
                if (textView != null) {
                    textView.setText("無內存洩漏");
                }
            }
        }
    }

為以上代碼作以下幾點解釋:
1、上述Handler的作用,是在無內存洩漏的情況下,為外部Activity的mTextView設置文本信息。
2、靜態類不持有外部類的對象,所以外部Activity可以隨意被回收,不會因delay的Message持有了Handler的引用,而Handler又持有Activity的引用,導致Activity被關閉後無法被GC回收。多次的打開和關閉,會造成OOM。
3、WeakReference是弱引用類型,我們可以借助弱引用類型對外部非靜態變量進行操作,且Handler僅有一條弱引用指向了textView,不會影響textView的回收。

3. IntentService

這是Service 的子類,它使用工作線程逐一處理所有啟動請求。如果您不要求服務同時處理多個請求,這是最好的選擇。 您只需實現onHandleIntent()方法即可,該方法會接收每個啟動請求的 Intent,使您能夠執行後台工作。
以下是IntentService的實現示例:

public class HelloIntentService extends IntentService {

  /**
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}

您只需要一個構造函數和一個onHandleIntent()實現即可。
IntentService內部使用Handler來實現,以下提供了Service類實現與IntentService相同功能的代碼:

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service.  Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block.  We also make it
    // background priority so CPU-intensive work will not disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

正如您所見,與使用IntentService相比,這需要執行更多工作。
但是,因為是由您自己處理對onStartCommand()的每個調用,因此可以同時執行多個請求。此示例並未這樣做,但如果您希望如此,則可為每個請求創建一個新線程,然後立即運行這些線程(而不是等待上一個請求完成)。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved