我是代碼搬運工,不能僅僅只是搬運,還要整理一下。android
使用Handler以前的準備工做有三步:面試
調用Looper.prepare()(主線程不須要調這個,由於APP建立時,main方法裏面已經幫咱們建立了)設計模式
建立Handler對象,重寫handleMessage方法(你能夠不重寫),用於處理message回調的bash
調用Looper.loop()app
其中:異步
Looper.prepare()的做用主要有如下三點:async
建立Looper對象ide
建立MessageQueue對象,並讓Looper對象持有oop
讓Looper對象持有當前線程源碼分析
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
// 規定了一個線程只有一個Looper,也就是一個線程只能調用一次Looper.prepare()
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 若是當前線程沒有Looper,那麼就建立一個,存到sThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼
從上面的代碼能夠看出,一個線程最多隻有一個Looper對象。當沒有Looper對象時,去建立一個Looper,並存放到sThreadLocal中
private Looper(boolean quitAllowed) {
// 建立了MessageQueue,並供Looper持有
mQueue = new MessageQueue(quitAllowed);
// 讓Looper持有當前線程對象
mThread = Thread.currentThread();
}
複製代碼
這裏主要就是建立了消息隊列MessageQueue,並讓它供Looper持有,由於一個線程最多隻有一個Looper對象,因此一個線程最多也只有一個消息隊列。而後再把當前線程賦值給mThread。
Handler使用流程:
Handler.post(或sendMessage): handler發送消息msg
MessageQueue.enqueueMessage(msg, uptimeMillis):msg加入message隊列
loop() 方法中從MessageQue中取出msg,而後回調handler的dispatchMessage,而後執行callback(若是有的話) 或 handleMessage。(注意,loop方法是一直在循環的,從前面的handler準備工做開始就已經一直在運行了)
如圖所示:
Message的獲取方式有兩種:
1. Message msg = new Message();
2. Message msg = Message.obtain();
從全局池返回一個新的消息實例。容許咱們在許多狀況下避免分配新對象。
/**
* 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;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
複用以前用過的 Message 對象,這裏其實是用到了一種享元設計模式,這種設計模式最大的特色就是複用對象,避免重複建立致使的內存浪費
/**
* 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;
what = 0;
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;
sPool = this;
sPoolSize++;
}
}
}
每次message使用完了以後會調用recycleUnchecked回收message,方便下次使用
複製代碼
Message核心的信息有這些:
public int what;
public int arg1;
public int arg2;
public Object obj;
/*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;
複製代碼
handler提供的發送消息的方法有不少,大體分爲兩類:
雖然方法不少,但最終都會回調到這個方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
這個方法就是將消息添加到隊列裏。
enqueueMessage(添加消息)
源碼分析以下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
// 標記這個 Message 已經被使用
msg.markInUse();
msg.when = when;
// 這是這個消息隊列裏的目前第一條待處理的消息(當前消息隊列的頭部,有可能爲空)
Message p = mMessages;
boolean needWake;
// 若是目前隊列裏沒有消息 或 這條消息msg須要當即執行 或 這條消息msg的延遲時間比隊列裏的第一條待處理的消息還要早的話,走這個邏輯
if (p == null || when == 0 || when < p.when) {
// 把消息插入到消息隊列的頭部
// 最新的消息,若是已經blocked了,須要喚醒
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 下面這個for循環的做用就是找出msg應該放置的正確位置
// 通過下面這個for循環,最終會找出msg的前一個消息是prev,後一個消息是p
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 上面的for循環得出的結果就是:msg應該在prev後面,在p前面
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;
}
複製代碼
上面就是handler發送消息過來,而後添加到消息隊列裏。
下面就開始講添加消息到隊列以後的事情:取消息,而後執行。
前面已經說到,在使用Handler以前有三步準備工做:
調用Looper.prepare()(主線程不須要調這個,由於APP建立時,main方法裏面已經幫咱們建立了)
建立Handler對象,重寫handleMessage方法(你能夠不重寫),用於處理message回調的
調用Looper.loop()
其中第三步的Looper.loop()的做用就是不斷的從MessageQueue隊列裏取消息,也就是說,在使用handler發消息以前,就已經開始了loop的循環了。
loop()源碼比較長,這裏摘取核心部分:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*
* 大概的翻譯就是:在這個線程中運行消息隊列。確保調用{@link #quit()}來結束循環。
*
*/
public static void loop() {
····
····
for (;;) {
// 不斷的從MessageQueue的next方法裏取出隊列的頭部消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
·····
·····
·····
try {
// msg.target是Message在建立時傳入的Handler,也就是發送這條消息的發送者handler
// 因此最終會回調到handler的dispatchMessage方法
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
····
····
// 回收msg,重複利用
msg.recycleUnchecked();
}
}
複製代碼
loop()的做用就是不斷的從MessageQueue裏取消息,而後回調到dispatchMessage裏,再看看dispatchMessage裏幹啥了
/**
* 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);
}
}
複製代碼
因此最終會執行callback或handleMessage。
答:App的運行、更新UI、AsyncTask、Glide、RxJava等
答:建立Handler所使用的Looper所在的線程
答: 是根據when在MessageQueue中升序排序的,when=開機到如今的毫秒數+延時毫秒數
答:不會致使App的ANR,是Linux的pipe機制保證的,阻塞時,線程掛起;須要時,喚醒線程
答:可使用,可是Toast的顯示是基於Handler實現的,因此須要先建立Looper,而後調用Looper.loop。
答:能夠中止,Looper提供了quit和quitSafely方法
答: 靜態內部類+弱引用 、Handler的removeCallbacksAndMessages等方法移除MessageQueue中的消息
下面逐個來看看這些問題:
App的入口實際上是ActivityThread的main方法:
public static void main(String[] args) {
// 建立主線程中的Looper
Looper.prepareMainLooper();
// 建立ActivityThread對象
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
// 建立用於通訊的Handler
sMainThreadHandler = thread.getHandler();
}
// 執行loop方法
Looper.loop();
}
複製代碼
能夠看到這個main方法中,主要有以下幾步:
回顧下,Handler機制的原理圖:
能夠知道,App啓動後,由於Looper.loop是一個死循環,致使main方法一直沒有執行完,也就是說,咱們後續App中的全部操做,都是發生在Looper.loop中的
那Handler機制,是怎麼保證App的運行的呢?咱們來看看ActivityThread中用於通訊的Handler的定義:
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
// 省略部分代碼
String codeToString(int code) {
if (DEBUG_MESSAGES) {
switch (code) {
case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE";
// 省略部分代碼
}
}
return Integer.toString(code);
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
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 RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
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;
case PAUSE_ACTIVITY_FINISHING:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder)msg.obj, true, (msg.arg1&1) != 0, msg.arg2,
(msg.arg1&1) != 0);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_ACTIVITY_SHOW:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
handleStopActivity((IBinder)msg.obj, true, msg.arg2);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_ACTIVITY_HIDE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
handleStopActivity((IBinder)msg.obj, false, msg.arg2);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
// 省略部分代碼
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
複製代碼
經過代碼能夠看到,H繼承於Handler,定義了不少消息的類型,好比啓動Activity、中止Activity、顯示Window、電量太低等,它重寫了handleMessage方法,用於處理這些消息。
那發消息是在哪裏發的呢?咱們以鎖屏調用Activity的onStop生命週期爲例,其實就是在ApplicationThread中,它是一個ActivityThread的內部類咱們拿啓動Activity這個消息舉例,ActivityManagerService會先經過binder調用ApplicationThread中的scheduleStopActivity方法(牽涉到進程間通訊,不懂能夠略過),這個方法是在咱們App的Bindler線程池中執行的,那看看它是怎麼切換到主線程去啓動Activity的。
public final void scheduleStopActivity(IBinder token, boolean showWindow,
int configChanges) {
sendMessage(
showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE,
token, 0, configChanges);
}
複製代碼
咱們看到,這裏就是調用sendMessage方法,第一個參數,不就是上面H中定義的消息類型麼?接着看 sendMessage方法,它最後會調用到以下這個多參的構造方法:
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
if (DEBUG_MESSAGES) Slog.v(
TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+ ": " + arg1 + " / " + obj);
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
複製代碼
能夠看到,就是使用mH發送一個消息,而mH就是ActivityThread中定義的類型爲H的成員變量,定義以下:
final H mH = new H();
複製代碼
因此,調用鎖屏時,調用Activity的onStop方法,流程以下:
流程圖以下:
那麼,咱們App運行原理就出來了,App的運行依賴於Handler機制,當主線程當MessageQueue有消息時,主線程的Looper.loop會不斷從主線程中的MessageQueue中取出消息來處理(好比Activity的onCreate其實就是屬於對MessageQueue中取出的一個消息的處理),這樣就保證了App運行
當MessageQueue沒有消息時,MessageQueue的next方法會阻賽,致使當前線程掛起,等有消息(通常爲系統進程經過binder調用App的ApplicationThread中的方法,注意,方法在binder線程池中執行,而後ApplicationThread使用ActivityThread中的H對象發送消息,加入消息到主線程的MessageQueue中,當發現主線程被掛起了,則會喚醒主線程)
因此,當沒有任何消息時,咱們的App的主線程,是屬於掛起的狀態。有消息來時(鎖屏、點擊等),主線程會被喚醒,因此說,Handler機制保證了App的運行。
咱們知道,若是在子線程直接更新UI會拋出異常,異常以下:
咱們可使用Handler在子線程中更新UI,經常使用的方式有以下幾種:
2.1 Handler的sendMessage方式
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
btn.setText("handler.sendMessage方式");
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 使用sendMessage方式
Message msg = Message.obtain();
msg.what = 100;
handler.sendMessage(msg);
}
}).start();
複製代碼
這種方式,就是在子線程中,用主線程中建立的hander調用sendMessage發送消息,把Message加入到主線程的MessageQueue中,等主線程的Looper從MessageQueue中取到這個消息時,會調用這個Message的target的handleMessage方法,這個target其實就是咱們發消息用到的handler,也就是調用了咱們重寫的handleMessage方法。
發消息:Handler.sendMessage(Message) 處理消息:Message.target.handleMessage(其中target就是發消息的handler)
2.2 Handler的post方法
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
btn.setText("handler.sendMessage方式");
}
};
new Thread(new Runnable() {
@Override
public void run() {
// 使用post
handler.post(new Runnable() {
@Override
public void run() {
btn.setText("handler.post方式");
}
});
}
}).start();
複製代碼
在子線程中使用handler的post方法,也是能夠更新UI的,post方法須要傳入一個Runnalbe對象。咱們來看看post方法源碼
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
複製代碼
能夠看到,先調用了getPostMessage構建了一個Message對象,而後調用了sendMessageDelayed方法,前面知道,sendMessage也是調用的這個方法,因此咱們只要關注怎麼構建的Message對象,看getPostMessage方法。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
// post方法傳入的Runnable對象做爲Message的callback
m.callback = r;
return m;
}
複製代碼
其實很簡單,就是把post方法傳入的參數做爲Message的callback來建立一個Message。
咱們再來回顧一下從MessageQueue中取出消息來對消息對處理,方法是Handler對dispatchMessage方法
public void dispatchMessage(Message msg) {
// callback其實就是post方法傳入對Runnable對象
if (msg.callback != null) {
handleCallback(msg);
} else {
// 不會執行到這裏
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
// 至關於調用了post方法傳入對Runnable對象對run方法
message.callback.run();
}
複製代碼
能夠知道,咱們使用post發送消息,就是使用傳入對Runnable對象封裝成一個Message,而後加入到主線程中的MessageQueue,等主線程的Looper取出該消息處理時,由於Message.callback不爲空,而調用其run方法,也就是咱們調用post方法傳入的Runnable對象的run方法,且不會調用Hander的handleMessage方法。
發送消息:Handler.post(Runnable) 處理消息:Message.callback.run(callback爲調用post方法傳入的Runnable)
2.3 Activity的runOnUiThread方法
new Thread(new Runnable() {
@Override
public void run() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
btn.setText("Activity的runOnUiThread方法");
}
});
}
}).start();
複製代碼
咱們若是能在子線程中獲取到Activity對象,是能夠調用其runOnUiThread方法,來更新UI。咱們來看看Activity的runOnUiThread源碼。
public final void runOnUiThread(Runnable action) {
// 若是不是UI線程,則調用Handler的post
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
// 若是是ui線程,則直接回調Runnable的run方法
action.run();
}
}
複製代碼
若是是UI線程,就直接調用了傳入的Runnable對象的run方法,咱們主要是看非UI線程的邏輯。
若是不是UI線程,則直接使用mHandler的post方法,看來Activity的runOnUiThread方法也是基於Handler的post方法來實現的,後面的邏輯就是把傳入的Runnable封裝成Message發送出去,上面講過了,就再也不復述了。
咱們再看看這個mHandler的定義,其實就是Activity的成員屬性
final Handler mHandler = new Handler();
複製代碼
Activity是在主線程建立的,因此這個Handler也是在主線程中建立的,且持有的Looper爲主線程的Looper。那麼使用這個Handler調用post方法發出的消息,是加入到主線程的MessageQueue中,這樣就完成了子線程跟主線程的通訊。
發送消息:Activity. runOnUiThread(Runnable) 處理消息:Message.callback.run(callback爲runOnUiThread方法傳入的Runnable)
2.4 View的post方法
new Thread(new Runnable() {
@Override
public void run() {
// 調用View的post方法
btn.post(new Runnable() {
@Override
public void run() {
btn.setText("View.post方式");
}
});
}
}).start();
複製代碼
咱們直接看View的post方法
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
複製代碼
能夠看到這裏也是調用的Handler的post方法,跟Activity. runOnUiThread相似。
發送消息:View.post(Runnable) 處理消息:Message.callback.run(callback爲post方法傳入的Runnable對象)
總結一下:
因此,以上子線程更新主線程UI的全部方式,都是依賴於Handler機制。
當咱們想在子線程中作耗時任務時,會考慮使用AsyncTask,咱們來舉個栗子,在子線程中去建立自定義的MyAsyncTask並執行它,在doInBackground中去模擬耗時操做:
public class AsyncTaskActivity extends AppCompatActivity {
private static final String TAG = "AsyncTaskActivity";
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_asynctask);
btn = (Button) findViewById(R.id.btn);
// 開啓一個子線程,去執行異步任務
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run, thread name:" + Thread.currentThread().getName());
MyAsyncTask asyncTask = new MyAsyncTask();
asyncTask.execute();
}
}).start();
}
// 自定義AsyncTask,並重寫相關方法,並打印執行所在線程
class MyAsyncTask extends AsyncTask<Void, Integer, Void>{
@Override
protected Void doInBackground(Void... voids) {
Log.e(TAG, "doInBackground, thread name:" + Thread.currentThread().getName());
// 模擬耗時任務
for (int i = 0; i < 5; i ++) {
SystemClock.sleep(1000);
publishProgress(i);
}
return null;
}
@Override
protected void onPreExecute() {
Log.e(TAG, "onPreExecute, thread name:" + Thread.currentThread().getName());
}
@Override
protected void onPostExecute(Void aVoid) {
Log.e(TAG, "onPostExecute, thread name:" + Thread.currentThread().getName());
btn.setText("執行完了!");
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
Log.e(TAG, "onProgressUpdate, thread name:" + Thread.currentThread().getName());
}
}
}
複製代碼
關於onPreExecute,這很好實現,不須要切換線程,直接回調就能夠;而doInBackground方法的執行,能夠直接取AsyncTask維持的線程池來執行就能夠。咱們重點關注onProgressUpdate和onPostExecute方法,是怎麼從子線程切換到主線程的。
咱們從AsyncTask的源碼中能夠看到這樣一個內部類
private static class InternalHandler extends Handler {
public InternalHandler() {
// 使用主線程的Looper去建立Handler
super(Looper.getMainLooper());
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// finish中主要調用onPostExecute
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
// 調用onProgressUpdate
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
複製代碼
從handleMessage方法看到,這裏切換到主線程,也是使用的Handler機制來實現的,可是爲何咱們無論在任何線程建立的AsyncTask去執行,最後均可以在主線程去回調這兩個方法呢?主要是建立InternalHandler時,使用的是主線程的Looper,這樣使用這個InternalHandler發送消息時,消息就會加入到主線程Looper對應的MessageQueue中,因此主線程Looper取出消息處理時,InternalHandler的handleMessage方法就是在主線程中回調的了。
因此AsyncTask其實就是基於線程池+Handler機制來實現的。
其餘使用Handler的地方
RxJava: 子線程切換到主線程執行觀察者的回調方法(RxJava我不熟悉)
Glide:圖片準備好之後的回顯
LocalBroadcastManager: 傳統的廣播是基於binder實現的,本地廣播LocalBroadcastManager是基於Handler實現的
其實還有不少使用到handler機制的地方,就不一一舉例了,反正記住,Handler機制很重要。
之前總以爲,處理消息的線程,就是建立Handler的線程,可是上一篇文章的分析,咱們知道這樣說實際上是不許確的(由於咱們建立Handler一般使用的默認Looper)。
處理消息的線程,實際上是發送handler所持有的Looper所在的線程。
其實原理很好理解,咱們知道Handler的原理如圖
就像上面講到AsyncTask的例子,就算咱們在子線程建立了AsyncTask(即在子線程建立了用於通訊的Handler),但只要咱們建立Handler的時候,經過Looper.getMainLooper()傳入主線程的Looper ,那麼消息就加入到了主線程所對應的MessageQueue中,消息就是在主線程中處理的。
以下,咱們在子線程中建立一個handler,而後在主線程發送消息,由於建立handler使用的是子線程中的Looper,因此消息是在主線程中處理的。代碼以下:
public class ThreadActivity extends AppCompatActivity {
private static final String TAG = "ThreadActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh);
btn = (Button) findViewById(R.id.btn);
// 開啓一個子線程,去建立Handler
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
}
};
Looper.loop();
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 發送一個消息
mHandler.sendEmptyMessage(100);
}
});
}
}
複製代碼
log輸出,能夠看出,處理消息是在子線程中:
public class ThreadActivity extends AppCompatActivity {
private static final String TAG = "ThreadActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh);
btn = (Button) findViewById(R.id.btn);
// 開啓一個子線程,去建立Handler
new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: , thread name: " + Thread.currentThread().getName());
Looper.prepare();
// 建立Handler傳入主線程的Looper
mHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG, "handleMessage, thread name: " + Thread.currentThread().getName());
}
};
Looper.loop();
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 發送一個消息
mHandler.sendEmptyMessage(100);
}
});
}
}
複製代碼
能夠看到,建立Handler使用了主線程的Looper後,的確消息是在主線程中處理的了:
咱們以前說,全部handler.post和handler.sendMessage都會調用到Handler的sendMessageDelayed方法,方法以下:
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
複製代碼
這裏邏輯就很簡單了,直接調用了sendMessageAtTime方法,第一個參數爲Message,第二個參數爲SystemClock.uptimeMillis() + delayMillis,其中delayMillis爲延時的時間,單位爲毫秒,SystemClock.uptimeMillis() 爲開機到如今的時間(不包括休眠時間),單位爲毫秒。第二個參數主要是決定該Message在MessageQueue的順序,好比如今開機時間爲100s,發送一個延時20s的消息,則二者之和爲120s; 過了5秒,又發了一個延時5s的消息,則二者只喝爲105+5 = 110s。
sendMessageAtTime最後會調用到MessageQueue的enqueueMessage方法,咱們來看看這個方法:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
// when = 開機到目前的時間 + 延時時間
msg.when = when;
Message p = mMessages;
boolean needWake;
// 若是當前消息隊列爲空或者當前when小於隊頭when
// 則把消息插入到隊頭
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 {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 死循環,根據when尋找Message插入到MessageQueue合適的位置
for (;;) {
prev = p;
p = p.next;
// MessageQueue中依次日後找到第一個Message.when大於當前Message.when的Message
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 把當前Message插入到MessageQueue的合適位置
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 若是須要喚醒,則調用nativeWake去喚醒處理線程
nativeWake(mPtr);
}
}
return true;
}
複製代碼
從上面的代碼,能夠很容易看出,Message在MessageQueue中是根據when從小到大來排隊的,when是開機到如今的時間+延時時間。
好比,咱們假設開機時間爲100s,此時MessageQueue沒有消息,這時候發一個延時20s的消息,即when爲120000,則MessageQueue中的消息狀況如圖:
咱們知道Activity若是5s的事件都不能相應用戶的請求,則會ANR。咱們在來回顧下Looper.loop方法:
public static void loop() {
final Looper me = myLooper();
// 取到當前線程的MessageQueue
final MessageQueue queue = me.mQueue;
// 死循環
for (;;) {
// 調用MessageQueue.next,從隊列中取出消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 對消息對分發
msg.target.dispatchMessage(msg);
// 省略無關代碼
}
}
複製代碼
在前面講過Looper.loop方法維持了App的運行,它是裏面使用了一個死循環,咱們App日常的操做(Activity的生命週期、點擊事件等)都是屬於調用了 msg.target.dispatchMessage(msg)對消息的處理。可是若是MessageQueue中沒有消息時,MessageQueue的next方法會阻塞,那它會致使App對ANR麼?
咱們來看看MessageQueue的next方法
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
// 死循環
for (;;) {
// 阻塞MessageQueue
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) {
// 若是是延時消息,則算出須要阻塞的時間nextPollTimeoutMillis
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 若是不是延時顯示,則直接把消息返回,以供處理
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 {
// 沒有消息時,設置nextPollTimeoutMillis爲-1,阻塞MessageQueue
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 通常都會知足if條件,而後mBlocked設置爲true,繼續continue
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
// 省略無關代碼
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
複製代碼
next方法中,首先設置一個死循環,而後調用nativePollOnce(ptr, nextPollTimeoutMillis)方法,它是一個native方法,用於阻塞MessageQueue,主要是關注它的第二個參數nextPollTimeoutMillis,有以下三種可能:
咱們先繼續往下看,開始nextPollTimeoutMillis爲0,也就是不會阻塞,則繼續往下,這時候有三種狀況
因此,當消息隊列爲空時,實際上是調用本地方法nativePollOnce,且第二個參數爲-1,它會致使當前線程阻塞,且不會超時,因此不會出現ANR的狀況,其實這是由Linux的管道機制(pipe)來保證的,當線程阻塞時,對CPU等資源等消耗時極低的,具體的原理能夠自行查閱。
那線程阻塞之後,何時才能再喚醒呢?記得以前咱們說消息加入MessageQueue的邏輯麼?咱們再來回顧一下enqueueMessage的流程:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
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;
// 若是MessageQueue爲空
needWake = mBlocked;
} else {
// 若是MessageQueue中有消息
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;
}
// 若是須要喚醒當前線程,則調用nativeWake方法
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製代碼
其實就是使用Handler發送消息,把消息加入到MessageQueue時,會判斷當前MessageQueue是否阻塞,若是阻塞了,則須要調用nativeWake方法去喚醒線程,而這個阻塞是在前面提到的MessageQueue的next方法中,MessageQueue沒有消息或者消息爲延時消息時設置的。 因此MessageQueue的next方法可能會阻塞線程,但不會形成ANR。
有時候,咱們須要在子線程中直接彈出Toast來提示一些信息,代碼以下:
public class ToastActivity extends AppCompatActivity {
private static final String TAG = "ToastActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toast);
new Thread(new Runnable() {
@Override
public void run() {
// 子線程中彈出toast
Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
}
}).start();
}
}
複製代碼
運行程序,會發現程序會崩潰。是由於子線程不能更新UI麼?其實不是不這樣的。
既然不是這兩方面的緣由,咱們來看看報錯的log吧
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
// 子線程中彈出toast
Toast.makeText(ToastActivity.this, "提示一下!", Toast.LENGTH_LONG).show();
Looper.loop();
}
}).start();
複製代碼
發現就能夠了,能夠看出Toast也是須要使用Handler,咱們來看看Toast的實現,直接看Toast中的一個內部類TN,它是一個IBinder實現類,咱們來看它的定義:
private static class TN extends ITransientNotification.Stub {
final Runnable mShow = new Runnable() {
@Override
public void run() {
// 調用handleShow,處理顯示邏輯
handleShow();
}
}
final Handler mHandler = new Handler();
// 省略無關代碼
WindowManager mWM;
TN() {
// XXX This should be changed to use a Dialog, with a Theme.Toast
// defined that sets up the layout params appropriately.
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.windowAnimations = com.android.internal.R.style.Animation_Toast;
params.type = WindowManager.LayoutParams.TYPE_TOAST;
params.setTitle("Toast");
params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
}
/**
* schedule handleShow into the right thread
*/
@Override
public void show() {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
// 這裏是Binder線程池,用handler切換到有Looper的線程
mHandler.post(mShow);
}
// Toast的真實顯示
public void handleShow() {
if (mView != mNextView) {
mView = mNextView;
// 省略無關代碼
// 把View加入到Window上
mWM.addView(mView, mParams);
}
}
//省略無關代碼
}
複製代碼
能夠看出,Toast的顯示,使用了Binder通訊,其實就是WindowManagerService會拿到TN對象,調用其show方法,可是這是Binder線程池中執行的,因此使用handler切換到調用Toast的show方法所在的線程去執行,這裏使用的就是handler.post,因此就須要調用Toast.show方法所在線程有Looper。最後調用的就是handleShow方法,把View加載到Window上。
總結一下Handler:
前面咱們知道,loop方法中是一個死循環,又由於代碼是順序執行的,因此它以後的代碼是得不到執行的,以下:
public class LooperActivity extends AppCompatActivity {
private static final String TAG = "LooperActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_looper);
btn = (Button) findViewById(R.id.btn);
// 開啓一個子線程,去執行異步任務
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Log.e(TAG, "Looper.loop以前" );
// Looper.loop方法是一個死循環
Looper.loop();
// 得不到執行
Log.e(TAG, "Looper.loop以後" );
}
}).start();
}
}
複製代碼
log如圖,只會打印loop方法以前的,loop以後的代碼得不到執行:
其實Looper提供了quit和quitSafely方法來中止Looper,咱們先來看看quit的用法,在點擊事件中調用了Looper的quit方法,修改後的代碼以下
public class LooperActivity extends AppCompatActivity {
private static final String TAG = "LooperActivity";
private Button btn;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_looper);
btn = (Button) findViewById(R.id.btn);
// 開啓一個子線程,去執行異步任務
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Log.e(TAG, "Looper.loop以前" );
// Looper.loop方法是一個死循環
Looper.loop();
// 得不到執行
Log.e(TAG, "Looper.loop以後" );
}
}).start();
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 調用Looper的quit方法,中止Looper
mHandler.getLooper().quit();
}
});
}
}
複製代碼
開始和以前同樣,Looper.loop後的方法不會獲得執行,咱們點擊按鈕後,Looper會中止,Looper.loop以後的代碼也能夠獲得執行,log以下:
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
複製代碼
咱們發現,兩個方法都是調用了MessageQueue的quit方法,只是傳入的參數不一樣,咱們來看看MessageQueue的quit方法:
void quit(boolean safe) {
synchronized (this) {
if (mQuitting) {
return;
}
// MessageQueue正在中止,用於next方法退出死循環
mQuitting = true;
if (safe) {
// 刪除MessageQueue中的延時消息
removeAllFutureMessagesLocked();
} else {
// 刪除MessageQueue中的全部消息
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
複製代碼
首先把mQuitting設置爲true,主要用於MessageQueue的next方法退出死循環,而後經過safe去判斷邏輯邏輯,這裏就能夠看出Looper的quit和quitSafely的區別了
咱們繼續看mQuitting對MessageQueue的next方法的影響,回到next方法,咱們只看關鍵性代碼:
Message next() {
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
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;
}
// 判斷mQuitting
if (mQuitting) {
dispose();
return null;
}
}
}
}
複製代碼
直接看最後的部分,對mQuitting作判斷,咱們以前在MessageQueue的quit方法中,會把這個屬性設置爲true,其實就是會影響到這裏。知足條件之後,調用了dispose方法,並返回了null。
咱們先來看dispose方法
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
複製代碼
其實就是調用了nativeDestroy方法,它是一個native方法,用於在底層中止MessageQueue。
這裏只是中止了MessageQueue的next中的死循環,Looper.loop方法中的死循環仍是沒有退出,咱們繼續看Looper.loop方法。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
// 當mQuitting爲true,queue.next方法返回了null
Message msg = queue.next(); // might block
if (msg == null) {
// 直接return,退出loop的死循環
return;
}
// 省略無關代碼
}
}
複製代碼
前面咱們知道,當調用了Looper的quit或者quitSafely時,會設置當前線程的MessageQueue的 mQuitting爲true,而後致使了MessageQueue的next返回了null,而後直接return了,退出了loop中的死循環,這樣就完成了中止Looper的邏輯。
咱們一般會使用以下的方式去使用handler來通訊
public class HandlerActivity extends AppCompatActivity {
private static final String TAG = "HandlerActivity";
private Handler mHandler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 匿名內部類
mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 處理消息
}
};
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 發送延時100s的消息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 使用leakcanary作內存泄漏檢測
RefWatcher refWatcher = MyApplication.getRefWatcher(this);
if (refWatcher != null) {
refWatcher.watch(this);
}
}
}
複製代碼
可是會有一個問題,咱們進入這個頁面而後點擊按鈕,發送一個延時100s的消息,再退出這個Activity,這時候可能致使內存泄漏。
根本緣由是由於咱們建立的匿名內部類Handler對象持有了外部類Activity的對象,咱們知道,當使用handler發送消息時,會把handler做爲Message的target保存到MessageQueue,因爲延時了100s,因此這個Message暫時沒有獲得處理,這時候它們的引用關係爲MessageQueue持有了Message,Message持有了Handler,Handler持有了Activity,以下圖所示
那怎麼來解決Handler泄漏呢?主要有以下兩種方式:
咱們知道,靜態內部類是不會引用外部類的對象的,可是既然靜態內部類對象沒有持有外部類的對象,那麼咱們怎麼去調用外部類Activity的方法呢?答案是使用弱引用。代碼以下:
public class HandlerActivity extends AppCompatActivity {
private static final String TAG = "HandlerActivity";
private Handler mHandler;
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
// 建立Handler對象,把Activity對象傳入
mHandler = new MyHandler(HandlerActivity.this);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 發送延時100s的消息
mHandler.sendEmptyMessageDelayed(100, 100 * 1000);
}
});
}
// 靜態內部類
static class MyHandler extends Handler {
private WeakReference<Activity> activityWeakReference;
public MyHandler(Activity activity) {
activityWeakReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 處理消息
if (activityWeakReference != null) {
Activity activity = activityWeakReference.get();
// 拿到activity對象之後,調用activity的方法
if (activity != null) {
}
}
}
}
}
複製代碼
首先,咱們自定義了一個靜態內部類MyHandler,而後建立MyHandler對象時傳入當前Activity的對象,供Hander以弱應用的方式持有,這個時候Activity就被強引用和弱引用兩種方式引用了,咱們繼續發起一個延時100s的消息,而後退出當前Activity,這個時候Activity的強引用就不存在了,只存在弱引用,gc運行時會回收掉只有弱引用的Activity,這樣就不會形成內存泄漏了。
但這個延時消息仍是存在於MessageQueue中,獲得這個Message被取出時,仍是會進行分發處理,只是這時候Activity被回收掉了,activity爲null,不能再繼續調用Activity的方法了。因此,其實這是Activity能夠被回收了,而Handler、Message都不能被回收。
至於爲何使用弱引用而沒有使用軟引用,其實很簡單,對比下二者回收前提條件就清楚了
很明顯,當咱們Activity退出時,咱們但願無論內存是否足夠,都應該回收Activity對象,因此使用弱引用合適。
咱們知道,內存泄漏的源頭是MessageQueue持有的Message持有了Handler持有了Activity,那咱們在合適的地方把Message從MessageQueue中移除,不就能夠解決內存泄漏了麼?
Handler爲咱們提供了removeCallbacksAndMessages等方法用於移除消息,好比,在Activity的onDestroy中調用Handler的removeCallbacksAndMessages,代碼以下:
@Override
protected void onDestroy() {
super.onDestroy();
// 移除MessageQueue中target爲該mHandler的Message
mHandler.removeCallbacksAndMessages(null);
}
複製代碼
其實就是在Activity的onDestroy方法中調用mHandler.removeCallbacksAndMessages(null),這樣就移除了MessageQueue中target爲該mHandler的Message,由於MessageQueue沒有引用該Handler發送的Message了,因此當Activity退出時,Message、Handler、Activity都是可回收的了,這樣就能解決內存泄漏的問題了。
Handler的知識很少,但細節特別多,一旦久一點沒看就會忘記。 因此,無論是別人寫的仍是本身寫的,先把相關知識記下來,下次忘記了回來再看一下就好了。