1. 程式人生 > >Android深入理解:Handler + Looper + Message

Android深入理解:Handler + Looper + Message

宣告:本文是一篇對Handler相關內容的整理(經過相當一段時間,幾次內容增減),有相當部分內容來源網路,其中融入部分作者本身的理解,並加以整理。如有涉及到哪位老師的原作,在此深表感謝!

目錄

 

Handler + Looper + Message:生產者 + 消費者 + 倉庫(任務佇列)

1.Handler(生產者add)

1.1.Handler建立物件

1.2.Handler傳送訊息

1.3.Handler處理訊息

1.4.Handler移除訊息

2.HandlerThread

2.1.定義:

2.2.適用場景:

2.3.原始碼:

2.4.DEMO:

3.IntentService

3.1.定義:

3.2.原始碼:

3.3.DEMO:

3.3.1.多檔案下載:

3.3.2.廣播更新UI:

3.4.總結:

4.Looper(消費者get and deal)

4.1.定義:

4.2.機制:

4.3.應用:

4.4.quit:

5.Message(任務,產品)(Intent, Runnable, Message)

5.1.定義:

5.2.關鍵屬性:

5.3.構造方式:

6.MessageQueue(任務佇列,產品池)

6.1.定義:

7.應用案例

7.1.子執行緒發訊息給子執行緒

7.2.主執行緒發訊息給子執行緒

7.3.子執行緒發訊息給主執行緒

7.4.AsyncTask


Handler + Looper + Message:生產者 + 消費者 + 倉庫(任務佇列)

作者淺見。

Handler和Looper是多對一的關係,Looper和MessageQueue是一對一的關係。一個執行緒中,只有一個Looper 和 MessageQueue,但可以有多個Handler。Handler傳送訊息時,本身作為關聯的target存入訊息,Looper獲取訊息時,最終呼叫訊息關聯的Handler進行處理。

1.Handler(生產者add)

Handler:執行緒間通訊,是由Looper和MessageQueue來構建訊息機制的。Handler傳送訊息給MessageQueue,並處理Looper分發過來的訊息(誰傳送的,最終由誰來處理)。

Handler實現執行緒轉換:

Handler在子執行緒把Message或者Runnable傳送到MessageQueue,然後由Looper迴圈排程,再召喚傳送訊息的Handler進行處理,這個時候,處理過程將在Handler所屬執行緒執行,即完成執行緒切換。

可以這樣理解:

(1)跨執行緒

場景:Handler的加工廠(訊息處理場所:handleMessage())在A;Handler想把B的原料運回A加工:

過程:Handler出差到B把原料(Message or Runnable)委託在A的快遞 + 倉儲公司(Looper + MessageQueue)通過中轉郵寄給自己,再由自己在加工廠(A城)加工處理。

(2)子執行緒

場景:Handler想在B本地加工B的原料:

過程:Handler把加工廠(訊息處理場所:handleMessage())建在B城address1,B城address2的原料委託在B的快遞公司(Looper + MessageQueue)通過中轉郵寄給自己,再由自己在加工廠(B城address1)加工處理。

規則:快遞公司郵寄的目的地只能是快遞公司所在地(註冊地點)。

所以,選擇不同城市(Thead)的快遞公司(Looper + MessageQueue),可以實現在不同的地點加工原料(Message)。

區別:A城已經存在成熟的快遞公司,並一直在運營;而B城沒有現成的快遞公司,需要臨時建立,並啟動運營。

1.1.Handler建立物件

每個Handler物件都會繫結一個Looper物件,每個Looper物件對應一個訊息佇列(MessageQueue)。如果在建立Handler時不指定與其繫結的Looper物件,系統預設會將當前執行緒的Looper繫結到該Handler上。

在主執行緒中(系統會自動為主執行緒建立Looper物件,開啟訊息迴圈)可以直接使用new Handler()建立Handler物件,其將自動與主執行緒的Looper物件繫結。

new Handler()等價於new Handler(Looper.myLooper()),Looper.myLooper():獲取當前執行緒的looper物件,類似的 Looper.getMainLooper() 用於獲取主執行緒的Looper物件。

在非主執行緒中,需先在該執行緒中手動開啟Looper(Looper.prepare()-->Looper.loop()),然後將其繫結到Handler物件上;或者通過Looper.getMainLooper(),獲得主執行緒的Looper,將其繫結到此Handler物件上。

建立Handler物件,還可以通過傳入Callback介面型別方式建立。

public Handler(Callback callback) {
    this(callback, false);
}
public interface Callback {
    public boolean handleMessage(Message msg);
}

1.2.Handler傳送訊息

Handler傳送的訊息都會加入到Looper的MessageQueue中。Handler只有一個訊息佇列,即MessageQueue。

關於:

handler.post(new Runnable() {
      @Override
      public void run() {
                        
      }
});

通過post()傳進去的Runnable物件將會被封裝成訊息物件後傳入MessageQueue;使用Handler.sendMessage()將訊息物件直接加入到訊息佇列中。

使用post()將Runnable物件放到訊息佇列中後,當Looper輪循到該Runnable執行時,實際上並不會單獨開啟一個新執行緒,而仍然在當前Looper繫結的執行緒中執行,Handler只是呼叫了該執行緒物件的run()而已。如,在子執行緒中定義了更新UI的指令,若直接開啟將該執行緒執行,則會報錯;而通過post()將其加入到主執行緒的Looper中並執行,就可以實現UI的更新。

使用sendMessage()將訊息物件加入到訊息佇列後,當Looper輪詢到該訊息時,就會呼叫Handler的handleMessage()來對其進行處理。再以更新UI為例,使用這種方法的話,就先將主執行緒的Looper繫結在Handler物件上,過載handleMessage()來處理UI更新,然後向其傳送訊息就可以了。

如果Handler物件與其呼叫者在同一執行緒中,如果在Handler的訊息處理方法中設定了延時操作,則呼叫執行緒也會堵塞,因為Looper輪循是線性的,所以Handler處理Message的過程也是線性的。

1.3.Handler處理訊息

Handler 在處理訊息時,會有三種情況:

if :msg.callback 不為空

這在使用 Handler.postXXX(Runnable) 傳送訊息的時候會發生,直接呼叫 Runnable 的 run() 方法。

else if :mCallback 不為空

如果構造Handler時候以 Handler.Callback 為引數構造 Handler 時會發生,呼叫建構函式裡傳入的 handleMessage() 方法,如果返回 true,那就不往下走了。

else :最後就呼叫 Handler.handleMessage() 方法

需要我們在 Handler 子類裡重寫

1.4.Handler移除訊息

public final void removeCallbacks(Runnable r){

    mQueue.removeMessages(this, r, null);

}

public final void removeMessages(int what) {

    mQueue.removeMessages(this, what, null);

}

2.HandlerThread

2.1定義

HandlerThread 是一個包含 Looper 的 Thread,我們可以直接使用這個 Looper 建立 Handler。避免在子執行緒中對Looper手動繁瑣的操作(Looper.prepare() ——> Looper.loop()),讓我們可以直接線上程中使用 Handler 來處理非同步任務。

2.2適用場景

為某個任務 / 回撥單獨開啟執行緒,並提供由Handler + Looper提供任務(Message)排程機制。

HandlerThread 本身是一個Thread,需要start()啟動。

HandlerThread 在run() 方法中為本執行緒建立了Looper(Looper.prepare()),呼叫 onLooperPrepared 後開啟了迴圈(Looper.loop())

HandlerThread 需要在子類中重寫 onLooperPrepared,做Looper啟動前的初始化工作

HandlerThread 可以指定優先順序,注意這裡的引數是 Process.XXX 而不是 Thread.XXX

HandlerThread = Thread + Looper,適合在子執行緒中執行耗時的、可能有多個任務的操作的場景,比如說多個網路請求操作,或者多檔案 I/O 等等。

2.3.原始碼:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    /**
     * 子類需要重寫的方法,在這裡做一些執行前的初始化工作
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }
    //呼叫 start() 後就會執行的 run()
    @Override
    public void run() {
        mTid = Process.myTid();
	//建立了 Looepr
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();//Looper 已經建立,喚醒阻塞在獲取 Looper 的執行緒
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();//開始迴圈
        mTid = -1;
    }
    /**
     * 獲取當前執行緒的 Looper
     * 如果執行緒不是正常執行的就返回 null
     * 如果執行緒啟動後,Looper 還沒建立,就 wait() 等待 建立 Looper 後 notify
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper.
     */
    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;
    }
    /**
     * @return a shared {@link Handler} associated with this thread
     * @hide
     */
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }
    /**
     * Quits the handler thread's looper.
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     *
     * @see #quitSafely
     */
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    /**
     * Quits the handler thread's looper safely.
     * @return True if the looper looper has been asked to quit or false if the
     * thread had not yet started running.
     */
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }
    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}

2.4.DEMO:

使用 HandlerThread 實現子執行緒完成多個下載任務。

DownloadThread,它有兩個 Handler 型別的成員變數,一個是用於在子執行緒傳遞、執行任務,另一個用於外部傳入,在主執行緒顯示下載狀態:

public class DownloadThread extends HandlerThread implements Handler.Callback {
    private final String KEY_URL = "url";
    public static final int TYPE_START = 1;
    public static final int TYPE_FINISHED = 2;
    /**
     * 外部傳入,通知主執行緒顯示下載狀態
     */
    private Handler mUIHandler;
    /**
     * 內部建立,子執行緒傳遞、執行任務
     */
    private Handler mWorkerHandler;
    /**
     * download list
     */
    private List<String> mDownloadUrlList;
    public DownloadThread(final String name) {
        super(name);
    }
    /**
     * 執行初始化任務
     */
    @Override
    protected void onLooperPrepared() {
        super.onLooperPrepared();
        mWorkerHandler = new Handler(getLooper(), this);
        if (mUIHandler == null) {
            throw new IllegalArgumentException("No UIHandler!");
        }
        // 將接收到的任務訊息挨個新增到訊息佇列中
        for (String url : mDownloadUrlList) {
            Message message = mWorkerHandler.obtainMessage();
            Bundle bundle = new Bundle();
            bundle.putString(KEY_URL, url);
            message.setData(bundle);
            mWorkerHandler.sendMessage(message);
        }
    }
    public void setDownloadUrls(String... urls) {
        mDownloadUrlList = Arrays.asList(urls);
    }
    /**
     * 獲取主執行緒 Handler
     */
    public Handler getUIHandler() {
        return mUIHandler;
    }
    /**
     * 注入主執行緒 Handler
     */
    public DownloadThread setUIHandler(final Handler UIHandler) {
        mUIHandler = UIHandler;
        return this;
    }
    /**
     * 子執行緒中執行任務,完成後傳送訊息到主執行緒
     */
    @Override
    public boolean handleMessage(final Message msg) {
        if (msg == null || msg.getData() == null) {
            return false;
        }
        String url = (String) msg.getData().get(KEY_URL);
        //下載開始,通知主執行緒
        Message startMsg = mUIHandler.obtainMessage(TYPE_START, "\n 開始下載 @" + url);
        mUIHandler.sendMessage(startMsg);
        SystemClock.sleep(2000);    //模擬下載
        //下載完成,通知主執行緒
        Message finishMsg = mUIHandler.obtainMessage(TYPE_FINISHED, "\n 下載完成 @" + url);
        mUIHandler.sendMessage(finishMsg);
        return true;
    }
    @Override
    public boolean quitSafely() {
        mUIHandler = null;
        return super.quitSafely();
    }
}

建立一個子執行緒 mWorkerHandler,在 onLooperPrepared()中初始化 Handler,使用的是 HandlerThread 建立的 Looper 。同時將外部傳入的下載 url 以 Message 的方式傳送到子執行緒中的 MessageQueue 中。

當呼叫 DownloadThread.start() 時,子執行緒中的 Looper 開始工作,會按順序取出訊息佇列中的佇列處理,然後呼叫子執行緒的 Handler 處理(handleMessage()),在這個方法中進行耗時任務,然後通過 mUIHandler 將下載狀態資訊傳遞到主執行緒

在外部Activity(Fragment)實現Hander.Callback介面,呼叫:

mUIHandler = new Handler(this);
mDownloadThread = new DownloadThread("下載執行緒");
mDownloadThread.setUIHandler(mUIHandler);
mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ",
        "http://bbs.005.tv/thread-589833-1-1.html", 
        "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?");
mDownloadThread.start();

同時在Activiy的onDestroy()方法中退出下載執行緒:

mDownloadThread.quitSafely();

3.IntentService

3.1.定義:

IntentService 是一個抽象類,本身是一個Service,同時又在內部建立了一個HandlerThread 。IntentService 使用工作執行緒逐一處理所有啟動請求。如果你不需要在 Service 中執行併發任務,IntentService 是最好的選擇。

3.2.原始碼:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;
    //內部建立的 Handler
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            //呼叫這個方法處理資料
            onHandleIntent((Intent) msg.obj);
            //處理完Service就自盡了
            stopSelf(msg.arg1);
        }
    }
    //子類需要重寫的建構函式,引數是服務的名稱
    public IntentService(String name) {
        super();
        mName = name;
    }
    //設定當前服務被意外關閉後是否重新啟動
    //如果設定為 true,onStartCommand() 方法將返回 Service.START_REDELIVER_INTENT,這樣當
    //當前程序在 onHandleIntent() 方法返回前銷燬時,會重啟程序,重新使用之前的 Intent 啟動這個服務
    //(如果有多個 Intent,只會使用最後的一個)
    //如果設定為 false,onStartCommand() 方法返回 Service.START_NOT_STICKY,當程序銷燬後也不重啟服務
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        //建立時啟動一個 HandlerThread
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        //拿到 HandlerThread 中的 Looper,然後建立一個子執行緒中的 Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        //將 intent 和 startId 以訊息的形式傳送到 Handler
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     *
     * @see android.app.Service#onStartCommand
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }
    @Override
    public void onDestroy() {
        mServiceLooper.quit();    //值得學習的,在銷燬時退出 Looper
    }
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

原始碼做了以下幾件事情:

(1)建立了一個 HandlerThread 預設的工作執行緒

(2)使用 HandlerThread 的 Looper 建立了一個 Handler,這個 Handler 執行在子執行緒

(3)在 onStartCommand() 中呼叫 onStart(),然後在 onStart() 中將 intent 和 startId 以訊息的形式傳送到 Handler

(4)在 Handler 中將訊息佇列中的 Intent 按順序傳遞給 onHandleIntent() 

(5)在處理完所有啟動請求後自動停止服務,不需要我們呼叫 stopSelf()

可以看到在 handleMessage 方法中只調用了一次onHandleIntent() 之後就呼叫 stopSelf() 了,那麼說,該Handler只能執行一個任務麼?

非也!stopSelf() 方法傳遞了一個 id,這個 id 是啟動服務時 IActivityManager 分配的 id,當我們呼叫 stopSelf(id) 方法結束服務時,IActivityManager 會對比當前 id 是否為最新啟動該服務的 id,如果是就關閉服務。子執行緒中onHandleIntent方法經歷了耗時操作,當本次stopSelf(id)呼叫之前,很可能onStartCommand會多次呼叫,每次呼叫都會生成新的啟動id,所以本次stopSelf傳入的id,很可能已經不是最新啟動服務的id了。

因此只有當最後一次啟動 IntentService 的任務執行完畢才會關閉這個服務。

注意:只有onHandleIntent方法是執行在子執行緒的,因為處理訊息的looper是子執行緒HandlerThread提供的looper。其餘方法均在主執行緒執行。

由於最終每個任務的處理都會呼叫 onHandleIntent(),因此使用 IntentService 也很簡單,只需實現 onHandleIntent() 方法,在這裡執行對應的後臺工作即可。

3.3.DEMO:

3.3.1.多檔案下載:

/**
 * Description:使用 IntentService 實現下載
 */
public class DownloadService extends IntentService {
    private static final String TAG = "DownloadService";
    public static final String DOWNLOAD_URL = "down_load_url";
    public static final int WHAT_DOWNLOAD_FINISHED = 1;
    public static final int WHAT_DOWNLOAD_STARTED = 2;
    public DownloadService() {
        super(TAG);
    }
    private static Handler mUIHandler;
    public static void setUIHandler(final Handler UIHandler) {
        mUIHandler = UIHandler;
    }
    /**
     * 這個方法執行在子執行緒
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(final Intent intent) {
        String url = intent.getStringExtra(DOWNLOAD_URL);
        if (!TextUtils.isEmpty(url)) {
            sendMessageToMainThread(WHAT_DOWNLOAD_STARTED, "\n " + DateUtils.getCurrentTime() + " 開始下載任務:\n" + url);
            try {
                Bitmap bitmap = downloadUrlToBitmap(url);
                SystemClock.sleep(1000);    //延遲一秒傳送訊息
                sendMessageToMainThread(WHAT_DOWNLOAD_FINISHED, bitmap);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 傳送訊息到主執行緒
     *
     * @param id
     * @param o
     */
    private void sendMessageToMainThread(final int id, final Object o) {
        if (mUIHandler != null) {
            mUIHandler.sendMessage(mUIHandler.obtainMessage(id, o));
        }
    }
    /**
     * 下載圖片
     *
     * @param url
     * @return
     * @throws Exception
     */
    private Bitmap downloadUrlToBitmap(String url) throws Exception {
        HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection();
        BufferedInputStream in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);
        Bitmap bitmap = BitmapFactory.decodeStream(in);
        urlConnection.disconnect();
        in.close();
        return bitmap;
    }
}

主執行緒呼叫:

public void downloadImage() {
    DownloadService.setUIHandler(new Handler(this));
    Intent intent = new Intent(this, DownloadService.class);
    for (String url : urlList) {
        intent.putExtra(DownloadService.DOWNLOAD_URL, url);
        startService(intent);
    }
    mBtnDownload.setEnabled(false);
}

(1)設定 UI 執行緒的 Handler 給 IntentService

(2)使用 startService(intent) 啟動 IntentService 執行圖片下載任務

(3)在 Handler 的 handleMessage 中根據訊息型別進行相應處理

在第一次啟動 IntentService 後,IntentService 仍然可以接受新的請求,接受到的新的請求被放入了工作佇列中,等待被序列執行。

3.3.2.廣播更新UI:

public class MyIntentService extends IntentService {
    /**
     * 是否正在執行
     */
    private boolean isRunning;
    /**
     *進度
     */
    private int count;
    /**
     * 廣播
     */
    private LocalBroadcastManager mLocalBroadcastManager;
    public MyIntentService() {
        super("MyIntentService");
        Logout.e("MyIntentService");
    }
    @Override
    public void onCreate() {
        super.onCreate();
        Logout.e("onCreate");
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
    }
    @Override
    protected void onHandleIntent(Intent intent) {
        Logout.e("onHandleIntent");
        try {
            Thread.sleep(1000);
            isRunning = true;
            count = 0;
            while (isRunning) {
                count++;
                if (count >= 100) {
                    isRunning = false;
                }
                Thread.sleep(50);
                sendThreadStatus("執行緒執行中...", count);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 傳送進度訊息
     */
    private void sendThreadStatus(String status, int progress) {
        Intent intent = new Intent(IntentServiceActivity.ACTION_TYPE_THREAD);
        intent.putExtra("status", status);
        intent.putExtra("progress", progress);
        mLocalBroadcastManager.sendBroadcast(intent);
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Logout.e("執行緒結束執行..." + count);
    }
}

3.4.總結:

IntentService是一個序列執行非同步任務、會自盡的 Service,優先順序比較高,在後臺不會輕易被系統殺死;它可以接收多個 Intent 請求,然後在子執行緒中按順序執行。

適合於執行由UI觸發的處理多工的後臺Service任務,並可以把後臺任務執行的情況通過一定的機制反饋給UI。

IntentService,在onHandlerIntent()的回撥裡面來處理塞到IntentService的任務。所以IntentService就不僅僅具備了非同步執行緒的特性,還同時保留了Service不受主頁面生命週期影響的特點。我們可以在IntentService裡面通過設定鬧鐘間隔性的觸發非同步任務,例如重新整理資料,更新快取的圖片等。

首先,因為IntentService內建的是HandlerThread作為非同步執行緒,所以每一個交給IntentService的任務都將以佇列的方式逐個被執行到,一旦佇列中有某個任務執行時間過長,那麼就會導致後續的任務都會被延遲處理。

其次,通常使用到IntentService的時候,我們會結合使用BroadcastReceiver把工作執行緒的任務執行結果返回給主UI執行緒。使用廣播容易引起效能問題,我們可以使用LocalBroadcastManager來發送只在程式內部傳遞的廣播,從而提升廣播的效能。我們也可以使用runOnUiThread()快速回調到主UI執行緒。

最後,包含正在執行的IntentService的程式相比起純粹的後臺程式更不容易被系統殺死,該程式的優先順序是介於前臺程式與純後臺程式之間的。

4.Looper(消費者get and deal)

4.1.定義:

訊息迴圈管理器。每個執行緒只有一個Looper,負責管理MessageQueue, 以無限迴圈的方式去查詢MessageQueue中是否有新訊息。如果有,MessageQueue中取出訊息,並將訊息分給對應的Handler處理;如果沒有就standby(等待)。一個執行緒建立Handler時首先需要建立Looper的,不然報錯:RuntimeException: No Looper; Looper.prepare() wasn't called on this thread,而且每個執行緒下只能建立一個Looper,不然會報錯:RuntimeException: Only one Looper may be created per thread。

4.2.機制:

執行緒中預設沒有 Looper,我們需要呼叫 Looper.prepare() 方法為當前執行緒建立一個 Looper,呼叫Looper.loop()來使訊息迴圈起作用,使用Looper.prepare()和Looper.loop()就可以讓訊息處理在當前執行緒(主執行緒預設開啟,不需要手動呼叫)中完成。

Looper.loop()之後,進入無限迴圈:

for (;;) {    //無限迴圈模式
    Message msg = queue.next(); //從訊息佇列中讀取訊息,可能會阻塞
    if (msg == null) {    //當訊息佇列中沒有訊息時就會返回,不過這隻發生在 queue 退出的時候
        return;
    }
    //...
    try {
        msg.target.dispatchMessage(msg);    //呼叫訊息關聯的 Handler 處理訊息
    } finally {
        if (traceTag != 0) {
            Trace.traceEnd(traceTag);
        }
    }
    //...
    msg.recycleUnchecked();    //標記這個訊息被回收
}

可以看到,loop()其實是呼叫 MessageQueue.next() 方法取訊息,如果沒有訊息的話會阻塞,直到有新的訊息進入或者訊息佇列退出。

拿到訊息後Looper 並沒有執行訊息,而是呼叫訊息關聯的 Handler (target)處理訊息,真正執行訊息的還是新增訊息到佇列中的那個 Handler。

所以應該說Looper屬於哪個執行緒的,訊息處理就在哪個執行緒執行!!!

4.3.應用:

首先,Android 本身是由事件驅動的,在主執行緒中…… looper.loop() 不斷地接收事件、處理事件。四大元件的排程、輸入事件、繪製請求、每一個點選觸控或者說Activity的生命週期都是執行在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。

只能是UI執行緒中某一個訊息或者說對訊息的處理阻塞了 Looper.loop(),而不能是 Looper.loop() 阻塞UI執行緒。也可以理解為,UI執行緒中執行的程式碼其實是執行在looper.loop()的迴圈週期中。

主執行緒同一時間只能處理一個 Message,所以,如果UI執行緒中某個訊息處理時間過長(點選事件業務處理操作),那麼下一次的訊息比如使用者的點選事件不能處理了,整個迴圈就會產生卡頓,時間一長就成了ANR

4.4.quit:

Looper開啟loop()之後,必須保證結束quit(),quitSafely()!!!

Looper 兩種結束方式的區別:

(1)quit():

立即回收連結串列中所有訊息,並且禁止後續訊息入佇列。在停止後如果 Handler 還發送訊息,會返回 false,表示入隊失敗,所以這個方法是不安全的。

(2)quitSafely():

將還未執行的訊息回收掉,後續進入佇列的訊息將不會被處理,同時標記訊息佇列為退出狀態。Handler.sendMessage 也會返回 false。當訊息佇列被標記位退出狀態時,它的 next() 方法會返回 null,於是 Looper.loop() 迴圈就結束了。

5.Message(任務,產品)(Intent, Runnable, Message)

5.1.定義:

Message可以承載任意型別的物件和描述資訊,可以被髮送給 Handler。

5.2.關鍵屬性:

(1)public int what;                 // 用來標識一個訊息,接收訊息方可以根據/它知道這個訊息是做什麼的

(2)public int arg1;

         public int arg2;                  // 資料載體:int 型

(3)public Object obj;              // 資料載體: 物件型

(4)/*package*/Bundle data;    // 資料載體:複雜物件

(5)/*package*/Handler target; // 傳送和處理訊息關聯的 Handler

(6)private static final Object sPoolSync = new Object();

(7)private static Message sPool; // 回收訊息連結串列

(8)private static int sPoolSize = 0;

5.3.構造方式:

new Message();

Message.obtain();  推薦

Handler.obtainMessage();   推薦

推薦使用後兩個,會從一個訊息回收池裡獲取訊息,而不是新建一個,這樣可以節省記憶體。原理:如果 sPool回收訊息連結串列)存在就從複用訊息連結串列頭部取一個訊息,然後重置它的標誌位;如果不存在複用訊息連結串列就新建一個訊息。

一個訊息在被 Looper 處理時或者移出佇列時會被標識為 FLAG_IN_USE,然後會被加入回收的訊息連結串列,這樣我們呼叫 Message.obtain() 方法時就可以從回收的訊息池中獲取一箇舊的訊息,從而節約成本。

6.MessageQueue(任務佇列,產品池)

6.1定義:

MessageQueue(先進先出):訊息佇列,管理著一個 Message 的列表,Handlers 為它新增訊息,Looper 從中取訊息。。雖然名為佇列,但事實上它的內部儲存結構並不是真正的佇列,而是採用單鏈表的資料結構來儲存訊息列表的,其中主要有插入enqueue()和從中拿走並刪除next()兩個方法。

7.應用案例

寫在Looper.loop()之後的程式碼不會被立即執行,這個函式內部是一個迴圈,當呼叫後mHandler.getLooper().quit()後,loop才會中止,其後的程式碼才能得以執行。

警惕執行緒未終止造成的記憶體洩露;譬如在Activity中關聯了一個生命週期超過Activity的Thread,在退出Activity時切記結束執行緒。一個典型的例子就是HandlerThread的run方法是一個死迴圈,它不會自己結束,執行緒的生命週期超過了Activity生命週期,我們必須手動在Activity的銷燬方法中中調運thread.getLooper().quit();才不會洩露。

另外,注意不要在任何子執行緒持有 UI 元件或者 Activity 的引用。

7.1.子執行緒發訊息給子執行緒

class TestThread extends Thread{
    @Override
    public void run() {
        super.run();
        // prepare MessageQueue and looper
        Looper.prepare();
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
// deal message……
            }
        };
handler.sendMessage(new Message())
        // start looper
        Looper.loop();
    }
}

7.2.主執行緒發訊息給子執行緒

(1)子執行緒中建立持有子執行緒Looper的Handler

(2)主執行緒使用子執行緒中Handler傳送訊息,最終在子執行緒的Handler中執行。

7.3.子執行緒發訊息給主執行緒

方案一:子執行緒中建立持有主執行緒Looper的Handler,由該Handler在子執行緒傳送訊息到主執行緒的MessageQueue,並由該Handler在主執行緒執行。

          Handler handler = new Handler(getMainLooper());

方案二:子執行緒持有主執行緒建立的Handler物件,並由該Handler在子執行緒傳送訊息到主執行緒的MessageQueue,並由該Handler在主執行緒執行。

7.4.AsyncTask

預設情況下,所有的AsyncTask任務都是被線性排程執行的,他們處在同一個任務隊列當中,按順序逐個執行。假設你按照順序啟動20個AsyncTask,一旦其中的某個AsyncTask執行時間過長,佇列中的其他剩餘AsyncTask都處於阻塞狀態,必須等到該任務執行完畢之後才能夠有機會執行下一個任務。

為了解決線性佇列等待的問題,我們可以使用AsyncTask.executeOnExecutor()強制指定AsyncTask使用執行緒池併發排程任務。

使用AsyncTask很容易導致記憶體洩漏,一旦把AsyncTask寫成Activity的內部類的形式就很容易因為AsyncTask生命週期的不確定而導致Activity發生洩漏。