為了更好的理解 Looper 的工作原理岖研,我們需要對 ThreadLocal 進行了解,如果對 ThreadLocal 沒有了解的童鞋,可以參看 ThreadLocal 原理
概述
Handler 作為日常開發(fā)的必備,不可避免就要涉及這方面的知識拦英。從開發(fā)者角度來說,Handler 是 Android 消息機制的上層接口测秸,使得開發(fā)的時只需與 Handler 交互即可。Handler 使用也很簡單灾常,能夠輕松將一個任務(wù)切換到 Handler 所在的線程中執(zhí)行霎冯。
很多人認(rèn)為Handler的作用就是更新UI,的確沒錯钞瀑,但是更新UI僅僅是Handler的一個特殊的使用場景沈撞。具體來說,就是有時候需要在子線程做一些耗時操作雕什,比如說訪問網(wǎng)絡(luò)或者耗時的I/O操作缠俺,當(dāng)這些耗時操作完成時显晶,程序的UI進行相應(yīng)的改變。由于安卓開發(fā)規(guī)范的限制壹士,我們不能在子線程中訪問UI控件磷雇,因為UI的控件是線程非安全的,這個時候通過Handler就可以將更新UI的操作切換到主線程中執(zhí)行躏救。
Android 的消息機制主要是指 Handler 的運行機制唯笙。事實上,Handler盒使,Looper崩掘,MessageQueue 是一套運行體制而出現(xiàn)的,MessageQueue 是一個消息隊列少办,以隊列形式提供插入和刪除苞慢,主要用于消息的存儲,內(nèi)部實現(xiàn)是采用單鏈表的形式來組織 Message英妓。而 Looper 用于處理消息挽放,Looper 內(nèi)部會以無限循環(huán)去查是否有新的 Message ,有則處理鞋拟,沒有就等待骂维。需要特殊說明的是,Looper 內(nèi)部是使用 ThreadLocal 實現(xiàn)的贺纲,由于ThreadLocal 可以在每一個線程中互不干擾的存取數(shù)據(jù)航闺,所以通過ThreadLocal 就可以輕松獲取每個線程的 Looper蹭越。
Message :android.os.Message是定義一個Messge包含必要的描述和屬性數(shù)據(jù)也颤,并且此對象可以被發(fā)送給android.os.Handler處理堆生。屬性字段:arg1白华、arg2腥椒、what方妖、obj匣缘、replyTo等窘哈;其中arg1和arg2是用來存放整型數(shù)據(jù)的澄成;what是用來保存消息標(biāo)示的胧洒;obj是Object類型的任意對象;replyTo是消息管理器墨状,會關(guān)聯(lián)到一個handler卫漫,handler就是處理其中的消息。通常對Message對象不是直接new出來的肾砂,只要調(diào)用handler中的obtainMessage方法來直接獲得Message對象列赎。
需要特殊說明的是,線程是默認(rèn)沒有 Looper 的镐确,如果需要使用 Handler 就必須為線程創(chuàng)建Looper包吝,但是APP 的主線程中饼煞,即 ActivityThread ,在主線程被創(chuàng)建的時候就會初始化 Looper诗越。另外砖瞧,在沒有Looper 的線程創(chuàng)建 Handler 也會失敗。
使用案例
話不多說掺喻,上例子:
public class MainActivity extends AppCompatActivity {
private TextView textView;
private String TAG = "MainActivity";
private int i = 0;
Handler mHandler = new Handler(){
/**
* handleMessage接收消息后進行相應(yīng)的處理
* @param msg
*/
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what==1){
textView.setText(msg.arg1+"");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
}
public void onClick(View v){
++i;
//創(chuàng)建新的線程
new Thread(){
@Override
public void run() {
super.run();
doSendMsg();
}
}.start();
}
/**
* 在子線程中做耗時操作芭届,完成之后,通知Handler更新UI
*/
private void doSendMsg(){
try {
Thread.sleep(1000);//模擬耗時操作
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = Message.obtain();
message.arg1 = i;
message.what = 1;
mHandler.sendMessage(message);
}
}
原理分析
代碼版本 : Android API25
Message
生成一個 Message
前面講過 Message 通常不是 new 出來的感耙,而是通過調(diào)用 Handler 的obtainMessage() 得到一個新的 Message:
public final Message obtainMessage()
{
return Message.obtain(this);
}
而 Handler 就調(diào)用 Message 的 obtain(Handler h) 方法:
public final class Message implements Parcelable {
public int what; // 讓接收者知道這是什么
public int arg1; // 存儲 int 用于傳遞過去
public int arg2;
public Object obj; // 傳遞一個對象過去
public Messenger replyTo; // 可以發(fā)送對此消息的回復(fù)褂乍。具體如何使用取決于發(fā)送者和接收者
public int sendingUid = -1;
/*package*/ static final int FLAG_IN_USE = 1 << 0; // 標(biāo)記消息被使用
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1; // 標(biāo)記消息是異步的
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/ int flags;
/*package*/ long when; // Message 的執(zhí)行時間 重要!<磁稹逃片!
/*package*/ Bundle data;
/*package*/ Handler target; // target 負(fù)責(zé)處理該消息
/*package*/ Runnable callback; // Runnable 類型的 callback
/*package*/ Message next; // 下一條消息,因為消息隊列是鏈?zhǔn)酱鎯Φ?
private static final Object sPoolSync = new Object(); // 控制并發(fā)訪問
private static Message sPool; // 回收池的頭結(jié)點
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
private static boolean gCheckRecycle = true;
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool; // 將頭結(jié)點賦值給Message
sPool = m.next; // 將頭結(jié)點更新為下一個節(jié)點
m.next = null; // 斷掉以前頭節(jié)點與當(dāng)前節(jié)點聯(lián)系
m.flags = 0; // clear in-use flag
sPoolSize--; // 將回收池的數(shù)量減一
return m;
}
}
return new Message();
}
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h; // 注意只酥,這里將 Handler 保存在了 target 中褥实,后面會調(diào)用target來處理這個Message
return m;
}
// ...
}
到了最后就是Message 的 obtain() 方法,從 global pool 返回一個新的Message實例裂允。 允許我們在許多情況下避免分配新對象损离。而這個 global pool 呢,其實就是一個單鏈表绝编,從頭結(jié)點取一個 Message 僻澎,如果沒有就 new 一個 Message。而這個單鏈表呢十饥,就是講無用需要回收的 Message 組織起來的窟勃。the global pool 其實就是使用靜態(tài)常量組織了一些無用了的 Message,組織的數(shù)據(jù)結(jié)構(gòu)就是單鏈表逗堵。
看源碼就知道秉氧,重載了多個obtain 方法,其實就是把上述可選參數(shù)配置一下蜒秤,然后調(diào)用 obtain() 得到一個Message汁咏。
回收 Message
可能看了生成有點懵,那我們提前看一下回收作媚,就好一點了梆暖, Message 的 源碼:
public void recycle() {
if (isInUse()) { // isInUse() return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return; // 在用就不回收
}
recycleUnchecked(); // 直接回收
}
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE; // 設(shè)置為沒有使用
what = 0; // 抹去數(shù)據(jù)
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool; // 將當(dāng)前 Message 的下一個設(shè)置為以前的頭結(jié)點
sPool = this; // 更新頭結(jié)點為當(dāng)前節(jié)點
sPoolSize++;
} // 如果到達(dá)MAX_POOL_SIZE數(shù)量,這個Message就會因為沒有與引用鏈相連而被GC回收
}
}
回收還是很簡單的掂骏,就是先檢驗是否在使用,如果不是厚掷,抹去上面的數(shù)據(jù)弟灼,將 Message 加到我稱為 回收池
的鏈表里级解。需要注意的是,這里如果回收池數(shù)量到了上限田绑,這個Message就會因為沒有與引用鏈相連而被GC回收勤哗。
MessageQueue
MassageQueue 叫做消息隊列,通過一個單鏈表的數(shù)據(jù)結(jié)構(gòu)來維護消息列表掩驱,而且單鏈表在插入和刪除上比較有優(yōu)勢芒划。MessageQueue 主要包含兩個操作:插入 和 讀取。插入讀取對應(yīng)的方法分別是enqueueMessage(Message msg, long when) 和 next()欧穴。
工作流程
這里的工作流程先是講一下例子里面的工作流程民逼,其次補充一些必要的流程,如回收等涮帘。
UI線程 Looper 的創(chuàng)建
我們知道 UI 線程是在 ActivityThread 中創(chuàng)建的拼苍,這個函數(shù)就是整個 APP 的入口。接下來就是 ActivityThread 中的 main:
public static void main(String[] args) {
...
Looper.prepareMainLooper(); // 1. 創(chuàng)建UI線程的 Looper
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
// UI 線程的Handle
// getHandler() 得到的是 ActivityThread.H (extends Handler)
sMainThreadHandler = thread.getHandler();
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop(); //2. 執(zhí)行消息循環(huán)
throw new RuntimeException("Main thread loop unexpectedly exited");
}
看到?jīng)]有调缨,通過1,2步的配置疮鲫,這時候 UI 線程中 Looper 其實已經(jīng)跑起來了,在程序中就已經(jīng)可以使用 Handler 了弦叶。而子線程卻默認(rèn)沒有配置俊犯。
需要注意的 UI 線程使用的 prepareMainLooper()
來準(zhǔn)備 Looper,但是這個方法雖然是 public 的伤哺,但是這是專門為 UI 線程量身定做的燕侠,我們絕對不可以使用,我們準(zhǔn)備Looper可以使用 Looper 的 prepare()
就好默责。
prepareMainLooper()
接下來我們來一步一步分析 (Looper 源碼:)
public static void prepareMainLooper() {
prepare(false); // new 一個 Looper 放到該線程的ThreadLocalMap中
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// 將 UI 線程的 Looper 放到靜態(tài)常量 sMainLooper 中贬循,那么隨時隨地都可以new 出主線程的 Handler
sMainLooper = myLooper(); // myLooper() return sThreadLocal.get();
}
}
public static void prepare() {
prepare(true); // 子線程中默認(rèn)是可以銷毀消息隊列的
}
private static void prepare(boolean quitAllowed) { // True if the message queue can be quit.
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// sThreadLocal 是在類定義的時候就初始化了的 static final ThreadLocal<Looper>
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get(); // 得到當(dāng)前線程的 Looper
}
很簡單,結(jié)合注釋桃序,應(yīng)該都懂了杖虾,就是 new 了一個 Looper ,放到了主線程的 ThreadLocalMap 中媒熊。那 new 的時候干了什么呢奇适?
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
原來是這樣,在 new Looper 的時候就創(chuàng)建了 UI 線程的消息隊列芦鳍,并且指定不可以刪除嚷往。
// True if the message queue can be quit.
private final boolean mQuitAllowed;
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
隊列是否可以刪除,一直向下傳遞柠衅,最后原來是保存在這里皮仁。但是這里還有一個 Native 方法,是干嘛的呢?筆者認(rèn)為是得到了這個對象所在線程的引用贷祈。
到這里趋急,ActivityThread 中的第一步就算是完成了。由于 sMainThreadHandler 的后期使用涉及復(fù)雜势誊,就留到后面講解呜达,這里用 例子中的 Handler 代替講解,最后執(zhí)行的 Handler 是一樣的粟耻,但是 ActivityThread.H(sMainThreadHandler )封裝了其他東西查近。
創(chuàng)建 Handler
看著例子中的 Handler 是直接 new 出來的,那我們看一下 Handler 的無參構(gòu)造方法:
public class Handler {
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) { // ***重要<访ΑK!
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
}
我們看到 Handler 在構(gòu)造方法中饭玲,通過 Looper.myLooper() 得到當(dāng)前線程(UI 線程)的Looper侥祭,并且保存到本地 final 變量 mLooper 中。
要知道茄厘,消息隊列被封裝在 Looper 中矮冬,而每一個 Looper 又會關(guān)聯(lián)一個線程(Looper 通過 ThreadLocal 封裝),最終等于每一個消息隊列都會關(guān)聯(lián)一個線程次哈。同時胎署,由上代碼可知,每個 Handler 也都會關(guān)聯(lián)一個消息隊列窑滞。在這里需要注意琼牧,Looper 和 MessageQueue 并沒有與 Handler關(guān)聯(lián),而是Handler 與 Looper 和 MessageQueue 建立聯(lián)系哀卫。
Looper.loop()
創(chuàng)建了Looper之后巨坊,就要執(zhí)行消息循環(huán)了,我們知道此改,通過 Handler 來 Post 消息給消息隊列趾撵,那么怎么處理呢?那就是最開始第二步的 Looper.loop() 中的共啃。
public static void loop() {
final Looper me = myLooper(); // 獲取當(dāng)前線程的 Looper
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; // 1. 獲取消息隊列
// 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 (;;) { // 2. 消息循環(huán)
Message msg = queue.next(); // might block可能阻塞 3. 獲取消息
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg); // 4. 處理消息
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked(); // 回收
}
}
我們可以看到 loop 方法中實際上就是建立一個死循環(huán)占调,然后通過從消息隊列中逐個取出消息,最后進行處理移剪,至到取到 null 值才退出究珊。這里并沒有任何的阻塞,那我消息取完了就退出了么纵苛?不剿涮,原理請看 MassageQueue 的 next() :
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// 注意言津,以下的代碼都在循環(huán)體里面
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis); // 重要!aB病纺念!
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis(); // 從開機到現(xiàn)在的毫秒數(shù)(手機睡眠的時間不包括在內(nèi))
Message prevMsg = null; // 前驅(qū)結(jié)點
Message msg = mMessages; // 頭結(jié)點
// 如果沒有 Handler,就在列表中找下一個同步的 Message 來執(zhí)行想括。
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) { // 如果頭結(jié)點不為空
if (now < msg.when) { // 如果還沒有到執(zhí)行時間
// Next message is not ready. Set a timeout to wake up when it is ready.
// 設(shè)置喚醒時間,距離多久執(zhí)行 與 int 最大值 2^31 - 1 作比較
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 注意這時候已經(jīng)到執(zhí)行時間了
// Got a message.
mBlocked = false;
// 從消息隊列中取出這個 Message
if (prevMsg != null) { // 如果前驅(qū)結(jié)點不為 null烙博,
prevMsg.next = msg.next; // 請參看 next() 圖 1
} else {
mMessages = msg.next; // 將頭結(jié)點后移
}
msg.next = null; // 斷掉要處理的 Message 與 消息隊列的聯(lián)系
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse(); // 標(biāo)記為使用
return msg;
}
} else { // 頭結(jié)點為空
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} //同步代碼塊結(jié)束
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
// Disposes of the underlying message queue.
// Must only be called on the looper thread or the finalizer.
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
代碼雖然很長瑟蜈,但是我們還是得看啊,其實就是取出單鏈表(我們前面已說過渣窜,MessageQueue其實是一個單鏈表結(jié)構(gòu))中的頭結(jié)點铺根,然后修改對應(yīng)指針,再返回取到的頭結(jié)點而已乔宿。因為這里采用的是無限循環(huán)位迂,所以可能會有個疑問:該循環(huán)會不會特別消耗CPU資源?其實并不會详瑞,如果messageQueue有消息掂林,自然是繼續(xù)取消息;如果已經(jīng)沒有消息了坝橡,此時該線程便會阻塞在該next()方法的 nativePollOnce() 方法中泻帮,主線程便會釋放CPU資源進入休眠狀態(tài),直到下個消息到達(dá)或者有事務(wù)發(fā)生(設(shè)置的nextPollTimeoutMillis到了)時计寇,才通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作锣杂。這里涉及到的是Linux的pipe/epoll機制,epoll機制是一種IO多路復(fù)用機制番宁,可以同時監(jiān)控多個描述符元莫,當(dāng)某個描述符就緒(讀或?qū)懢途w),則立刻通知相應(yīng)程序進行讀或?qū)懖僮鞯海举|(zhì)同步I/O踱蠢,即讀寫是阻塞的。
總結(jié):
通過 Looper.loop() 來創(chuàng)建 Looper 對象( 消息隊列封裝在 Looper 對象中 )播聪,并且保存在 sThreadLocal 中朽基,然后通過 Looper.loop() 來執(zhí)行消息循環(huán)。
說了取离陶,當(dāng)然接著就是分發(fā)了稼虎。我們調(diào)用msg.target.dispatchMessage(msg) 來執(zhí)行 Message 。在創(chuàng)建 Message 的過程中招刨,傳過來的 Handler 的引用就被保存在了Message中(最上面有代碼和講解)霎俩。接下來看一下 Handler 的處理:
final Callback mCallback;
public interface Callback {
public boolean handleMessage(Message msg);
}
public void handleMessage(Message msg) {
}
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(); // 此時是在 Handler 所在線程中執(zhí)行
}
這里面也特別簡單,大家都知道當(dāng)我們在 Handler post()消息的時候是使用 Runnable,而 sendMessage() 是自己構(gòu)建的 Message打却。這里首先判斷 Message 類型杉适,如果是 Runnable ,就調(diào)用run運行柳击,如果不是猿推,先判斷創(chuàng)建 Handler 的時候是否設(shè)置回調(diào),設(shè)置了就調(diào)用回調(diào)中的處理方法 handleMessage(msg)捌肴,如果沒有就使用默認(rèn)的 handleMessage(msg)蹬叭,這時候的這個方法大多數(shù)時候都會被重寫,就像例子一樣状知。
這里我們可以看到秽五,在分發(fā)消息時三個方法的優(yōu)先級分別如下:
- Message的回調(diào)方法優(yōu)先級最高,即message.callback.run()饥悴;
- Handler的回調(diào)方法優(yōu)先級次之坦喘,即Handler.mCallback.handleMessage(msg);
-
Handler的默認(rèn)方法優(yōu)先級最低西设,即Handler.handleMessage(msg)瓣铣。
Handler 工作原理.png
使用 Handler 來 sendMessage(Message msg)
既然提到 post() 和 sendMessage(),那么下面就講解一下它是如何將一個 Message 加到隊列中的济榨。
現(xiàn)講解 sendMessage() 吧坯沪,例子就是最上面的例子,那我們接著看 Handler 的源碼吧:
/**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received 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.
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0); // 默認(rèn)延時為 0
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) { // 校驗延時擒滑,因為延時只能 >= 0
delayMillis = 0;
}
// SystemClock.uptimeMillis() 從開機到現(xiàn)在的毫秒數(shù)(手機睡眠的時間不包括在內(nèi))
// SystemClock.uptimeMillis() + delayMillis算出來就是更新時間
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 這個值是通過從Looper 賦值過來的腐晾,就意味著是持有和Looper 中相同的引用
// Looper 中的修改的話mQueue,這個值也會被修改
// 當(dāng)調(diào)用 Looper.quit() 的時候丐一,這個值就被置空了的藻糖。
MessageQueue queue = mQueue;
if (queue == null) { // 因為需要發(fā)送到 MassageQueue中,所以不能為空
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; // 保存 Message 的 target
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
一直往下調(diào)用了三個方法才到底库车,還好都簡單巨柒,來看一下。將設(shè)置的時延轉(zhuǎn)化成應(yīng)該被執(zhí)行的時間柠衍,拿到關(guān)聯(lián)的消息隊列洋满,隨后保存 Message 的 target,然后調(diào)用消息隊列的 enqueueMessage(msg, uptimeMillis)
珍坊,將這個Message 加入隊列牺勾。那怎么加的么?接下來就是 MessageQueue 的 enqueueMessage(Message msg, long when)
:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) { // 一個新的 Message 是不會 InUse 的阵漏,在回收的時候設(shè)置為沒在使用的
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) { // 獲得自身的同步鎖
if (mQuitting) { // MessageQueue 正在退出
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle(); // 將Message實例回收
return false;
}
msg.markInUse(); // flags |= 1 標(biāo)記正在使用
msg.when = when; // 設(shè)置要插入 Message 的執(zhí)行時間
Message p = mMessages; // mMessages為頭結(jié)點
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(); //記得看上面的英文注釋 默認(rèn)為 false
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); // 喚醒線程的 Native 方法
}
}
return true;
}
看 enqueueMessage 的實現(xiàn)驻民,它的主要操作就是單鏈表的插入操作翻具,并且這鏈表是以執(zhí)行時間 when 作為順序的。需要重提的是回还,這時候的 when
已經(jīng)轉(zhuǎn)換成了距離開機到執(zhí)行的毫秒數(shù)裆泳。
- 校驗。是檢測target是否存在柠硕,因為Message. targe 是用來處理這個 Message 的工禾,所以一定要有target,其次判斷當(dāng)前 Message 是否正在被使用蝗柔,然后驗證當(dāng)前 MessageQueue 是不是已經(jīng)被 quit 了(mQuitting)帜篇,驗證通過過后就是正式的插入操作。
- 配置诫咱。設(shè)置 Message 該有的屬性,msg.markInUse(); msg.when = when;
- 這個方法比較巧妙洪灯,將幾種具體情況用一份代碼解決了坎缭,但是都可以歸結(jié)為:在頭結(jié)點之前插入結(jié)點∏┕常看到這里應(yīng)該注意到掏呼,如果插入的是需要非延時 Message,并且線程阻塞了铅檩,就會調(diào)用
nativeWake(mPtr)
喚醒線程憎夷。
- 當(dāng)還沒有鏈表的時候(p == 0)
- 消息的執(zhí)行時間 比 里面的消息還要早(when == 0 || when < p.when)
-
在鏈表中間或最后插入。循環(huán)遍歷鏈表昧旨,拿到鏈表合適的 Message拾给,然后再將新 Message 插入。由于還沒有到消息的處理時間兔沃,就不會喚醒線程蒋得。此插入過程如下:
使用 Handler 來 post()
先上一個使用的例子:
private Handler mHandler;//全局變量
@Override
protected void onCreate(Bundle savedInstanceState) {
mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);//在子線程有一段耗時操作,比如請求網(wǎng)絡(luò)
mHandler.post(new Runnable() {
@Override
public void run() {
mTestTV.setText("This is post");//更新UI
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
例子很簡單,就不講解了乒疏,接下來就來看一下 Handler 怎么 post 的吧:
/**
* 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);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain(); // 得到一個新的 Message
m.callback = r; // 是使用 post 的 Runnable 才會保存有這個參數(shù)
return m;
}
眼尖的童鞋估計看到就要easy了额衙。在 post 一開始就調(diào)用了 sendMessageDelayed(Message msg, long delayMillis)
,是不是很眼熟怕吴,對的窍侧,前面sendMessage(Message msg)
一開始也是調(diào)用這個方法。這就是非延時消息的插入转绷。后面具體如何插入請參看上一條伟件。
同樣的,如果是使用 postDelayed(Runnable r, long delayMillis)
呢暇咆?使用例子請參看 面試題:Handler機制锋爪,Handler除了線程通信還有什么作用 中的作用二的第二種實現(xiàn)方式丙曙。轉(zhuǎn)回來看 Handler 的 postDelayed :
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
原來啊,大家都用的一套其骄,還是調(diào)用的 sendMessageDelayed
方法亏镰,只不過延遲時延不再為默認(rèn)的 0. 這時候就是延遲消息的插入。
退出消息循環(huán)
退出消息循環(huán)有兩種方式拯爽,分別是Looper 的 quit()
和 quitSafely()
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
這還是很簡單索抓,直接調(diào)用了 MassageQueue 的 qiut(boolean safe)
:
void quit(boolean safe) {
if (!mQuitAllowed) { // 主線程設(shè)置不允許退出,其他子線程默認(rèn)設(shè)置的true
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) { // 默認(rèn)為 false毯炮,表示是否正在退出消息隊列
return;
}
mQuitting = true; // 全局唯一一次賦值逼肯,表示正在退出消息隊列
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
首先判斷是否是主線程,因為主線程的消息隊列不允許退出桃煎,然后判斷當(dāng)前線程是否正在退出篮幢。值得注意的是,mQuitting 是 MassageQueue 的成員變量为迈,是擁有默認(rèn)值的三椿,默認(rèn)值是 false。如果不是正在退出消息隊列葫辐,則將其標(biāo)志為 正在退出搜锰,注意,全局就只有這里修改了 mQuitting 的值耿战。然后判斷根據(jù)要求是否安全移除 MessageQueue蛋叼。
private void removeAllMessagesLocked() {
Message p = mMessages; // 頭結(jié)點
while (p != null) {
Message n = p.next; // 下一個結(jié)點
p.recycleUnchecked(); // 前面講過,就是將數(shù)據(jù)抹去剂陡,放入回收的那個鏈表
p = n;
}
mMessages = null; // 將MessageQueue 中保存的頭結(jié)點設(shè)置為 null
}
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis(); // 從開機到現(xiàn)在的毫秒數(shù)(手機睡眠的時間不包括在內(nèi))狈涮;
Message p = mMessages; // 頭結(jié)點
if (p != null) {
// 如果頭結(jié)點設(shè)置的延遲時間 > 大于當(dāng)前時間
// 注意 這里比較的時候都是用的從開機到現(xiàn)在的毫秒數(shù)
// 這里意味著還沒有到設(shè)置的執(zhí)行時間
if (p.when > now) {
removeAllMessagesLocked(); // 直接移除全部還未到執(zhí)行時間的 Message
} else {
Message n;
for (;;) {
n = p.next; // 拿到下一個節(jié)點
if (n == null) { // 結(jié)束標(biāo)志:沒有下一個
return;
}
if (n.when > now) { // 還沒有到設(shè)置的執(zhí)行時間,退出循環(huán)
break;
}
p = n;
}
// 這時候得到的節(jié)點 p 是沒有到設(shè)置的執(zhí)行時間的前一個節(jié)點
// n 是沒有到設(shè)置的執(zhí)行時間的節(jié)點
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
removeAllMessagesLocked()
直接將MessageQueue 中的 Message 全部回收掉鹏倘。無論是延遲消息(延遲消息是指通過sendMessageDelayed或通過postDelayed等方法發(fā)送的需要延遲執(zhí)行的消息)還是非延遲消息(delayMillis == 0)薯嗤。
removeAllFutureMessagesLocked()
方法呢,就是只會清空MessageQueue消息池中所有的延遲消息纤泵,并將消息池中所有的非延遲消息派發(fā)出去讓Handler去處理骆姐,在這個方法中,表現(xiàn)為直接return 捏题,然后因為隊列有Message玻褪,所以相應(yīng)的 dispatchMessage(msg) 會調(diào)用。
quitSafely相比于quit方法安全之處在于清空消息之前會派發(fā)所有的非延遲消息公荧。
面試題
handler發(fā)消息給子線程带射,looper怎么啟動?
發(fā)消息就是把消息塞進去消息隊列循狰,looper在應(yīng)用起來的時候已經(jīng)就啟動了窟社,一直在輪詢?nèi)∠㈥犃械南ⅰ?/p>
為什么在子線程中創(chuàng)建Handler會拋異常券勺?
首先看如下代碼:
new Thread(){
Handler handler = null;
@Override
public void run() {
handler = new Handler();
}
}.start();
前面說過,Looper 對象是 ThreadLocal 的灿里,即每個線程都有自己的 Looper关炼,這個 Looper 可以為空。但是匣吊,當(dāng)你在子線程中創(chuàng)建 Handler 對象時儒拂,如果 Looper 為空,那就會拋出異常色鸳。源碼解釋一下:
public class 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;
}
...
}
從上述程序中社痛,我們可以看到,當(dāng) mLooper 對象為空的時候命雀,拋出了異常蒜哀。這是因為該線程中的 Looper 對象還沒有創(chuàng)建,因此 sThreadLocal.get() 會返回 null吏砂。Handler 的原理就是要與 MassageQueue 建立關(guān)聯(lián)凡怎,并且將消息投遞給MassageQueue,如果連 MassageQueue 都沒有赊抖,那么 Handler 就沒有存在的必要,而 MassageQueue 又被封裝在 Looper 中寨典,因此氛雪,創(chuàng)建 Handler 時 Looper 一定不能為空。解決方法:
new Thread(){
Handler handler = null;
@Override
public void run() {
Looper.prepare(); // 1. 為當(dāng)前線程創(chuàng)建 Looper耸成,并會綁定到 ThreadLocal 中
handler = new Handler();
Looper.loop(); // 2. 啟動消息循環(huán)
}
}.start();
在UI線程為什么可以直接使用呢报亩,就是因為在 ActivityThread中默認(rèn)幫你執(zhí)行了 1,2步了的井氢。
Handler為什么loop是死循環(huán)弦追。
在android中如果主線程(UI線程)中進行耗時操作會引發(fā)ANR(Application Not Responding)異常,產(chǎn)生ANR的原因一般有兩種:
當(dāng)前的事件沒有機會得到處理(即主線程正在處理前一個事件花竞,沒有及時的完成或者looper被某種原因阻塞住了)
當(dāng)前的事件正在處理劲件,但沒有及時完成
比如onCreate()中進行了耗時操作,導(dǎo)致點擊约急、觸摸等不響應(yīng)零远,就會產(chǎn)生ANR。為了避免ANR異常厌蔽,android使用了Handler消息處理機制牵辣,讓耗時操作在子線程運行,需要UI線程進行處理的操作給UI線程發(fā)送消息奴饮。
我們知道Handler發(fā)消息給UI線程就可以處理消息纬向,UI線程維護著一個Looper和一個消息隊列择浊,Looper不停的拿消息隊列的消息去分發(fā)處理。到這里問題來了:如果這么做的話逾条,UI線程豈不是要一直死循環(huán)輪詢消息隊列拿消息琢岩?死循環(huán)不是造成ANR嗎?
是的膳帕,確實是死循環(huán)粘捎,但是 ANR 還得另說。
ActivityThread 的 main()
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
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
...
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked();
}
}
Looper.loop()是在死循環(huán)處理消息危彩,如果main方法中沒有l(wèi)ooper進行循環(huán)攒磨,那么主線程一運行完畢就會退出,那才不正常了呢!延旧?
所以熙含,ActivityThread的main方法主要就是做消息循環(huán),一旦退出消息循環(huán)拼坎,那么你的應(yīng)用也就退出了。
那么問題重新問一遍:那為什么這個死循環(huán)不會造成ANR異常呢完疫?
其實原因是顯然的泰鸡,我們知道Android是由事件驅(qū)動的,Looper.loop() 不斷地接收事件壳鹤、處理事件盛龄,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper.loop() 的控制之下,如果它停止了芳誓,應(yīng)用也就停止了余舶。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞消息锹淌。換言之匿值,消息隊列為空的時候會阻塞主線程,而處理消息的時候不可以阻塞赂摆,這時候的阻塞 5 s就會 ANR挟憔。
也就說我們的代碼其實就是在這個循環(huán)里面去執(zhí)行的,當(dāng)然不會阻塞了烟号。
而且主線程Looper從消息隊列讀取消息曲楚,當(dāng)讀完所有消息時,主線程阻塞褥符。子線程往消息隊列發(fā)送消息龙誊,并且往管道文件寫數(shù)據(jù),主線程即被喚醒喷楣,從管道文件讀取數(shù)據(jù)趟大,主線程被喚醒只是為了讀取消息鹤树,當(dāng)消息讀取完畢,再次睡眠逊朽。因此loop的循環(huán)并不會對CPU性能有過多的消耗罕伯。
總結(jié):Looer.loop()方法可能會引起主線程的阻塞,但只要它的消息循環(huán)沒有被阻塞叽讳,能一直處理事件就不會產(chǎn)生ANR異常追他。
Handler 機制 很多細(xì)節(jié)需要關(guān)注:如線程如何建立和退出消息循環(huán)等等)
這里就是讓你回答 Handler 的工作原理。
關(guān)于Handler岛蚤,在任何地方new Handler 都是什么線程下?
這個需要分類討論:
- 像最開始那樣呢邑狸,直接new 出來,不帶Looper 參數(shù)涤妒,那么就在創(chuàng)建 Looper 的線程下单雾。
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(boolean async) {
this(null, async);
}
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;
}
- 在任何地方 new 出指定線程的 Handler。例如主線程她紫,看案例:
new Thread(new Runnable() {
@Override
public void run() {
// 在子線程中實例化Handler同樣是可以的硅堆,只要在構(gòu)造函數(shù)的參數(shù)中傳入主線程的Looper即可
Handler handler = new Handler(Looper.getMainLooper());
}
}).start();
前面講過,UI線程調(diào)用的 prepare 函數(shù)不一樣贿讹,多保存了UI線程的 Looper 到 Looper.sMainLooper 中的渐逃。其目的是在任何地方都可以實例化 UI 線程的 Handler。
而這時候調(diào)用的構(gòu)造方法是
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
其實就是與傳入的 Looper綁定民褂,換言之朴乖,如果我傳入的是其他線程的Looper,我同樣也可以實例化其他線程的 Handler助赞。
請解釋下在單線程模型中Message、Handler袁勺、Message Queue雹食、Looper之間的關(guān)系
講到Handler,肯定離不開Looper期丰、MessageQueue群叶、Message這三者和Handler之間的關(guān)系,下面簡略地帶過街立,詳細(xì)自己可以參看上面源碼
Handler
將要執(zhí)行的Message或者Runnable到消息隊列埠通。Looper
每一個線程只有一個Looper,每個線程在初始化Looper之后端辱,然后Looper會維護好該線程的消息隊列虽画,用來存放Handler發(fā)送的Message荣病,并處理消息隊列出隊的Message码撰。它的特點是它跟它的線程是綁定的个盆,處理消息也是在Looper所在的線程去處理颊亮,所以當(dāng)我們在主線程創(chuàng)建Handler時编兄,它就會跟主線程唯一的Looper綁定狠鸳,從而我們使用Handler在子線程發(fā)消息時,最終也是在主線程處理卸察,達(dá)到了異步的效果坑质。
那么就會有人問涡扼,為什么我們使用Handler的時候從來都不需要創(chuàng)建Looper呢吃沪?這是因為在主線程中票彪,ActivityThread默認(rèn)會把Looper初始化好降铸,prepare以后摇零,當(dāng)前線程就會變成一個Looper線程。
MessageQueue
MessageQueue是一個消息隊列终佛,用來存放Handler發(fā)送的消息铃彰。每個線程最多只有一個MessageQueue牙捉。MessageQueue通常都是由Looper來管理邪铲,而主線程創(chuàng)建時带到,會創(chuàng)建一個默認(rèn)的Looper對象揽惹,而Looper對象的創(chuàng)建,將自動創(chuàng)建一個MessageQueue狭握。其他非主線程论颅,不會自動創(chuàng)建Looper恃疯。Message
消息對象今妄,就是MessageQueue里面存放的對象,一個MessageQueu可以包括多個Message杆兵。當(dāng)我們需要發(fā)送一個Message時琐脏,我們一般不建議使用new Message()的形式來創(chuàng)建日裙,更推薦使用Message.obtain()來獲取Message實例昂拂,因為在Message類里面定義了一個消息池格侯,當(dāng)消息池里存在未使用的消息時联四,便返回朝墩,如果沒有未使用的消息收苏,則通過new的方式創(chuàng)建返回倒戏,所以使用Message.obtain()的方式來獲取實例可以大大減少當(dāng)有大量Message對象而產(chǎn)生的垃圾回收問題杜跷。
Handler機制葛闷,Handler除了線程通信還有什么作用
Handler的主要用途 :
- 推送未來某個時間點將要執(zhí)行的Message或者Runnable到消息隊列阳仔。
- 在子線程把需要在另一個線程執(zhí)行的操作加入到消息隊列中去近范。
1. 推送未來某個時間點將要執(zhí)行的Message或者Runnable到消息隊列
實例:通過Handler配合Message或者Runnable實現(xiàn)倒計時
- 方法一评矩,通過Handler + Message的方式實現(xiàn)倒計時斥杜。代碼如下:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding mBinding;
private Handler mHandler ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//設(shè)置監(jiān)聽事件
mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//通過Handler + Message的方式實現(xiàn)倒計時
for (int i = 1; i <= 10; i++) {
Message message = Message.obtain(mHandler);
message.what = 10 - i;
mHandler.sendMessageDelayed(message, 1000 * i); //通過延遲發(fā)送消息忘渔,每隔一秒發(fā)送一條消息
}
}
});
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
mBinding.time.setText(msg.what + ""); //在handleMessage中處理消息隊列中的消息
}
};
}
}
這里用到了DataBiding畦粮,可能沒用過的同學(xué)看起來有點奇怪,但其實反而簡略了代碼拉背,有一定基礎(chǔ)的同學(xué)看起來都不會有太大壓力椅棺。通過這個小程序两疚,筆者希望大家可以了解到Handler的一個作用就是诱渤,在主線程中勺美,可以通過Handler來處理一些有順序的操作赡茸,讓它們在固定的時間點被執(zhí)行占卧。
- 方法二华蜒,通過Handler + Runnable的方式實現(xiàn)倒計時。代碼如下:
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding mBinding;
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
//設(shè)置監(jiān)聽事件
mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
for (int i = 1; i <= 10; i++) {
final int fadedSecond = i;
//每延遲一秒域滥,發(fā)送一個Runnable對象
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mBinding.time.setText((10 - fadedSecond) + "");
}
}, 1000 * i);
}
}
});
}
}
方法二也是通過代碼讓大家加深Handler處理有序事件的用途启绰,之所以分開Runnable和Message兩種方法來實現(xiàn),是因為很多人都搞不清楚為什么Handler可以推送Runnable和Message兩種對象着倾。
其實卡者,無論Handler將Runnable還是Message加入MessageQueue崇决,最終都只是將Message加入到MessageQueue恒傻。Handler的post Runnable對象這個方法只是對post Message進行了一層封裝,即將Runnable 放到的Message 中的 mCallback 存儲起來沸手,值得注意的是罐氨,如果直接將Message 加入MessageQueue的話栅隐,那么mCallback將為null租悄,所以最終我們都是通過Handler推送了一個Message罷了,至于為什么會分開兩種方法潭辈,只是為了更方便開發(fā)者根據(jù)不同需要進行調(diào)用把敢。下面再來看看Handler的第二個主要用途修赞。
2. 在子線程把需要在另一個線程執(zhí)行的操作加入到消息隊列中去
實例:通過Handler + Message來實現(xiàn)子線程加載圖片柏副,在UI線程顯示圖片
效果圖如下
代碼如下(布局代碼也不放出來了)
public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {
private ActivityThreadBinding mBinding = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
// 設(shè)置點擊事件
mBinding.clickBtn.setOnClickListener(this);
mBinding.resetBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
// 響應(yīng)load按鈕
case R.id.clickBtn:
// 開啟一個線程
new Thread(new Runnable() {
@Override
public void run() {
// 在Runnable中進行網(wǎng)絡(luò)讀取操作,返回bitmap
final Bitmap bitmap = loadPicFromInternet();
// 在子線程中實例化Handler同樣是可以的铅歼,只要在構(gòu)造函數(shù)的參數(shù)中傳入主線程的Looper即可
Handler handler = new Handler(Looper.getMainLooper());
// 通過Handler的post Runnable到UI線程的MessageQueue中去即可
handler.post(new Runnable() {
@Override
public void run() {
// 在MessageQueue出隊該Runnable時進行的操作
mBinding.photo.setImageBitmap(bitmap);
}
});
}
}).start();
break;
case R.id.resetBtn:
mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));
break;
}
}
/***
* HttpUrlConnection加載圖片,很簡單
* @return
*/
public Bitmap loadPicFromInternet() {
Bitmap bitmap = null;
int respondCode = 0;
InputStream is = null;
try {
URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10 * 1000);
connection.setReadTimeout(5 * 1000);
connection.connect();
respondCode = connection.getResponseCode();
if (respondCode == 200) {
is = connection.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
}
} catch (MalformedURLException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(), "訪問失敗", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
}
很簡單,其實最主要的就是當(dāng)我們需要在主線程執(zhí)行一些操作的時候瓤的,就可以直接使用這種方式圈膏,這種方式有著直接發(fā)送 Message 不可實現(xiàn)的先天優(yōu)勢稽坤。
handler機制組成尿褪,handler機制每一部分的源碼包括looper中的loop方法、threadlocal概念摆马、dispatchmessage方法源碼囤采,runnable封裝message等
上面講解的還記得住么斑唬?
請解釋下在單線程模型中Message、Handler褐着、Message Queue含蓉、Looper之間的關(guān)系
- Android的單線程模型
當(dāng)一個程序第一次啟動時馅扣,Android會同時啟動一個對應(yīng)的主線程(Main Thread),主線程主要負(fù)責(zé)處理與UI相關(guān)的事件蓄喇,如:用戶的按鍵事件妆偏,用戶接觸屏幕的事件以及屏幕繪圖事件钱骂,并把相關(guān)的事件分發(fā)到對應(yīng)的組件進行處理。所以主線程通常又被叫做UI線程张吉。
在開發(fā)Android 應(yīng)用時必須遵守單線程模型的原則:Android UI操作并不是線程安全的并且這些操作必須在UI線程中執(zhí)行肮蛹。
如果在非UI線程中直接操作UI線程省核,會拋出異常气忠,這與普通的java程序不同旧噪。因為 ViewRootImpl 對 UI 操作做了驗證,這個驗證工作是由 ViewRootImpl 的 checkThread 方法來完成的:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
由于UI線程負(fù)責(zé)事件的監(jiān)聽和繪圖米母,因此铁瞒,必須保證UI線程能夠隨時響應(yīng)用戶的需求,UI線程里的操作應(yīng)該向中斷事件那樣短小蜂绎,費時的操作(如網(wǎng)絡(luò)連接)需要另開線程师枣,否則,如果UI線程超過5s沒有響應(yīng)用戶請求陨倡,會彈出對話框提醒用戶終止應(yīng)用程序兴革。順便說一下 ANR 默認(rèn)情況下庶艾,在android中Activity的最長執(zhí)行時間是5秒咱揍,BroadcastReceiver的最長執(zhí)行時間則是10秒。
如果在新開的線程那為什么系統(tǒng)不對 UI 控件的訪問加上鎖機制呢硼砰?缺點有兩個:首先加上鎖機制會讓 UI 訪問的邏輯變得復(fù)雜缅疟;其次鎖機制會降低 UI 訪問的效率存淫,因為鎖機制會阻塞某些線程的執(zhí)行括授。鑒于這兩個缺點荚虚,最簡單且高效的方法就是采用單線程模型來處理UI操作,對于開發(fā)者來說也不是很麻煩渴析,只需要通過 Handler 切換一下 UI 訪問的執(zhí)行線程就好俭茧。
中需要對UI進行設(shè)定,就可能違反單線程模型场斑,因此android采用一種復(fù)雜的Message Queue機制保證線程間通信喧半。
- 后面就是分析的那一套了挺据。Message Queue 、 Handler 婉称、 Looper
Handler王暗、Thread和HandlerThread的差別
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
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() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is 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;
}
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @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.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @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;
}
public int getThreadId() {
return mTid;
}
}
由于 HandlerThread 實在是代碼少得變態(tài)庄敛,我就直接將全部源碼貼上來了俗壹。
直接看一下run() 方法可能就明白了,就是將 Looper 啟動起來藻烤,就等于主線程一樣绷雏,可以直接使用 Handler 了怖亭,沒必要 Looper.prepare() 再 Looper.loop() 了涎显。需要知道這根本不是 handler ,而是封裝了 Looper 的 Thread兴猩,方便了子線程與子線程通信棺禾。
還有一點
Handler: 它的消息處理方式是阻塞式的,必須一條一條的處理峭跳。耗時操作 不應(yīng)該用handler處理膘婶。
HandlerThread:繼承自Thread,它有個Looper蛀醉,在這里可以執(zhí)行耗時操作
什么是 IdleHandler悬襟?有什么用?怎么用
https://mp.weixin.qq.com/s/KpeBqIEYeOzt_frANoGuSg
這里還沒寫拯刁,努力更新中...
Handler消息機制脊岳,postDelayed會造成線程阻塞嗎?對內(nèi)存有什么影響?
- Handler消息機制
- postDelayed會造成線程阻塞嗎:
還有印象么割捅,上面講解的 postDelayed奶躯。postDelayed只是在 post 的時候加了延時,最后這個延時講被轉(zhuǎn)換成執(zhí)行時間存在每一個 Message 中亿驾。而在 loop() 中調(diào)用 next() 的死循環(huán)是阻塞式的嘹黔,只有在下個消息到達(dá)或者有事務(wù)發(fā)生(設(shè)置的nextPollTimeoutMillis到了)時,才通過往pipe管道寫端寫入數(shù)據(jù)來喚醒線程工作莫瞬。
也就是說如果當(dāng)前消息隊列中消息全部為 延時Message(全部沒到執(zhí)行時間)儡蔓,而這個 Message 的執(zhí)行時間又比MessageQueue 中所有消息執(zhí)行時間早,那么在loop循環(huán)取next 的時候就會因為最早這一個Message(剛剛postDelayed的Message)還沒到執(zhí)行時間而阻塞疼邀。
-
對內(nèi)存有什么影響
ThreadLocal內(nèi)存解釋
Looper 是通過 ThreadLocal 存儲在線程中的喂江,而MessageQueue 是封裝在 Looper 中的。
參考文章:
Android 官方文檔 :Handler
MessageQueue
Looper
《 Android 開發(fā)藝術(shù)探索 》
《 Android 開發(fā)進階 從小工到專家 》
深入理解Android中的Handler機制
一步一步分析Android的Handler機制
【從源碼看Android】03Android MessageQueue消息循環(huán)處理機制(epoll實現(xiàn))