編輯:關於Android編程
大家對於Android中的消息處理機制的用法一定都比較熟悉,至於工作原理估計不少人有研究。就像我們自己寫的類我們用起來比較熟悉一樣,如果我們熟悉了消息處理機制的具體實現,那麼我們用起來肯定也會事半功倍。
博主之前只是稍有涉獵,對其中一些地方也還心存疑慮,比如既然Looper.loop()裡是一個死循環,那它會不會很消耗CPU呢?死循環阻塞了線程,那我們其他的事務是如何被處理的呢?Android的UI線程是在哪裡被初始化的呢?等等。索性今天就把他們放到一起,說道說道。
帶有消息隊列,用來執行循環性任務(例如主線程、android.os.HandlerThread)
有消息時就處理
沒有消息時就睡眠
沒有消息隊列,用來執行一次性任務(例如java.lang.Thread)
任務一旦執行完成便退出Message(消息)
MessageQueue(消息隊列)
Looper(消息循環)
Handler(消息發送和處理)
具體工作過程
消息隊列的創建
消息循環
消息的發送
最基本的兩個API
Handler.sendMessage
帶一個Message參數,用來描述消息的內容Handler.post
帶一個Runnable參數,會被轉換為一個Message參數消息的處理
android.os.HandlerThread
適合用來處於不需要更新UI的後台任務android.os.AyncTask
適合用來處於需要更新UI的後台任務ThreadLocal並不是一個Thread,而是Thread的局部變量。當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。
從線程的角度看,目標變量就象是線程的本地變量,這也是類名中“Local”所要表達的意思。
用於在指定線程中運行一個消息循環,一旦有新任務則執行,執行完繼續等待下一個任務,即變成Looper線程。Looper類的注釋裡有這樣一個例子:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
//將當前線程初始化為Looper線程
Looper.prepare();
// ...其他處理,如實例化handler
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
// 開始循環處理消息隊列
Looper.loop();
}
}
其實核心代碼就兩行,我們先來看下Looper.prepare()方法的具體實現
public final class Looper {
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static Looper sMainLooper; // guarded by Looper.class
//Looper內的消息隊列
final MessageQueue mQueue;
// 當前線程
final Thread mThread;
private Printer mLogging;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//試圖在有Looper的線程中再次創建Looper將拋出異常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//~省略部分無關代碼~
}
從中我們可以看到以下幾點:
prepare()其核心就是將looper對象定義為ThreadLocal 一個Thread只能有一個Looper對象 prepare()方法會調用Looper的構造方法,初始化一個消息隊列,並且指定當前線程 在調用Looper.loop()方法之前,確保已經調用了prepare(boolean quitAllowed)方法,並且我們可以調用quite方法結束循環
說到初始化MessageQueue,我們來看下它是干什麼的
/**
* Low-level class holding the list of messages to be dispatched by a
* {@link Looper}. Messages are not added directly to a MessageQueue,
* but rather through {@link Handler} objects associated with the Looper.
*
*
You can retrieve the MessageQueue for the current thread with
* {@link Looper#myQueue() Looper.myQueue()}.
*/
它是一個低等級的持有Messages集合的類,被Looper分發。Messages並不是直接加到MessageQueue的,而是通過Handler對象和Looper關聯到一起。我們可以通過Looper.myQueue()方法來檢索當前線程的MessageQueue。
接下來再看看Looper.loop()
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//得到當前線程Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//得到當前looper的MessageQueue
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);
}
//將真正的處理工作交給message的target,即handler
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();
}
}
通過這段代碼可知,調用loop方法後,Looper線程就開始真正工作了,它不斷從自己的MessageQueue中取出隊頭的消息(或者說是任務)執行。
除了prepare()和loop()方法,Looper類還有一些比較有用的方法,比如
Looper.myLooper()得到當前線程looper對象
getThread()得到looper對象所屬線程
quit()方法結束looper循環
這裡需要注意的一點是,quit()方法其實調用的是MessageWueue的quite(boolean safe)方法。
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
我們看到其實主線程是不能調用這個方法退出消息隊列的。至於mQuitAllowed參數是在Looper初始化的時候初始化的,主線程初始化調用的是Looper.prepareMainLooper()方法,這個方法把參數設置為false。
Message
在整個消息處理機制中,message又叫task,封裝了任務攜帶的信息和處理該任務的handler。我們看下這個類的注釋
/**
*
* Defines a message containing a description and arbitrary data object that can be
* sent to a {@link Handler}. This object contains two extra int fields and an
* extra object field that allow you to not do allocations in many cases.
*
*
While the constructor of Message is public, the best way to get
* one of these is to call {@link #obtain Message.obtain()} or one of the
* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
* them from a pool of recycled objects.
*/
這個類定義了一個包含描述和一個任意類型對象的對象,它可以被發送給Handler。
從注釋裡我們還可以了解到以下幾點:
盡管Message有public的默認構造方法,但是你應該通過Message.obtain()來從消息池中獲得空消息對象,以節省資源。
如果你的message只需要攜帶簡單的int信息,請優先使用Message.arg1和Message.arg2來傳遞信息,這比用Bundle更省內存
用message.what來標識信息,以便用不同方式處理message。
Handler
從MessageQueue的注釋中,我們知道添加消息到消息隊列是通過Handler來操作的。我們通過源碼來看下具體是怎麼實現的
/**
* A Handler allows you to send and process {@link Message} and Runnable
* objects associated with a thread’s {@link MessageQueue}. Each Handler
* instance is associated with a single thread and that thread’s message
* queue. When you create a new Handler, it is bound to the thread /
* message queue of the thread that is creating it – from that point on,
* it will deliver messages and runnables to that message queue and execute
* them as they come out of the message queue.
*
*
There are two main uses for a Handler: (1) to schedule messages and
* runnables to be executed as some point in the future; and (2) to enqueue
* an action to be performed on a different thread than your own.
*
*/
注釋比較簡單,這裡就不過多翻譯了,主要內容是:每一個Handler實例關聯了一個單一的ghread和這個thread的messagequeue,當Handler的實例被創建的時候它就被綁定到了創建它的thread。它用來調度message和runnables在未來某個時間點的執行,還可以排列其他線程裡執行的操作。
public class Handler {
//~省略部分無關代碼~
final MessageQueue mQueue;
final Looper mLooper;
public Handler() {
this(null, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(boolean async) {
this(null, async);
}
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;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//~省略部分無關代碼~
}
先看構造方法,其實裡邊的重點是初始化了兩個變量,把關聯looper的MessageQueue作為自己的MessageQueue,因此它的消息將發送到關聯looper的MessageQueue上。
有了handler之後,我們就可以使用Handler提供的post和send系列方法向MessageQueue上發送消息了。其實post發出的Runnable對象最後都被封裝成message對象
接下來我們看一下handler是如何發送消息的
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
/**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) uptimeMillis.
* The time-base is {@link android.os.SystemClock#uptimeMillis}.
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
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);
}
這裡我們只列出了一種調用關系,其他調用關系大同小異,我們來分析一下
調用getPostMessage(r),把runnable對象添加到一個Message對象中。 sendMessageDelayed(getPostMessage(r), 0),基本沒做什麼操作,又繼續調用sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)方法,在這個方法裡拿到創建這個Handler對象的線程持有的MessageQueue。 調用enqueueMessage(queue, msg, uptimeMillis)方法,給msg對象的target變量賦值為當前的Handler對象,然後放入到MessageQueue。
那發送消息說完了,那我們的消息是怎樣被處理的呢?
我們看到message.target為該handler對象,這確保了looper執行到該message時能找到處理它的handler,即loop()方法中的關鍵代碼。
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
我們看到這裡最終又調用到了我們重寫的handleMessage(Message msg)方法來做處理子線程發來的消息或者調用handleCallback(Message message)去執行我們子線程中定義並傳過來的操作。
思考
為什麼要有Handler機制
這個問題可以這麼考慮
我們如何在子線程更新UI?——使用Handler機制傳遞消息到主線程(UI線程) 為什麼我們不在子線程更新UI呢?——因為Android是單線程模型 為什麼要做成單線程模型呢?——多線程並發訪問UI可能會導致UI控件處於不可預期的狀態。如果加鎖,雖然能解決,但是缺點也很明顯:1.鎖機制讓UI訪問邏輯變得復雜;2.加鎖導致效率低下。
Handler機制與命令模式
我在之前分享過Android源碼中的命令模式,我們仔細分下一下不難看出Handler機制其實是一個非典型的命令模式。
接收者:Handler,執行消息處理操作。
調用者:Looper,調用消息的的處理方法。
命令角色:Message,消息類。
客戶端:Thread,創建消息並綁定Handler(接受者)。
Android主線程是如何管理子線程消息的
我們知道Android上一個應用的入口,應該是ActivityThread。和普通的Java類一樣,入口是一個main方法。
public static void main(String[] args) {
//~省略部分無關代碼~
//創建Looper和MessageQueue對象,用於處理主線程的消息
Looper.prepareMainLooper();
//創建ActivityThread對象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (創建新線程)
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//消息循環運行
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我們可以看到其實我們在這裡初始化了我們主線程(UI)的Looper並且啟動它。然後就可以處理子線程和其他組件發來的消息了。
為什麼主線程不會因為Looper.loop()裡的死循環卡死或者不能處理其他事務
這裡涉及到的東西比較多,概括的理解是這樣的
為什麼不會卡死
handler機制是使用pipe來實現的,主線程沒有消息處理時會阻塞在管道的讀端。
binder線程會往主線程消息隊列裡添加消息,然後往管道寫端寫一個字節,這樣就能喚醒主線程從管道讀端返回,也就是說queue.next()會調用返回。
主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。
既然是死循環又如何去處理其他事務呢?
答案是通過創建新線程的方式。
我們看到main方法裡調用了thread.attach(false),這裡便會創建一個Binder線程(具體是指ApplicationThread,Binder的服務端,用於接收系統服務AMS發送來的事件),該Binder線程通過Handler將Message發送給主線程。
ActivityThread對應的Handler是一個內部類H,裡邊包含了啟動Activity、處理Activity生命周期等方法。
本文代碼以MTK平台Android 4.4為分析對象,與Google原生AOSP有些許差異,請讀者知悉。 Android系統通話記錄存儲在聯系人數據庫contacts
效果圖:使用了 一個時間相關的工具類 package com.yqy.yqy_date;import android.util.Log;import java.
在上一篇文章寫了SAX解析XML,感覺Pull方式和SAX方式非常相似,只是SAX需要一個輔助的類,解析時觸發事件後在回調方法裡面寫代碼,而Pull則通過調
為多屏設計(一) - 支持多個屏幕尺寸參考地址:http://developer.android.com/training/multiscreen/index.htmlA