編輯:關於Android編程
大家知道子線程沒有辦法對UI界面上的內容進行操作,如果操作,將拋出異常:CalledFromWrongThreadException,為了讓子線程能間接操作UI界面,Android中引入了Handler消息傳遞機制,通過Handler切換到主線程進行UI操作。
Handler用於發送和處理消息。而發出的Message經過一系列的周轉後,最終會傳遞回Handler中,最後更新UI。
Message是在線程之間傳遞的消息,它可以在內部攜帶少量的信息,用於在不同線程之前交換數據。
MessageQueue是消息隊列,用於存放Message。Message在消息隊列中,等待Looper取出。每個線程中只會有一個MessageQueue對象。
Looper是每個線程中的MessageQueue的管家,調用Looper的loop()方法後,就會進入一個無限循環中,每當MessageQueue中存在一個Message,Looper對象就會將其取出,傳遞到Handler中進行處理。每個線程中只會有一個Looper對象。
從上面的基本概念中,我們不難會產生疑問,比如:
在Message的周轉過程中,是怎麼切換到主線程的? 如果當前線程中new了多個Handler,它們發送消息後會錯亂嗎?會不會找錯Handler對象。 Handler的post方法和sendMessage有什麼區別? 怎麼保證每個線程中只能存在一個Looper和MessageQueue? 子線程為什麼不能直接new Handler? 主線程為什麼不用手動創建Looper? 主線程中的Looper無限循環,為什麼沒有造成ANR?下面,我將從源碼角度,對以上疑問進行探索。看完本篇博客,相信你心裡就會有答案了。
我們先來簡單認識一下Message
public int what;
public int arg1;
public int arg2;
public Object obj;
上面幾個的用法,大家都知道,就不用介紹了。我們來看看下面的。
Handler target;
Runnable callback;
Message中可以保存Handler對象,也可以保存Runnable,具體用法看完本篇博客就知道了。
還記得Message.obtain
用法嗎?
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0;
sPoolSize--;
return m;
}
}
return new Message();
}
因為Message被處理完過後,是不會直接回收的,默認會保存一定數量的Message以供復用。我們可以使用Message.obtain
復用,以免創建多余的Message。
MessageQueue是什麼?我們可以給它理解成一個集合,比如List,我們可以添加消息,也可以讀取消息,移除消息。當然MessageQueue的內部是通過一個單鏈表的數據結構來實現的,理解起來可能有點費勁,我們只需知道有兩個重要的方法enqueueMessage
插入消息,next
取出消息。
//======================插入消息=================================
boolean enqueueMessage(Message msg, long when) {
//...
//省略了部分代碼
msg.markInUse();
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;
}
//======================取出消息=================================
Message next() {
//...
//省略了部分代碼
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
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 (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//...
//省略了部分代碼
}
}
Handler平時用的都比較多,一般都會直接使用 mHandler.sendMessage
進行發送消息,然後重寫Handler的handleMessage
進行接收和處理消息。那麼 mHandler.sendMessage
到底做了什麼事呢?
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);
}
我們可以發現,在Handler內部,其實調用的是sendMessageDelayed
,然後sendMessageDelayed
中又調用了sendMessageAtTime
。
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);
}
在sendMessageAtTime
中我們看到了什麼?沒錯MessageQueue
,而且MessageQueue
不可為空,否則會拋出異常,你可能為疑問,這個MessageQueue
是從哪裡來的,不要急,下面馬上就會介紹,在Handler的構造方法那裡就能看到。我們暫且不管MessageQueue
是怎麼工作的,我們只需知道,我們當前的任務是將Message
塞進MessageQueue
中,我們接下來看看enqueueMessage
中做了什麼。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
我們可以發現,這裡調用了MessageQueue中的queue.enqueueMessage
方法將Message插入到隊列中去,到此為止,我們完成了第一步。
我們還能發現這一行msg.target = this;
,沒錯,將當前的Handler對象綁定給了Message,這也就能解釋,為什麼Message一番周轉之後,仍然知道該傳遞給哪個Handler對象。
將Message
塞給了MessageQueue
後,現在就該輪到Looper
登場了。Looper是怎麼從MessageQueue
取出中取出Message
的呢。
先來簡單看一下Looper的三個屬性。
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static Looper sMainLooper;
final MessageQueue mQueue;
可以看出Looper
中會綁定一個對應的MessageQueue
,還有一個線程變量sThreadLocal
。
在介紹之前,我們先來解釋下,為什麼不能直接在子線程中new Handler。
大家都知道在線程中使用Handler如下,但是可能不知道為什麼要這麼寫。
Looper.prepare();
Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();
那麼,我們先來看一下Handler構造方法,Handler構造方法有兩種,一種是顯示指定Looper對象的,另一種是不顯示指定Looper的(會默認獲取當前線程的Looper),所有不顯示指定Looper的構造方法都會在內部轉為調用以下構造。
public Handler(Callback callback, boolean async) {
//...
//省略了部分代碼
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;
}
我們可以發現只要mLooper為空,就會拋出異常。不為空的話就連同MessageQueue
賦值給當前Handler,你可能又想問,Looper的MessageQueue
是怎麼來的,莫急,待會會介紹Looper的構造方法。我們先看看Looper.myLooper()
方法
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal
是什麼鬼?
static final ThreadLocal sThreadLocal = new ThreadLocal();
看到這裡終於明白了,原來用了ThreadLocal
啊,ThreadLocal
是Java用來存儲線程變量的api。也就是說,假如我們在主線程使用sThreadLocal
存儲了一個Looper,在子線程也使用了sThreadLocal
存儲了一個Looper,他們互不干擾。每個線程取出來的值都是不一樣的。這也就保證了,每一個線程都有一個單獨的Looper。
那麼如何在子線程使用Handler呢?相信大家都有思路了,只要保證sThreadLocal.get()
能取到值就行,我們可以在new 之前給當前線程一個Looper對象。api中已經提供了方法,Looper.prepare()
如下。
public static void prepare() {
prepare(true);
}
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));
}
而且也可以看出,每個線程中只能設置一個Looper對象,否則還是會拋出異常。為什麼要強制只能有一個Looper對象呢?當然是因為Android的單線程模型啊。如果允許多個Looper那麼和在子線程中直接處理沒有任何區別,會導致一系列的並發問題。
sThreadLocal.set(new Looper(quitAllowed))
給當前線程綁定一個Looper,我們來看一下Looper的構造。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
從源碼可以看出,與此同時,也給Looper綁定了一個MessageQueue對象。
那麼,Looper到底是怎麼從MessageQueue中取出Message的呢。我們來看下 Looper.loop()
public static void loop() {
final Looper me = myLooper();//獲取當前線程的Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //獲取Looper對應的消息隊列
//...
//省略了部分代碼
for (;;) {//死循環
Message msg = queue.next(); //從消息隊列中取出一下個Message
//...
//省略了部分代碼
msg.target.dispatchMessage(msg);//msg.target就是Handler,調用Handler的dispatchMessage方法處理消息
//...
//省略了部分代碼
msg.recycleUnchecked();//回收Message
}
}
流程非常清晰,通過一個死循環,不停調用MessageQueue
的next()
方法,取出Message,然後看到了沒msg.target
,前面我們發送消息時,綁定的Handler對象。經歷了一番周轉變換了線程,又交給了Handler對象的dispatchMessage
中進行處理消息。
dispatchMessage
處理消息)在介紹處理消息之前,我們先來認識一下Handler的其他使用方法。我們知道只要重寫Handler的handleMessage
方法,然後就可以接收消息了。但是,我如果不想重寫handleMessage
呢?有沒有其他方法?當然是有的,請往下看。
前面我也提到了,Handler的構造方法分為兩種,顯示指定Looper與否,其實這兩種都是一樣的,沒有本質上的區別。但是我們忽略了Handler構造方法中的其他參數,現在再來看一遍。
public Handler(Callback callback, boolean async) {
//...
//省略了部分代碼
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;
}
有沒有發現一個Callback參數,其實它是一個接口,也是用於處理消息的。
public interface Callback {
public boolean handleMessage(Message msg);
}
mHandler.sendMessage
這種方法大家都很常用,也很簡單。大家都知道Handler有個post方法,那麼它和sendMessage有什麼區別呢?我們來看一下源碼。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
從上面可以看出,使用post,仍然會包裝為Message,然後調用sendMessageDelayed
進行發送消息。先來看一下到底是怎麼包裝的,如下
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
包裝給了一個callback,就是前面提到的Runable。
在介紹完Handler的其他用法後,現在回到Handler的dispatchMessage
中。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
看到沒有,if (msg.callback != null)
,也就是說,如果是使用post發送的,會調用handleCallback
進行處理。
private static void handleCallback(Message message) {
message.callback.run();
}
直接走了Runable的run方法。不會走Handler的handleMessage
。
然後 if (mCallback != null)
判斷那個接口有沒有實現,如果實現了,直接走接口。最後才是調用Handler內部的handleMessage
方法。
我們來看一個類ActivityThread
,這個類是Android的入口,我們可以找到一個久違的方法public static void main(String[] args)
。
public static void main(String[] args) {
//...
//省略了部分代碼
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
//...
//省略了部分代碼
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
從上面可以看出,Android在啟動一個應用時,已經默認給主線程開啟了Looper。
主線程中的Looper無限循環,為什麼沒有造成ANR?什麼情況下會造成ANR呢?只有當主線程的Handler處理一個消息耗時過長才會ANR。
Looper無限循環,不是一個簡單的死循環,當消息隊列中沒有消息時,就會阻塞休眠,釋放cpu資源。
我們Activity之所以有生命周期,也依賴於Handler,是因為ApplicationThread
根據系統ActivityManageService
發來的事件,然後發送消息來調度生命周期的。以下是ActivityThread
中處理消息相關的部分代碼。
public void handleMessage(Message msg) {
//..
//省略了部分代碼
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
(msg.arg1&2) != 0);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
//..
//省略了部分代碼
}
}
END
做一個記錄~
Content Provider:一個組件,必須放在應用的主包或應用的子包之下;組件的配置需要在清單文件中進行配置;content provider需要在applicat
在設計應用的時候,我們應該熱愛極簡主義,簡單就是好的,對於很多用戶來說,復雜的東西並不受歡迎。 我要實現的是根據不同的情況去顯示不同的加載效果,隨用隨調,效果是借鑒於某一
引言在我們的應用程序中經常需要提供搜索服務,比如搜索聯系人, 搜索商品信息等等。我們可以自己在布局中自定義我們的搜索框,實現我們的搜索邏輯。但是還有一種更簡單的方法:使用