前言
Handler系列文章共兩篇:
在上一篇中,我們對Handler的主體部分進(jìn)行了講解,今天,我們就來學(xué)習(xí)一下Handler相關(guān)的一些擴(kuò)展知識,講完這些擴(kuò)展知識后,在來回答之前列出來的一系列問題
同步屏障
通過上一篇的學(xué)習(xí),我們知道: Handler發(fā)送的Message會放入到MessageQueue中,MessageQueue中維護(hù)了一個優(yōu)先級隊列,優(yōu)先級隊列的意思就是將存儲數(shù)據(jù)的單鏈表按照時間升序進(jìn)行排序形成的,Looper則按照順序,每次從這個優(yōu)先級隊列中取出一個Message進(jìn)行分發(fā),一個處理完就處理下一個。
那么問題來了:我能不能讓我的一個Message被優(yōu)先處理?
可以,使用同步屏障
這里,我心里又會有個疑問,什么是同步屏障?怎么使用同步屏障?同步屏障有啥作用?帶著這些疑問???,我們來分析下源碼
先看下MessageQueue的next方法,在上一篇中,我們省略了一部分代碼,其中有一部分是這樣子的,僅貼出關(guān)鍵代碼
Message next() {
//...
for (;;) {
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());
}
}
}
上述代碼:
1、判斷當(dāng)前msg不為空并且msg.target為空,則進(jìn)入條件體里面
2、條件體里面有一行源碼注釋,翻譯過來就是: 被一個屏障給阻礙。在隊列中查找下一個異步消息
3、接下來就是一個循環(huán),遍歷找出一條異步消息,循環(huán)體里面就是鏈表相關(guān)的操作
這里大家是不是會有個疑問?msg.target怎么可能會為空呢?之前發(fā)送消息的一系列方法不是都會給msg.target對象賦值嗎?
沒錯,我們在回顧一下Handler的enqueueMessage:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
//將當(dāng)前Handler賦值給msg.target
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//調(diào)用MessageQueue的enqueueMessage方法
return queue.enqueueMessage(msg, uptimeMillis);
}
我們知道Handler的post和send系列方法發(fā)送的消息,最終都會走到這個方法,msg.target都會被賦值,因此不可能為空。那msg.target啥時候會為空呢?我們推斷肯定是其他發(fā)送消息的方法使得msg.target為空,那我們就找一下,會發(fā)現(xiàn)MessageQueue的postSyncBarrier的方法中沒有給msg.target對象賦值:
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屬性為null的Message
因此我們是不是可以知道,Message的target屬性為空和非空是很不一樣的,這里就不賣關(guān)子了,直接給結(jié)論: target屬性為空的Message就是同步屏障,他是一種特殊的消息,并不會被消費,僅僅是作為一個標(biāo)識處于 MessageQueue 中,當(dāng)MessageQueue的next方法遇到同步屏障的時候,就會循環(huán)遍歷整個鏈表找到標(biāo)記為異步消息的Message,其他的消息會直接忽視,那么這樣異步消息就會提前被執(zhí)行了
現(xiàn)在我們現(xiàn)在就可以回答上面的問題了:target屬性為空的Message就是同步屏障,同步屏障可以使得異步消息優(yōu)先被處理,通過MessageQueue的postSyncBarrier可以添加一個同步屏障
注意: 在異步消息處理完之后,同步屏障并不會被移除,需要我們手動移除,從上面的源碼我們也可以看出,如果不移除同步屏障,那么他會一直在那里,這樣同步消息就永遠(yuǎn)無法被執(zhí)行了。
因此我們在使用完同步屏障后,需要手動移除,代碼如下:
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
到這里我心里又有一個疑問了?怎么把一個消息變成異步消息呢?還是回到Handler的enqueueMessage方法:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
//如果mAsynchronous,則將該消息設(shè)置為異步消息
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
從上述代碼我是可以看到,通過msg.setAsynchronous方法設(shè)置為true,可以把一個消息變成異步消息,但是前提得滿足mAsynchronous屬性為true,mAsynchronous是Handler中的一個屬性,他會在這兩個構(gòu)造方法中被賦值:
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
//...
mAsynchronous = async;
}
public Handler(@Nullable Callback callback, boolean async) {
//...
mAsynchronous = async;
}
因此我們是不是可以得出結(jié)論,把一個消息設(shè)置為異步消息,有兩種方式:
1、在Handler的構(gòu)造方法中,傳入async為true,那么這個時候發(fā)送的Message就都是異步的的消息
2、給Message通過setAsynchronous 方法標(biāo)志為異步
但是,上面兩個構(gòu)造方法對外是不可見的,我們調(diào)用不到,而且設(shè)置同步屏障的方法對外也是不可見的,說明谷歌不想要我們?nèi)ナ褂盟?。所以這里同步屏障也是作為一個了解,一般只有系統(tǒng)會去使用它,例如:在進(jìn)行UI繪制的時候,以下是ViewRootImpl中執(zhí)行UI繪制的方法使用到了同步屏障:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
上述代碼在把繪制消息放入隊列之前,先放入了一個同步屏障,然后在發(fā)送異步繪制消息,從而使得界面繪制的消息會比其他消息優(yōu)先執(zhí)行,避免了因為 MessageQueue 中消息太多導(dǎo)致繪制消息被阻塞導(dǎo)致畫面卡頓,當(dāng)繪制完成后,就會將同步屏障移除。
IdleHandler
見名知意,idle是空閑的意思,那么IdleHandler就是空閑的Handler,有點這個意思,實際上它是MessageQueue中有一個靜態(tài)接口
public static interface IdleHandler {
boolean queueIdle();
}
可以看到它是一個單方法的接口,也可稱為函數(shù)型接口,它的作用是:在UI線程處理完所有View事務(wù)后,回調(diào)一些額外的操作,且不會堵塞主進(jìn)程;我們來實際操作一下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Handler().getLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.d("print", "queueIdle: 空閑時做一些輕量級別");
return false;
}
});
}
}
//上面代碼會打印如下結(jié)果
queueIdle: 空閑時做一些輕量級別
接著進(jìn)行源碼分析,我們在看下addIdleHandler這個方法:
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
可以看到,被添加進(jìn)來的handler放到了mIdleHandlers,跟過去看下mIdleHandlers,會發(fā)現(xiàn)MessageQueue中定義了IdleHandler的集合和數(shù)組,并且有一些操作方法,如下:
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private IdleHandler[] mPendingIdleHandlers;
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
最后在看下MessageQueue中的Next方法,僅貼出關(guān)鍵代碼:
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
for (;;) {
//...
synchronized (this) {
//...
//當(dāng)前無消息,或還需要等待一段時間消息才能分發(fā),獲得IdleHandler的數(shù)量
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
//如果沒有idle handler需要執(zhí)行,阻塞線程進(jìn)入下次循環(huán)
mBlocked = true;
continue;
}
//初始化mPendingIdleHandlers
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
//把List轉(zhuǎn)化成數(shù)組類型
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//循環(huán)遍歷所有的IdleHandler
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 {
//獲得idler.queueIdle的返回值
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//keep即idler.queueIdle的返回值,如果為false表明只要執(zhí)行一次,并移除,否則不移除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
//將pendingIdleHandlerCount置為0避免下次再次執(zhí)行
pendingIdleHandlerCount = 0;
// 當(dāng)在執(zhí)行IdleHandler的時候,可能有新的消息已經(jīng)進(jìn)來了
// 所以這個時候不能阻塞,要回去循環(huán)一次看一下
nextPollTimeoutMillis = 0;
}
}
上述代碼解析:
1、當(dāng)調(diào)用next方法的時候,會將pendingIdleHandlerCount賦值為-1
2、判斷pendingIdleHandlerCount是否小于0并且MessageQueue 是否為空或者有延遲消息需要執(zhí)行,如果是則把存儲IdleHandler的list的長度賦值給pendingIdleHandlerCount
3、判斷如果沒有IdleHandler需要執(zhí)行,阻塞線程進(jìn)入下次循環(huán),如果有,則初始化mPendingIdleHandlers,把list中的所有IdleHandler放到數(shù)組中。這一步是為了不讓在執(zhí)行IdleHandler的時候List被插入新的IdleHandler,造成邏輯混亂
4、循環(huán)遍歷所有的IdleHandler并執(zhí)行,查看idler.queueIdle方法的返回值,為false表明這個IdleHandler只需要執(zhí)行一次,并移除,為true,則不移除
5、將pendingIdleHandlerCount置為0避免下次再次執(zhí)行, 當(dāng)在執(zhí)行IdleHandler的時候,可能有新的消息已經(jīng)進(jìn)來了,所以這個時候不能阻塞,要回去循環(huán)一次看一下
到這里同步屏障和IdleHandler都講完了,建議讀者配合完整的源碼在去仔細(xì)閱讀一次。
實際應(yīng)用: 可以在IdleHandler里面獲取View的寬高
主線程消息循環(huán)
在上一篇中我們講到,ActivityThread就是主線程,也可以說是UI線程,在主線程的main方法中創(chuàng)建了Looper,并開啟了消息循環(huán):
public static void main(String[] args) {
//...
//創(chuàng)建Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//...
//開啟循環(huán)讀取消息
Looper.loop();
//Looper如果因異常原因停止循環(huán)則拋異常
throw new RuntimeException("Main thread loop unexpectedly exited");
}
主線程的消息循環(huán)開始了以后,ActivityThread還需要有一個Handler來和消息隊列進(jìn)行交互,這個Handler就是ActivityThread.H,它內(nèi)部定義了很多的消息類型,例如四大組件的啟動,Application的啟動等等
class H extends Handler {
public static final int BIND_APPLICATION = 110;
@UnsupportedAppUsage
public static final int EXIT_APPLICATION = 111;
@UnsupportedAppUsage
public static final int RECEIVER = 113;
@UnsupportedAppUsage
public static final int CREATE_SERVICE = 114;
@UnsupportedAppUsage
public static final int SERVICE_ARGS = 115;
@UnsupportedAppUsage
public static final int STOP_SERVICE = 116;
public static final int CONFIGURATION_CHANGED = 118;
public static final int CLEAN_UP_CONTEXT = 119;
@UnsupportedAppUsage
public static final int GC_WHEN_IDLE = 120;
@UnsupportedAppUsage
public static final int BIND_SERVICE = 121;
@UnsupportedAppUsage
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
public static final int DESTROY_BACKUP_AGENT = 129;
public static final int SUICIDE = 130;
//...
public void handleMessage(Message msg) {
//...
}
}
關(guān)于ActivityThread.H的實際應(yīng)用,我們在看Activity的啟動流程可能會有比較深入的理解,ActivityThread通過ApplicationThread和AMS進(jìn)行進(jìn)程間通信的方式完成ActivityThread的請求后,會回調(diào)ApplicationThread中的Binder方法,然后ApplicationThread會向H發(fā)送消息,H收到消息后會將ApplicationThread中的邏輯切換到ActivityThread中去執(zhí)行,即切換到主線程去執(zhí)行,這個過程就是主線程的消息循環(huán)模型
妙用 Looper 機(jī)制
1、我們可以通過LoopergetMainLooper方法獲取主線程Looper,從而可以判斷當(dāng)前線程是否是主線程
2、將 Runnable post 到主線程執(zhí)行
public final class MainThread {
private MainThread() {
}
private static final Handler HANDLER = new Handler(Looper.getMainLooper());
public static void run(@NonNull Runnable runnable) {
if (isMainThread()) {
runnable.run();
}else{
HANDLER.post(runnable);
}
}
public static boolean isMainThread() {
return Looper.myLooper() == Looper.getMainLooper();
}
}
子線程使用Handler及相關(guān)注意事項
我們通常使用Handler都是從子線程發(fā)送消息到主線程去處理,那么這里我們嘗試一下從主線程發(fā)送消息到子線程來處理,上代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//創(chuàng)建線程實例并開啟
MyThread myThread = new MyThread();
myThread.start();
//打開這段注釋就不會crash,且看下面分析
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//獲取Handler發(fā)送消息
myThread.getHandler().sendEmptyMessage(0x001);
}
public static class MyThread extends Thread {
private Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(@NonNull Message msg) {
if(msg.what == 0x001){
Log.d("print", "handleMessage: ");
}
}
};
Looper.loop();
}
public Handler getHandler(){
return mHandler;
}
}
}
運行一下上述代碼,發(fā)現(xiàn)會Crash,如下圖:
報了一個空指針異常,原因就是多線程并發(fā),當(dāng)主線程執(zhí)行到sendEnptyMessage時,子線程的Handler還沒有創(chuàng)建。因此我們可以在獲取Handler的時候讓主線程休眠一下在執(zhí)行,應(yīng)用就不會Crash了,打開上面代碼的注釋即可
值得注意的是:我們自己創(chuàng)建的Looper在使用完畢后應(yīng)該調(diào)用quit方法來終止消息循環(huán),如果不退出的話,那么該線程的Looper處理完所有的消息后,就會處于一個阻塞狀態(tài),要知道線程是比較重量級的,如果一直存在,肯定會對應(yīng)用性能造成一定的影響。而如果退出Looper,這個線程就會立刻終止,因此建議不需要的時候終止Looper。
因此在子線程使用Handler,我們需要注意一下兩點:
1、必須調(diào)用Looper.prepare()創(chuàng)建當(dāng)前線程的 Looper,并調(diào)用Looper.loop()開啟消息循環(huán)
2、必須在使用結(jié)束后調(diào)用Looper的quit方法退出當(dāng)前線程
HandlerThread
上面講到主線程發(fā)送消息到子線程來處理,其實Android已經(jīng)給我們提供了一個這樣輕量級的異步類,那就是HandlerThread
HandlerThread的實現(xiàn)原理也比較簡單:繼承Thread并對Looper進(jìn)行了封裝
具體源碼就不過多分析了,大家有興趣的可以去看一下,也就100多行代碼,這里主要講解一下使用:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1,創(chuàng)建Handler實例
HandlerThread mHandlerThread = new HandlerThread("HandlerThread");
//2,啟動線程
mHandlerThread.start();
//3,使用傳入Looper為參數(shù)的構(gòu)造方法創(chuàng)建Handler實例
Handler mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
Log.d("print", "當(dāng)前線程: " + Thread.currentThread().getName() + " handleMessage");
}
};
//4,使用Handler發(fā)送消息
mHandler.sendEmptyMessage(0x001);
//5,在合適的時機(jī)調(diào)用HandlerThread的quit方法,退出消息循環(huán)
}
}
//上述代碼打印結(jié)果:
當(dāng)前線程: HandlerThread handleMessage
Handler HandlerThread Thread三者區(qū)別
Handler:在Android中負(fù)責(zé)發(fā)送和處理消息
HandlerThread:繼承自Thread,對Looper進(jìn)行了封裝,也就是說它在子線程維護(hù)了一個Looper,方便我們在子線程中去處理消息
Thread: cpu執(zhí)行的最小單位,即線程,它在執(zhí)行完后就立馬結(jié)束了,并不能去處理消息。如果要處理,需要配合Looper,Handler一起使用
子線程彈Toast
//1
new Thread(){
@Override
public void run() {
Toast.makeText(MainActivity.this, "子線程彈Toast", Toast.LENGTH_SHORT).show();
}
}.start();
//2
new Thread(){
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this, "子線程彈Toast", Toast.LENGTH_SHORT).show();
Looper.loop();
}
}.start();
上述1代碼運行會奔潰,會報這么一個異常提示:"Can't toast on a thread that has not called Looper.prepare()"
原因就是Toast的實現(xiàn)也是依賴Handler,而我們知道在子線程中創(chuàng)建Handler,需先創(chuàng)建Looper并開啟消息循環(huán),這點在Toast中的源碼也有體現(xiàn),如下圖:
因此我們在子線程創(chuàng)建Toast就需要使用上述2代碼的方式
子線程彈Dialog
new Thread(){
@Override
public void run() {
Looper.prepare();
new AlertDialog.Builder(MainActivity.this)
.setTitle("標(biāo)題")
.setMessage("子線程彈Dialog")
.setNegativeButton("取消",null)
.setPositiveButton("確定",null)
.show();
Looper.loop();
}
}.start();
和上面Toast差不多,這里貼出正確的代碼示例,它的實現(xiàn)也是依賴Handler,我們在它的源碼中可以看到:
private final Handler mHandler = new Handler();
他直接就new了一個Handler實例,我們知道,創(chuàng)建Handler,需要先創(chuàng)建Looper并開啟消息循環(huán),主線程中已經(jīng)給我們創(chuàng)建并開啟消息循環(huán),而子線程中并沒有,如果不創(chuàng)建那就會報這句經(jīng)典的異常提示:"Can't create handler inside thread that has not called Looper.prepare() ",因此在子線程中,需要我們手動去創(chuàng)建并開啟消息循環(huán)
到這里,Handler相關(guān)的擴(kuò)展知識就全部講完了,我們會發(fā)現(xiàn)也有著很多使用的小技巧,比如 IdleHandler,判斷是否是主線程等等
由于 Handler 的特性,它在 Android 里的應(yīng)用非常廣泛,比如: AsyncTask、HandlerThread、Messenger、IdleHandler 和 IntentService 等等,下面我們來回答上一篇中列出來的一系列問題
問題
1、Handler有哪些作用?
答:
1、Handler能夠進(jìn)行線程之間的切換
2、Handler能夠按照順序處理消息,避免并發(fā)
3、Handler能夠阻塞線程
4、Handler能夠發(fā)送并處理延遲消息
解析:
1、Handler能夠進(jìn)行線程之間的切換,是因為使用了不同線程的Looper處理消息
2、Handler能夠按照順序處理消息,避免并發(fā),是因為消息在入隊的時候會按照時間升序?qū)Ξ?dāng)前鏈表進(jìn)行排序,Looper讀取的時候,MessageQueue的next方法會循環(huán)加鎖,同時配合阻塞喚醒機(jī)制
3、Handler能夠阻塞線程主要是基于Linux的epoll機(jī)制實現(xiàn)的
4、Handler能夠處理延遲消息,是因為MessageQueue的next方法中會拿當(dāng)前消息時間和當(dāng)前時間做比較,如果是延遲消息,那么就會阻塞當(dāng)前線程,等阻塞時間到,在執(zhí)行該消息
2、為什么我們能在主線程直接使用Handler,而不需要創(chuàng)建Looper?
答:主線程已經(jīng)創(chuàng)建了Looper,并開啟了消息循環(huán)
3、如果想要在子線程創(chuàng)建Handler,需要做什么準(zhǔn)備?
答:需要先創(chuàng)建Looper,并開啟消息循環(huán)
4、一個線程有幾個Handler?
答:可以有任意多個
5、一個線程有幾個Looper?如何保證?
答:一個線程只有一個Looper,通過ThreadLocal來保證
6、Handler發(fā)送消息的時候,時間為啥要取SystemClock.uptimeMillis() + delayMillis,可以把SystemClock.uptimeMillis() 換成System.currentTimeMillis()嗎?
答:不可以
SystemClock.uptimeMillis() 這個方法獲取的時間,是自系統(tǒng)開機(jī)到現(xiàn)在的一個毫秒數(shù),這個時間是個相對的
System.currentTimeMillis() 這個方法獲取的是自1970-01-01 00:00:00 到現(xiàn)在的一個毫秒數(shù),這是一個和系統(tǒng)強(qiáng)關(guān)聯(lián)的時間,而且這個值可以做修改
1、使用System.currentTimeMillis()可能會導(dǎo)致延遲消息失效
2、最終這個時間會被設(shè)置到Message的when屬性,而Message的when屬性只是需要一個時間差來表示消息的先后順序,使用一個相對時間就行了,沒必要使用一個絕對時間
7、為什么Looper死循環(huán),卻不會導(dǎo)致應(yīng)用卡死?
答:因為當(dāng)Looper處理完所有消息的時候,會調(diào)用Linux的epoll機(jī)制進(jìn)入到阻塞狀態(tài),當(dāng)有新的Message進(jìn)來的時候會打破阻塞繼續(xù)執(zhí)行。
應(yīng)用卡死即ANR: 全稱Applicationn Not Responding,中文意思是應(yīng)用無響應(yīng),當(dāng)我發(fā)送一個消息到主線程,Handler經(jīng)過一定時間沒有執(zhí)行完這條消息,那么這個時候就會拋出ANR異常
Looper死循環(huán): 循環(huán)執(zhí)行各種事務(wù),Looper死循環(huán)說明線程還活著,如果沒有Looper死循環(huán),線程結(jié)束,應(yīng)用就退出了,當(dāng)Looper處理完所有消息的時候會調(diào)用Linux的epoll機(jī)制進(jìn)入到阻塞狀態(tài),當(dāng)有新的Message進(jìn)來的時候會打破阻塞繼續(xù)執(zhí)行
8、Handler內(nèi)存泄露原因? 如何解決?
內(nèi)存泄漏的本質(zhì)是長生命周期的對象持有短生命周期對象的引用,導(dǎo)致短生命周期的對象無法被回收,從而導(dǎo)致了內(nèi)存泄漏
下面我們就看個導(dǎo)致內(nèi)存泄漏的例子
public class MainActivity extends AppCompatActivity {
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
//do something
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//發(fā)送一個延遲消息,10分鐘后在執(zhí)行
mHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
}
}
上述代碼:
1、我們通過匿名內(nèi)部類的方式創(chuàng)建了一個Handler的實例
2、在onCreate方法里面通過Handler實例發(fā)送了一個延遲10分鐘執(zhí)行的消息
我們發(fā)送的這個延遲10分鐘執(zhí)行的消息它是持有Handler的引用的,根據(jù)Java特性我們又知道,非靜態(tài)內(nèi)部類會持有外部類的引用,因此當(dāng)前Handler又持有Activity的引用,而Message又存在MessageQueue中,MessageQueue又在當(dāng)前線程中,因此會存在一個引用鏈關(guān)系:
當(dāng)前線程->MessageQueue->Message->Handler->Activity
因此當(dāng)我們退出Activity的時候,由于消息需要在10分鐘后在執(zhí)行,因此會一直持有Activity,從而導(dǎo)致了Activity的內(nèi)存泄漏
通過上面分析我們知道了內(nèi)存泄漏的原因就是持有了Activity的引用,那我們是不是會想,切斷這條引用,那么如果我們需要用到Activity相關(guān)的屬性和方法采用弱引用的方式不就可以了么?我們實際操作一下,把Handler寫成一個靜態(tài)內(nèi)部類
public class MainActivity extends AppCompatActivity {
private final SafeHandler mSafeHandler = new SafeHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//發(fā)送一個延遲消息,10分鐘后在執(zhí)行
mSafeHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
}
//靜態(tài)內(nèi)部類并持有Activity的弱引用
private static class SafeHandler extends Handler{
private final WeakReference<MainActivity> mWeakReference;
public SafeHandler(MainActivity activity){
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
MainActivity mMainActivity = mWeakReference.get();
if(mMainActivity != null){
//do something
}
}
}
}
上述代碼
1、把Handler定義成了一個靜態(tài)內(nèi)部類,并持有當(dāng)前Activity的弱引用,弱引用會在Java虛擬機(jī)發(fā)生gc的時候把對象給回收掉
經(jīng)過上述改造,我們解決了Activity的內(nèi)存泄漏,此時的引用鏈關(guān)系為:
當(dāng)前線程->MessageQueue->Message->Handler
我們會發(fā)現(xiàn)Message還是會持有Handler的引用,從而導(dǎo)致Handler也會內(nèi)存泄漏,所以我們應(yīng)該在Activity銷毀的時候,在他的生命周期方法里,把MessageQueue中的Message都給移除掉,因此最終就變成了這樣:
public class MainActivity extends AppCompatActivity {
private final SafeHandler mSafeHandler = new SafeHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//發(fā)送一個延遲消息,10分鐘后在執(zhí)行
mSafeHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
mSafeHandler.removeCallbacksAndMessages(null);
}
//靜態(tài)內(nèi)部類并持有Activity的弱引用
private static class SafeHandler extends Handler{
private final WeakReference<MainActivity> mWeakReference;
public SafeHandler(MainActivity activity){
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
MainActivity mMainActivity = mWeakReference.get();
if(mMainActivity != null){
//do something
}
}
}
}
因此當(dāng)Activity銷毀后,引用鏈關(guān)系為:
當(dāng)前線程->MessageQueue
而當(dāng)前線程和MessageQueue的生命周期和應(yīng)用生命周期是一樣長的,因此也就不存在內(nèi)存泄漏了,完美。
所以解決Handler內(nèi)存泄漏最好的方式就是:將Handler定義成靜態(tài)內(nèi)部類,內(nèi)部持有Activity的弱引用,并在Activity銷毀的時候移除所有消息
9、線程維護(hù)的Looper,在消息隊列無消息時的處理方案是什么?有什么用?
答:當(dāng)消息隊列無消息時,Looper會阻塞當(dāng)前線程,釋放cpu資源,提高App性能
我們知道Looper的loop方法中有個死循環(huán)一直在讀取MessageQueue中的消息,其實是調(diào)用了MessageQueue中的next方法,這個方法會在無消息時,調(diào)用Linux的epoll機(jī)制,使得線程進(jìn)入阻塞狀態(tài),當(dāng)有新消息到來時,就會將它喚醒,next方法里會判斷當(dāng)前消息是否是延遲消息,如果是則阻塞線程,如果不是,則會返回這條消息并將其從優(yōu)先級隊列中給移除
10、MessageQueue什么情況下會被喚醒?
答:需要分情況
1、發(fā)送消息過來,此時MessageQueue中無消息或者當(dāng)前發(fā)送過來的消息攜帶的when為0或者有延遲執(zhí)行的消息,那么需要喚醒
2、當(dāng)遇到同步屏障且當(dāng)前發(fā)送過來的消息為異步消息,判斷該異步消息是否插入在所有異步消息的隊首,如果是則需要喚醒,如果不是,則不喚醒
11、線程什么情況下會被阻塞?
答:分情況
1、當(dāng)MessageQueue中沒有消息的時候,這個時候會無限阻塞,
2、當(dāng)前MessageQueue中全部是延遲消息,阻塞時間為(當(dāng)前延遲消息時間 - 當(dāng)前時間),如果這個阻塞時間超過來Integer類型的最大值,則取Integer類型的最大值
12、我們可以使用多個Handler往消息隊列中添加數(shù)據(jù),那么可能存在發(fā)消息的Handler存在不同的線程,那么Handler是如何保證MessageQueue并發(fā)訪問安全的呢?
答:循環(huán)加鎖,配合阻塞喚醒機(jī)制
我們可以發(fā)現(xiàn)MessageQueue其實是“生產(chǎn)者-消費者”模型,Handler不斷地放入消息,Looper不斷地取出,這就涉及到死鎖問題。如果Looper拿到鎖,但是隊列中沒有消息,就會一直等待,而Handler需要把消息放進(jìn)去,鎖卻被Looper拿著無法入隊,這就造成了死鎖。Handler機(jī)制的解決方法是循環(huán)加鎖。在MessageQueue的next方法中:
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
}
}
}
我們可以看到他的等待是在鎖外的,當(dāng)隊列中沒有消息的時候,他會先釋放鎖,再進(jìn)行等待,直到被喚醒。這樣就不會造成死鎖問題了。
那在入隊的時候會不會因為隊列已經(jīng)滿了然后一邊在等待消息處理一邊拿著鎖呢?這一點不同的是MessageQueue的消息沒有上限,或者說他的上限就是JVM給程序分配的內(nèi)存,如果超出內(nèi)存會拋出異常,但一般情況下是不會的。
13、Handler是如何進(jìn)行線程切換的呢?
答:使用不同線程的Looper處理消息
我們通常處理消息是在Handler的handleMessage方法中,那么這個方法是在哪里回調(diào)的呢?看下面這段代碼
public static void loop() {
//開啟死循環(huán)讀取消息
for (;;) {
// 調(diào)用Message對應(yīng)的Handler處理消息
msg.target.dispatchMessage(msg);
}
}
上述代碼中msg.target其實就是我們發(fā)送消息的Handler,因此他會回調(diào)Handler的dispatchMessage方法,而dispatchMessage這個方法我們在上一篇中重點分析過,其中有一部分邏輯就是會回調(diào)到Handler的handleMessage方法,我們還可以發(fā)現(xiàn),Handler的handleMessage方法所在的線程是由Looper的loop方法決定的。平時我們使用的時候,是從異步線程發(fā)送消息到 Handler,而這個 Handler 的 handleMessage() 方法是在主線程調(diào)用的,因為Looper是在主線程創(chuàng)建的,所以消息就從異步線程切換到了主線程。
14、我們在使用Message的時候,應(yīng)該如何去創(chuàng)建它?
答:Android 給 Message 設(shè)計了回收機(jī)制,官方建議是通過Message.obtain方法來獲取,而不是直接new一個新的對象,所以我們在使用的時候應(yīng)盡量復(fù)用 Message ,減少內(nèi)存消耗,方式有二:
1、調(diào)用 Message 的一系列靜態(tài)重載方法 Message.obtain 獲取
2、通過 Handler 的公有方法 handler.obtainMessage,實際上handler.obtainMessage內(nèi)部調(diào)用的也是Message.obtain的重載方法
15、Handler里面藏著的CallBack能做什么?
答: 利用此CallBack攔截Handler的消息處理
在上一篇中我們分析到,dispatchMessage方法的處理步驟:
1、首先,檢查Message的callback是否為null,不為null就通過handleCallBack來處理消息,Message的callback是一個Runnable對象,實際上就是Handler的post系列方法所傳遞的Runnable參數(shù)
2、其次,檢查Handler里面藏著的CallBack是否為null,不為null就調(diào)用mCallback的handleMessage方法來處理消息,并判斷其返回值:為true,那么 Handler 的 handleMessage(msg) 方法就不會被調(diào)用了;為false,那么就意味著一個消息可以同時被 Callback 以及 Handler 處理。
3、最后,調(diào)用Handler的handleMessage方法來處理消息
通過上面分析我們知道Handler處理消息的順序是:Message的Callback > Handler的Callback > Handler的handleMessage方法
使用場景: Hook ActivityThread.mH , 在 ActivityThread 中有個成員變量 mH ,它是個 Handler,又是個極其重要的類,幾乎所有的插件化框架都使用了這個方法。
16、Handler阻塞喚醒機(jī)制是怎么一回事?
答: Handler的阻塞喚醒機(jī)制是基于Linux的阻塞喚醒機(jī)制。
這個機(jī)制也是類似于handler機(jī)制的模式。在本地創(chuàng)建一個文件描述符,然后需要等待的一方則監(jiān)聽這個文件描述符,喚醒的一方只需要修改這個文件,那么等待的一方就會收到文件從而打破喚醒。和Looper監(jiān)聽MessageQueue,Handler添加message是比較類似的。具體的Linux層知識讀者可通過這篇文章詳細(xì)了解(傳送門)
17、什么是Handler的同步屏障?
答: 同步屏障是一種使得異步消息可以被更快處理的機(jī)制
18、能不能讓一個Message被加急處理?
答:可以,添加加同步屏障,并發(fā)送異步消息
19、什么是IdleHandler?
答: IdleHandler是MessageQueue中一個靜態(tài)函數(shù)型接口,它在主線程執(zhí)行完所有的View事務(wù)后,回調(diào)一些額外的操作,且不會阻塞主線程
總結(jié)
Handler消息機(jī)制在Android系統(tǒng)源碼中進(jìn)行了大量的使用,可以說是涉及了Android的方方面面,比如我們四大組件的啟動,Application的創(chuàng)建等等,學(xué)好Handler相關(guān)的知識,可以幫助我們更好的去閱讀Android源碼,而且Handler在我們?nèi)粘i_發(fā)中直接或間接的會被用到。同時通過對Handler源碼的學(xué)習(xí),讓我感受到了代碼設(shè)計的背后,蘊藏著工程師大量的智慧,心里直呼666,哈哈。
到了這里,關(guān)于Handler相關(guān)的知識就都講完了,如果你還有什么問題,評論區(qū)告訴我吧。
參考和推薦
Android全面解析之Handler機(jī)制(終篇):常見問題匯總
全文到此,原創(chuàng)不易,歡迎點贊,收藏,評論和轉(zhuǎn)發(fā),你的認(rèn)可是我創(chuàng)作的動力