這篇是以前Looper、Handler、Message以及MessageQueue之間的關係後續版本,將更加詳細講解一下以前沒提到的細節內容。java
先說一下這篇文章主要要解決的幾個點:bash
以前文章提到過要在線程(包括UI線程)中使用Handler,就須要Looper.prepare()以及Looper.loop()相關操做,可是爲何Activity中能夠直接使用Handler呢?這個問題在以前文章中提到過,是由於系統已經幫咱們作了Looper相關的準備操做,咱們來看一下具體實現。app
Android進程啓動過程當中會建立一個ActivityThread對象,並調用其中的main(),用以接收系統的各類操做。這個ActivityThread對象就是咱們常說的UI線程,其實並非一個線程。在main()中咱們就能看到Looper初始化相關的操做。異步
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼
這裏咱們只看到了MainLooper初始化操做,並無發現它跟Activity有何關聯,接着往下看。ide
ActivityThread有一個成員變量mH,是一個H類型的對象,這個H是是Handler的子類,內部用於處理4大組件的啓動、生命週期相關方法以及其餘一些系統的回調。固然系統的啓動過程太過冗長,這裏不作細緻的分析,如今只須要知道mH這個Handler對象。oop
final H mH = new H();
複製代碼
就是這個mH接收了來自ActivityManagerService傳遞過來的啓動Activity的消息,並調用了ActivityThread中的performLaunchActivity()來建立一個Activity並調用相關生命週期方法()post
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
....
return activity;
}
複製代碼
這裏咱們能夠知道,Activity運行在mH內部。再看最上面ActivityThread的建立,能夠看出mH中調用的方法也是在MainLooper中執行,Activity在MainLooper中執行,因此咱們並不須要先初始化Looper再使用Handler。ui
首先要明白一個問題,UI卡頓實際上是MessageQueue中某些方法,好比onCreate()、onResume()等生命週期的回調執行過長,致使UI繪製某些幀丟失甚至ANR。MessageQueue雖然是個死循環,可是當消息隊列爲空時,只是阻塞消息隊列,當時並不佔用cpu資源,因此並不會引發UI的卡頓。至於爲何MessageQueue能夠作到沒有消息時阻塞隊列,有消息時能夠喚起繼續工做的,讀者能夠查閱Handler機制在Native層中的實現,主要是Linux中的epoll。this
固然你還有疑問爲何要將MessageQueue寫成死循環,這是由於UI線程須要一直接收系統,以及其餘組件發送過來的事件,中間可能會有空閒的狀況,爲了保證MessageQueue不退出,就只能寫成死循環了。spa
前面的文章咱們能夠知道,無論Handler經過什麼形式發送消息,最後都會調用到enqueueMessage()。這個方法內部調用了MessageQueue的enqueueMessage()。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
//除了系統的同步控制,全部的msg都必需要有target來處理msg
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) {
//循環隊列正在退出,直接將msg回收
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;
}
//這裏開始正常的msg入隊操做,標記msg的使用狀態,處理時間等
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//若是當前隊列爲空,
//或者msg須要當即處理,
//或者當前消息隊列中頭消息處理的時間大於當前消息,
//都是將頭消息置爲當前消息,
//同時若是當前隊列阻塞,則喚起消息隊列進入循環
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//進入這個分支,是將msg按執行時間前後插入消息鏈表中
//正常狀況下咱們不須要喚醒隊列,除非當前消息對列正在
//同步消息控制,同時當前消息爲第一條異步消息
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;
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製代碼
上面的方法咱們能夠看到,處理了幾種狀況都在註釋裏作了說明,下面再來看看消息具體是如何取出來的
Message next() {
//ptr = 0 當前隊列正在退出
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
//這個nextPollTimeoutMillis用來控制消息隊列是否要阻塞,以及阻塞多久
//nextPollTimeoutMillis = 0 表示不阻塞
//nextPollTimeoutMillis = -1 表示一直阻塞,直到被喚醒
//nextPollTimeoutMillis > 0 表示阻塞這麼長時間以後喚醒
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) {
//當前消息隊列被同步控制,找到第一條異步的消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//第一條異步消息還沒有到執行時間,計算須要阻塞的時間
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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
//首次循環的時候執行設置的IdleHandler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
....
// 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;
}
}
複製代碼
上面消息的enqueueMessage()以及next()取出操做能夠很清晰的看到Handler.postDelay()的實現原理,就是消息按執行時間入隊,並延遲處理。
子線程中須要咱們手動準備Looper,而後很方便的進行線程間通訊,可是在消息處理完成以後要手動將Looper退出,以避免形成內存泄漏。 從SDK 23 開始系統提供一個HandlerThread供咱們方便的在子線程中使用Handler;
當咱們在Activity內部直接建立一個非靜態的Handler成員變量時,IDE就會提醒咱們,這樣使用Handler可能會形成內存泄漏,咱們來分析一下緣由在哪裏。
好,根據上面3點,咱們來看一下Activity的引用:
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
//do something
}
}, 10 * 60 * 1000);
}
}
複製代碼
當這個runnable消息非發送出去以後,mHandler被msg持有,並安排到10分鐘以後執行,同時MainActivity也一直被mHandler和runnable持用。 當MainActivity退出時,由於Looper是UI線程的Looper,咱們沒法退出,msg還在消息隊列中還沒有執行,會阻止MainActivity被系統回收,就形成了內存泄漏。
避免這種狀況方法有不少種這裏提2種經常使用的方式:
定義靜態Handler對象,並傳人UI線程Looper
static Handler sHandler = new Handler(Looper.getMainLooper());
複製代碼
將咱們須要的Handler定義成靜態內部類,同時使用弱引用保存對Activity的引用
public static class MyHandler extends Handler {
private final WeakReference<Activity> mActivityWf;
public MyHandler(WeakReference<Activity> activityWf) {
mActivityWf = activityWf;
}
}
複製代碼
消息隊列的同步控制是經過postSyncBarrier()這個方法來實現, 說是Barrier,其實就是在隊列的頭部插入一條特殊的msg
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } } 複製代碼
代碼其實也挺簡單的,就是在消息隊列適當的位置插入一條沒有target的msg,next()出去消息時,若是發現個同步控制消息,則它以後的同步消息就暫時不出列。
默認狀況下,咱們構建的Handler發送的都是同步消息,除非在Handler構建的時候指定發送的全部消息類型都爲異步消息。
固然通常咱們也不須要同步控制,雖然當前方法是public修飾的,可是hide,系統並無打算將此方法開放給咱們使用。
咱們再來看一下,系統哪裏用到了這個同步控制。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
複製代碼
這是ViewRootImpl中的一個方法,這個方法執行以後,系統就開始進行頁面的繪製流程,咱們看到這裏調用postSyncBarrier(),這麼作的目的就是要讓UI繪製儘快完成,以避免形成卡頓。