編輯:關於Android編程
我們在做 Android 開發時,常常需要實現異步加載圖片/網頁/其他。事實上,要實現異步加載,就需要實現線程間通信,而在 Android 中結合使用 Handler、Looper、Message 能夠讓不同的線程通信,完成異步任務。雖然 Android 官方為我們提供了 AsyncTask 類來完成異步任務,但這個類存在許多問題,並不好用,而且,AsyncTask 也是通過 Handler 和 Thread 來實現異步加載的,所以學習這方面的知識是有必要的
本文講解思路大致如下:繪制 Android 消息處理機制圖 -> 源碼剖析消息處理機制中的組件 -> 實現一個圖片異步加載 Demo。最終 Demo 效果圖如下:
我們不妨先想想,一個消息處理機制需要什麼?當然是:
消息源 消息隊列 消息處理器 消息管理器其中消息管理器又將劃分為三個子模塊:消息獲取、消息分發、消息循環。我們先不管 Android 內部將如何實現消息處理機制(因為處理機制的抽象結構肯定是一樣的,只是具體實現不一樣),按照我們列出來的4大模塊畫出一個簡單的消息處理模型:
現在我們已經知道消息處理模型需要哪些組件了,那就去 Android SDK 裡面找相應的類吧~然後我們會發現下面四個類:
Message 類代表消息 MessageQueue 類代表消息隊列 Handler 代表消息獲取、消息處理 Looper 代表消息循環、消息分發可能有人會懷疑我在吹nb,在騙大家,這個時候我只能選擇看源碼了……為了方便大家理解,我將從 Looper 類開始分析,因為 Looper 類在消息處理機制中是個“承上啟下”的功能模塊。
在解析 Looper 之前,不妨先來想想為什麼需要 Looper 吧。
我們在進行 Android 開發的時候,為了不阻塞主線程(UI 線程),常常需要另開一個線程完成一些操作,而這些操作有一些執行一次就完了,有一些可能需要執行幾次,幾十次,甚至只要程序進程存活就要不斷執行該操作。而普通線程通過 start() 方法只能執行相關動作一次,為了滿足多次執行的需求,於是有了 Looper。
那麼我們就進入 Looper 的源碼,看看 Looper 中有哪些成員吧:
public final class Looper {
private static final String TAG = Looper;
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private Printer mLogging;
}
大家可以看到,Looper 的核心成員是一個消息隊列,該Looper 對應的線程,ThreadLocal 對象,和一個主線程 Looper 的引用。我們根據 Looper 的使用流程來分析它們的作用:
要使用 Looper,就必須調用 Looper 的 prepare() 方法:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException(Only one Looper may be created per thread);
}
sThreadLocal.set(new Looper(quitAllowed));
}
我們可以看到,調用 prepare 方法後會通過 ThreadLocal 的 set 方法創建一個 Looper 對象,而且一個線程只能創建一個 Looper,我們不妨看看 ThreadLocal 通過 set 方法對 Looper 對象干了啥:
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
實際操作 Looper 對象的是 values() 方法返回對象
Values values(Thread current) {
return current.localValues;
}
values() 方法返回的對象是一個線程的內部變量,我們再進去看看會發現:在 Thread 類內部是這樣定義 localValues 的 - ThreadLocal.Values localValues
。也就是說,set 方法實際完成的操作是,將 Looper 對象與線程綁定,並且該 Looper 對象只在該線程內有效,其他線程無法訪問該 Looper 對象。
執行完 prepare() 方法之後,我們就要調用 Looper 的 loop() 方法來實現循環了:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException(No Looper; Looper.prepare() wasn't called on this thread.);
}
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);
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.recycleUnchecked();
}
}
方法有點長,但實際邏輯比較簡單:首先判斷 prepare() 方法是否被調用,以確保 Looper 和某個線程綁定(需要注意的是:默認情況下,新建的 Looper 對象都與主線程綁定),然後獲取對應線程的消息隊列,之後就不斷循環讀取隊列中的消息,如果隊列中沒有消息時 Loop() 方法就會結束(一般不會出現這種情況),否則將消息交給 msg.target 對象分發。
以上就是 Looper 的核心代碼了,通過分析我們可以了解到 Looper 與線程的關系,以及在消息分發機制中所起的作用,如圖:
既然我們在分析 Looper 源碼的最後提到了 Message 類,那麼我們就先來看看 Message 類~那麼 Message 類作為消息的載體到底存儲了什麼,做了什麼呢?
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
public int sendingUid = -1;
/*package*/ static final int FLAG_IN_USE = 1 << 0;
/** If set message is asynchronous */
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
/** Flags to clear in the copyFrom method */
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/ int flags;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ Message next;
現在我們可以知道,原來剛剛處理消息的 target 就是 Handler 類的對象啦。在 Message 類裡,what 用於辨識 Message 的用途,arg1 和 arg2 用於傳遞一些簡單的數據,obj 用於傳遞對象,data 用於傳遞復雜數據。
Message 類的方法我覺得是沒啥好說的,基本上都是 get/set 方法,當然還有回收方法,例如在 Looper 的 loop() 方法中,每一次循環結束都會執行 Message 的 recycleUnchecked() 方法,將被分發 Message 對象回收。
可能有人會奇怪,消息如果沒有被處理就被回收了不會發生消息丟失的情況嗎?莫慌,等會我會在分析 Handler 處理消息的時候給大家解釋。
我們在前面的分析中看到,真正處理消息的是 Handler 的 dispatchMessage() 方法,那麼我們就從這個方法入手分析 Handler 吧:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在 dispatchMessage() 方法中,如果 msg 的 callback 不為 null 會調用 Handler 的 handleMessage() 方法處理消息。也就是說,只要消息的 callback 不為 null,就會調用 handleCallback() 方法,那麼未處理的消息會不會回到消息隊列呢?
private static void handleCallback(Message message) {
message.callback.run();
}
看到這裡有沒有恍然大悟的感覺呢?剛剛我們在分析 Message 源碼的時候已經知道,callback 就是 Runnable 接口的實例,也就是說,如果消息沒有被處理,就會回到消息隊列中啦。那麼 Handler 又是怎樣處理消息的呢?
public void handleMessage(Message msg) {
}
竟然是個空方法……不過也很正常,因為 Handler 類只需要提供抽象,具體的處理邏輯應該由開發者決定嘛。那我們分析就到此為止了嗎?才沒有!我們還沒有剖析 Handler 能夠實現異步事件處理的原因呢,回到 Handler 的源碼,我們會看到下面這個代碼段:
final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;
我靠……Handler 裡面居然擁有消息隊列、Looper、異步標志位,我們回想一下剛剛分析得到過什麼結論:一個 Looper 只能屬於一個線程,Looper 有對應線程的消息隊列。我們再來看看 Handler 的構造方法,隨便挑一個吧:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class 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());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
Can't create handler inside thread that has not called Looper.prepare());
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
我們可以看到,Handler 內的消息隊列就是 Looper 裡的消息隊列,也就是說 Handler 能夠與任何一個線程的消息隊列進行通信,並處理其中的消息,或者發送信息到其他線程的消息隊列中!
完成上面的分析以後,我們就知道在 Android 中的消息處理機制了,那麼現在就來實現一個異步處理 Demo吧。Demo 非常簡單,就是異步下載一張圖片,並把它顯示到一個 ImageView 中,代碼比較多,就只給出核心代碼,下面有下載地址:
public class DownloadTask implements Runnable{
…………
private void updateMsg(Message msg){
Bundle data = new Bundle();
Bitmap img = downImage(url);
data.putString(url, url);
data.putParcelable(img, img);
msg.setData(data);
}
public Bitmap downImage(String url) {
Bitmap img = null;
try {
URL mUrl = new URL(url);
HttpURLConnection conn = (HttpURLConnection) mUrl.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
img = BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.d(TAG, downloadImg-Exception);
}
return img;
}
}
DownloadTask 類負責處理下載任務,下載開始下載任務,下載任務完成後將圖片地址和圖片存到 msg 裡邊,發送給 DownloadHandler :
public class DownloadHandler extends Handler{
……
@Override
public void handleMessage(Message msg) {
String url = msg.getData().getString(url);
Bitmap img = msg.getData().getParcelable(img);
Log.d(TAG, url);
loader.iLoader.update(img);
}
}
最後通過 ILoader 接口更新外部 UI:
public interface ILoader {
public void update(Bitmap img);
}
}
貼上代碼: 1.擴展Gallery: 復制代碼 代碼如下: public class GalleryFlow extends Gallery { private Came
一般情況下,Android系統安裝apk會出現一個安裝界面,用戶可以點擊確定或者取消來進行apk的安裝。 但在實際的項目需求中,有一種需求,就是希望apk在後台安裝(不出
相信很多朋友都有過錯手把內存卡的數據刪掉,這是重要的文件該怎麼辦呢?還能恢復內存卡的數據嗎?小編告訴你,有辦法!錯誤刪除數據:1、如果朋友們真的錯手把重要的
由於最近工作需要,需要一個自定義插件,本人研究了很久終於做出一個最簡單的插件,是基於android平台來開發的,雖然寫博客很花時間,但是為了以後再次查看復習能很好的提供參