標籤:重複 resources 覆蓋 標識 關係 turn int android 圖片
AsyncTask是執行後台線程的最簡單方式,但它不適用於那些重複且長時間啟動並執行任務。
1. Looper
Android中,線程擁有一個訊息佇列(message queue),使用訊息佇列的線程叫做訊息迴圈(message loop)。訊息迴圈會迴圈檢查隊列上是否有新訊息。
訊息迴圈由線程和looper組成,Looper對象管理著線程的訊息佇列。
主線程就是個訊息迴圈,因此也擁有Looper,主線程的所有工作都是由其looper完成的,looper不斷的從訊息佇列中抓去訊息,然後完成訊息指定的任務。
2. Message
訊息是Message類的一個執行個體,它有好幾個執行個體變數,其中有三個需要在實現時定義。
1.What:使用者定義的int型訊息代碼,用來描述訊息。
2.obj:隨訊息發送的使用者指定對象。
3.target: 處理訊息的Handler。
3. Handler
Message的目標(target)是Handler類的一個執行個體,Handler可看作message handler的簡稱,建立Message時,它會自動與一個handler相關聯,message待處理時,Handler對象負責觸發訊息處理事件、
Handler不僅僅是處理Message的目標,也是建立和發布Message的介面。
4. 關係
Looper擁有Message收件匣,所以Message必須在Looper上發布或處理。為與Looper協同工作,Handler總是引用著它。
一個Handler僅與一個Looper相關聯,一個Message也僅於一個目標Handler(也稱作Message目標)相關聯。Looper擁有整個Message隊列,多個Message可以引用同一目標Handler。
多個Handler也可與一個Looper相關聯,這意味著一個Handler的Message可能與另一個Handler的Message存放在同一訊息佇列中。
5. 使用Handler
通常不需要手動設定訊息的目標Handler。建立資訊時,調用Handler.obtainMessage()方法。當傳入其他訊息欄位給他時,該方法會自動化佈建目標給Handler對象。
為避免建立新的Message對象,Handler.obtainMessage()方法會從公用迴圈吃擷取訊息。
一旦取得Message,就可以調用sendToTarget()方法將其發送給它的Handler,然後Handler會將這個Message放置在Looper訊息佇列的尾部。
Looper取得訊息佇列中的特定訊息後,會將它發送給訊息目標去處理。訊息一般是在目標的Handler.handleMessage()實現方法中進行處理。
6. HandlerThread
HandlerThread類幫我們完成了建立looper的過程,只要繼承它就能省去一些工作。
public class ThumbnailDownloader<T> extends HandlerThread { private static final String TAG = "ThumbnailDownloader"; private static final int MESSAGE_DOWNLOAD = 0; //標識下載請求 private Boolean mHasQuit = false; private Handler mRequestHandler; //儲存對Handler的引用,這個Handler負責在ThumbnailDownloader後台線程上管理下載請求訊息佇列。這個Handler也負責從訊息佇列裡取出並處理下載請求訊息。 //onLooperPrepared()在Looper首次檢查訊息佇列之前調用的。 @Override protected void onLooperPrepared(){ mRequestHandler = new Handler(){ @Override public void handleMessage(Message msg){ //隊列中的下載訊息取出並可以處理時,就會觸發調用Handler.handleMessage()方法。 //處理操作 } }; } public void queueThumbnail(T target, String url) { //當傳入其他訊息欄位給它時,該方法會自動化佈建目標給Handler對象(obtainMessage) //sendToTarget()方法將Message發送給它的Handler,然後Handler會將這個Message放置在Looper訊息佇列的尾部。 mRequestHandler.obtainMessage(MESSAGE_DOWNLOAD,target).sendToTarget(); } public void clearQueue(){ mRequestHandler.removeMessages(MESSAGE_DOWNLOAD); } }
主線程中這樣調用:
mThumbnailDownloader = new ThumbnailDownloader<>(responseHandler);mThumbnailDownloader.start();mThumbnailDownloader.getLooper(); //在start()方法之後調用getLooper()方法是一種保證線程就緒的處理方式。可以避免潛在競爭。// 在需要的時候調用mThumbnailDownloader.queueThumbnail(holder, url);
7.線程互動
主線程現在能夠適時調用這個線程的方法,用於下載圖片了。但是還存在一個問題,那就是下載線程下載完一個任務以後如何更新視圖呢?我們知道 UI 只能在主線程裡更新,所以我們採用在主線程裡聲明一個 Handler,傳遞給下載線程,讓下載線程在下載完成後在主線程執行更新操作。因為不能直接引用主線程的方法,故而在這裡用到了回調。
7.1下載線程中:
// ThumbnailDownloader,也就是下載線程中// 成員聲明private Handler mResponseHandler;private ThumbnailDowloadListener<T> mThumbnailDownloadListener;// 回調介面public interface ThumbnailDowloadListener<T> {/* * 圖片下載完成,可以交給UI去顯示時,介面中的方法就會被調用。 * 會使用這個方法把處理已下載圖片的任務代理給另一個類(PhotoGalleryFragment),這樣ThumbnailDownloader就可以把下載結果傳給其他視圖對象。 */ void onThumbnailDownloaded(T target, Bitmap thumbnail);}public void setThumbnailDownloaderListener(ThumbnailDowloadListener<T> listener) { mThumbnailDownloadListener = listener;}// 通過建構函式傳遞主線程的 Handlerpublic ThumbnailDowloader(Handler responseHandler) { super(TAG); mResponseHandler = responseHandler;}
這樣,主線程通過調用這些方法,就能夠讓下載線程擷取到主線程的 Handler 和回調介面執行個體。
7.2主線程中
// 成員聲明private ThumbnailDowloader<PhotoHolder> mThumbnailDownloader;// 傳遞執行個體給下載線程// 這個 Handler 在主線程中建立,所以是和主線程 Looper 相關聯的Handler responseHandler = new Handler(); mThumbnailDownloader = new ThumbnailDowloader<>(responseHandler);mThumbnailDownloader.setThumbnailDownloaderListener( new ThumbnailDowloader.ThumbnailDowloadListener<PhotoHolder>() { @Override public void onThumbnailDownloaded(PhotoHolder target, Bitmap thumbnail) { Drawable drawable = new BitmapDrawable(getResources(), thumbnail); target.bindDrawable(drawable); } });
8.線程互動
現在,通過 mResponseHandler,下載線程能夠訪問與主線程 Looper 綁定的 Handler。同時,還有 ThumbnailDownloadListener 使用返回的 Bitmap 執行 UI 更新操作。具體來說, 就是通過 onThumbnailDownloaded 實現,使用新下載的 Bitmap 來設定 PhotoHolder 的 Drawable。
和在下載線程上把下載圖片的請求放入訊息佇列類似,我們也可以返回定製 Message 給主線程,要求顯示已下載圖片。不過,這需要另一個 Handler 子類,以及一個 handleMessage(…) 覆蓋方法。方便起見,我們轉而使用另一個方便的 Handler 方法——post(Runnable)。
mResponseHandler.post(new Runnable() { @Override public void run() { if (mRequestMap.get(target) != url || mHasQuit) { return; } mRequestMap.remove(target); mThumbnailDownloadListener.onThumbnailDownloaded(target, bitmap); }});
在這裡,建立的 Runnable 對象會被當成 Message 的回調方法,直接執行 run() 方法,所以相當於發送一個訊息,裡面寫明了怎麼做,而不是把對象和訊息類型發給 Handler,讓 Handler 決定怎麼做。
安卓權威編程指南-筆記(第24章 Looper Handler 和 HandlerThread)