前言
Handler系列文章共兩篇:
在上一篇中擦酌,我們對(duì)Handler的主體部分進(jìn)行了講解先口,今天,我們就來學(xué)習(xí)一下Handler相關(guān)的一些擴(kuò)展知識(shí)这敬,講完這些擴(kuò)展知識(shí)后戳葵,在來回答之前列出來的一系列問題
同步屏障
通過上一篇的學(xué)習(xí)筋量,我們知道: Handler發(fā)送的Message會(huì)放入到MessageQueue中植袍,MessageQueue中維護(hù)了一個(gè)優(yōu)先級(jí)隊(duì)列筋遭,優(yōu)先級(jí)隊(duì)列的意思就是將存儲(chǔ)數(shù)據(jù)的單鏈表按照時(shí)間升序進(jìn)行排序形成的打颤,Looper則按照順序,每次從這個(gè)優(yōu)先級(jí)隊(duì)列中取出一個(gè)Message進(jìn)行分發(fā)漓滔,一個(gè)處理完就處理下一個(gè)编饺。
那么問題來了:我能不能讓我的一個(gè)Message被優(yōu)先處理?
可以响驴,使用同步屏障
這里透且,我心里又會(huì)有個(gè)疑問,什么是同步屏障踏施?怎么使用同步屏障石蔗?同步屏障有啥作用?帶著這些疑問???畅形,我們來分析下源碼
先看下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耘纱、條件體里面有一行源碼注釋,翻譯過來就是: 被一個(gè)屏障給阻礙毕荐。在隊(duì)列中查找下一個(gè)異步消息
3束析、接下來就是一個(gè)循環(huán),遍歷找出一條異步消息憎亚,循環(huán)體里面就是鏈表相關(guān)的操作
這里大家是不是會(huì)有個(gè)疑問员寇?msg.target怎么可能會(huì)為空呢弄慰?之前發(fā)送消息的一系列方法不是都會(huì)給msg.target對(duì)象賦值嗎?
沒錯(cuò)蝶锋,我們?cè)诨仡櫼幌翲andler的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ā)送的消息陆爽,最終都會(huì)走到這個(gè)方法,msg.target都會(huì)被賦值扳缕,因此不可能為空慌闭。那msg.target啥時(shí)候會(huì)為空呢?我們推斷肯定是其他發(fā)送消息的方法使得msg.target為空躯舔,那我們就找一下驴剔,會(huì)發(fā)現(xiàn)MessageQueue的postSyncBarrier
的方法中沒有給msg.target對(duì)象賦值:
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;
}
}
上述代碼就是往消息隊(duì)列中合適的位置插入target屬性為null的Message
因此我們是不是可以知道,Message的target屬性為空和非空是很不一樣的庸毫,這里就不賣關(guān)子了仔拟,直接給結(jié)論: target屬性為空的Message就是同步屏障衫樊,他是一種特殊的消息飒赃,并不會(huì)被消費(fèi),僅僅是作為一個(gè)標(biāo)識(shí)處于 MessageQueue 中科侈,當(dāng)MessageQueue的next
方法遇到同步屏障的時(shí)候载佳,就會(huì)循環(huán)遍歷整個(gè)鏈表找到標(biāo)記為異步消息的Message,其他的消息會(huì)直接忽視臀栈,那么這樣異步消息就會(huì)提前被執(zhí)行了
現(xiàn)在我們現(xiàn)在就可以回答上面的問題了:target屬性為空的Message就是同步屏障蔫慧,同步屏障可以使得異步消息優(yōu)先被處理,通過MessageQueue的postSyncBarrier
可以添加一個(gè)同步屏障
注意: 在異步消息處理完之后权薯,同步屏障并不會(huì)被移除姑躲,需要我們手動(dòng)移除,從上面的源碼我們也可以看出盟蚣,如果不移除同步屏障黍析,那么他會(huì)一直在那里,這樣同步消息就永遠(yuǎn)無法被執(zhí)行了屎开。
因此我們?cè)谑褂猛晖狡琳虾蟛妫枰謩?dòng)移除,代碼如下:
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);
}
}
}
到這里我心里又有一個(gè)疑問了奄抽?怎么把一個(gè)消息變成異步消息呢蔼两?還是回到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额划,可以把一個(gè)消息變成異步消息,但是前提得滿足mAsynchronous屬性為true档泽,mAsynchronous是Handler中的一個(gè)屬性俊戳,他會(huì)在這兩個(gè)構(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é)論彬祖,把一個(gè)消息設(shè)置為異步消息,有兩種方式:
1品抽、在Handler的構(gòu)造方法中储笑,傳入async為true,那么這個(gè)時(shí)候發(fā)送的Message就都是異步的的消息
2圆恤、給Message通過setAsynchronous
方法標(biāo)志為異步
但是突倍,上面兩個(gè)構(gòu)造方法對(duì)外是不可見的,我們調(diào)用不到盆昙,而且設(shè)置同步屏障的方法對(duì)外也是不可見的羽历,說明谷歌不想要我們?nèi)ナ褂盟K赃@里同步屏障也是作為一個(gè)了解淡喜,一般只有系統(tǒng)會(huì)去使用它秕磷,例如:在進(jìn)行UI繪制的時(shí)候,以下是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);
}
}
上述代碼在把繪制消息放入隊(duì)列之前炼团,先放入了一個(gè)同步屏障澎嚣,然后在發(fā)送異步繪制消息,從而使得界面繪制的消息會(huì)比其他消息優(yōu)先執(zhí)行瘟芝,避免了因?yàn)?MessageQueue 中消息太多導(dǎo)致繪制消息被阻塞導(dǎo)致畫面卡頓易桃,當(dāng)繪制完成后,就會(huì)將同步屏障移除锌俱。
IdleHandler
見名知意晤郑,idle是空閑的意思,那么IdleHandler就是空閑的Handler贸宏,有點(diǎn)這個(gè)意思造寝,實(shí)際上它是MessageQueue中有一個(gè)靜態(tài)接口
public static interface IdleHandler {
boolean queueIdle();
}
可以看到它是一個(gè)單方法的接口,也可稱為函數(shù)型接口吭练,它的作用是:在UI線程處理完所有View事務(wù)后诫龙,回調(diào)一些額外的操作,且不會(huì)堵塞主進(jìn)程线脚;我們來實(shí)際操作一下
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: 空閑時(shí)做一些輕量級(jí)別");
return false;
}
});
}
}
//上面代碼會(huì)打印如下結(jié)果
queueIdle: 空閑時(shí)做一些輕量級(jí)別
接著進(jìn)行源碼分析赐稽,我們?cè)诳聪?code>addIdleHandler這個(gè)方法:
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姊舵,會(huì)發(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)前無消息,或還需要等待一段時(shí)間消息才能分發(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的時(shí)候抽诉,可能有新的消息已經(jīng)進(jìn)來了
// 所以這個(gè)時(shí)候不能阻塞,要回去循環(huán)一次看一下
nextPollTimeoutMillis = 0;
}
}
上述代碼解析:
1吐绵、當(dāng)調(diào)用next方法的時(shí)候迹淌,會(huì)將pendingIdleHandlerCount賦值為-1
2、判斷pendingIdleHandlerCount是否小于0并且MessageQueue 是否為空或者有延遲消息需要執(zhí)行己单,如果是則把存儲(chǔ)IdleHandler的list的長(zhǎng)度賦值給pendingIdleHandlerCount
3唉窃、判斷如果沒有IdleHandler需要執(zhí)行,阻塞線程進(jìn)入下次循環(huán)纹笼,如果有纹份,則初始化mPendingIdleHandlers,把list中的所有IdleHandler放到數(shù)組中廷痘。這一步是為了不讓在執(zhí)行IdleHandler的時(shí)候List被插入新的IdleHandler蔓涧,造成邏輯混亂
4、循環(huán)遍歷所有的IdleHandler并執(zhí)行牍疏,查看idler.queueIdle方法的返回值蠢笋,為false表明這個(gè)IdleHandler只需要執(zhí)行一次,并移除鳞陨,為true,則不移除
5瞻惋、將pendingIdleHandlerCount置為0避免下次再次執(zhí)行厦滤, 當(dāng)在執(zhí)行IdleHandler的時(shí)候,可能有新的消息已經(jīng)進(jìn)來了歼狼,所以這個(gè)時(shí)候不能阻塞掏导,要回去循環(huán)一次看一下
到這里同步屏障和IdleHandler都講完了,建議讀者配合完整的源碼在去仔細(xì)閱讀一次羽峰。
實(shí)際應(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還需要有一個(gè)Handler來和消息隊(duì)列進(jìn)行交互坯汤,這個(gè)Handler就是ActivityThread.H虐唠,它內(nèi)部定義了很多的消息類型,例如四大組件的啟動(dòng)惰聂,Application的啟動(dòng)等等
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的實(shí)際應(yīng)用疆偿,我們?cè)诳?a target="_blank">Activity的啟動(dòng)流程可能會(huì)有比較深入的理解咱筛,ActivityThread通過ApplicationThread和AMS進(jìn)行進(jìn)程間通信的方式完成ActivityThread的請(qǐng)求后,會(huì)回調(diào)ApplicationThread中的Binder方法杆故,然后ApplicationThread會(huì)向H發(fā)送消息迅箩,H收到消息后會(huì)將ApplicationThread中的邏輯切換到ActivityThread中去執(zhí)行,即切換到主線程去執(zhí)行处铛,這個(gè)過程就是主線程的消息循環(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)注意事項(xiàng)
我們通常使用Handler都是從子線程發(fā)送消息到主線程去處理篙贸,那么這里我們嘗試一下從主線程發(fā)送消息到子線程來處理,上代碼:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//創(chuàng)建線程實(shí)例并開啟
MyThread myThread = new MyThread();
myThread.start();
//打開這段注釋就不會(huì)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;
}
}
}
運(yùn)行一下上述代碼爵川,發(fā)現(xiàn)會(huì)Crash,如下圖:
報(bào)了一個(gè)空指針異常息楔,原因就是多線程并發(fā)寝贡,當(dāng)主線程執(zhí)行到sendEnptyMessage時(shí),子線程的Handler還沒有創(chuàng)建值依。因此我們可以在獲取Handler的時(shí)候讓主線程休眠一下在執(zhí)行圃泡,應(yīng)用就不會(huì)Crash了,打開上面代碼的注釋即可
值得注意的是:我們自己創(chuàng)建的Looper在使用完畢后應(yīng)該調(diào)用quit
方法來終止消息循環(huán)愿险,如果不退出的話颇蜡,那么該線程的Looper處理完所有的消息后,就會(huì)處于一個(gè)阻塞狀態(tài)辆亏,要知道線程是比較重量級(jí)的风秤,如果一直存在,肯定會(huì)對(duì)應(yīng)用性能造成一定的影響扮叨。而如果退出Looper缤弦,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper彻磁。
因此在子線程使用Handler碍沐,我們需要注意一下兩點(diǎn):
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ā)送消息到子線程來處理,其實(shí)Android已經(jīng)給我們提供了一個(gè)這樣輕量級(jí)的異步類恍箭,那就是HandlerThread
HandlerThread的實(shí)現(xiàn)原理也比較簡(jiǎn)單:繼承Thread并對(duì)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實(shí)例
HandlerThread mHandlerThread = new HandlerThread("HandlerThread");
//2鞍匾,啟動(dòng)線程
mHandlerThread.start();
//3,使用傳入Looper為參數(shù)的構(gòu)造方法創(chuàng)建Handler實(shí)例
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橡淑,在合適的時(shí)機(jī)調(diào)用HandlerThread的quit方法,退出消息循環(huán)
}
}
//上述代碼打印結(jié)果:
當(dāng)前線程: HandlerThread handleMessage
Handler HandlerThread Thread三者區(qū)別
Handler:在Android中負(fù)責(zé)發(fā)送和處理消息
HandlerThread:繼承自Thread咆爽,對(duì)Looper進(jìn)行了封裝梁棠,也就是說它在子線程維護(hù)了一個(gè)Looper,方便我們?cè)谧泳€程中去處理消息
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代碼運(yùn)行會(huì)奔潰漾稀,會(huì)報(bào)這么一個(gè)異常提示:"Can't toast on a thread that has not called Looper.prepare()"
原因就是Toast的實(shí)現(xiàn)也是依賴Handler模闲,而我們知道在子線程中創(chuàng)建Handler,需先創(chuàng)建Looper并開啟消息循環(huán)崭捍,這點(diǎn)在Toast中的源碼也有體現(xiàn)尸折,如下圖:
因此我們?cè)谧泳€程創(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差不多,這里貼出正確的代碼示例殷蛇,它的實(shí)現(xiàn)也是依賴Handler实夹,我們?cè)谒脑创a中可以看到:
private final Handler mHandler = new Handler();
他直接就new了一個(gè)Handler實(shí)例,我們知道晾咪,創(chuàng)建Handler收擦,需要先創(chuàng)建Looper并開啟消息循環(huán),主線程中已經(jīng)給我們創(chuàng)建并開啟消息循環(huán)谍倦,而子線程中并沒有,如果不創(chuàng)建那就會(huì)報(bào)這句經(jīng)典的異常提示:"Can't create handler inside thread that has not called Looper.prepare() "泪勒,因此在子線程中昼蛀,需要我們手動(dòng)去創(chuàng)建并開啟消息循環(huán)
到這里,Handler相關(guān)的擴(kuò)展知識(shí)就全部講完了圆存,我們會(huì)發(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)行線程之間的切換涧窒,是因?yàn)槭褂昧瞬煌€程的Looper處理消息
2心肪、Handler能夠按照順序處理消息,避免并發(fā)纠吴,是因?yàn)橄⒃谌腙?duì)的時(shí)候會(huì)按照時(shí)間升序?qū)Ξ?dāng)前鏈表進(jìn)行排序硬鞍,Looper讀取的時(shí)候,MessageQueue的next
方法會(huì)循環(huán)加鎖呜象,同時(shí)配合阻塞喚醒機(jī)制
3膳凝、Handler能夠阻塞線程主要是基于Linux的epoll機(jī)制實(shí)現(xiàn)的
4、Handler能夠處理延遲消息恭陡,是因?yàn)镸essageQueue的next
方法中會(huì)拿當(dāng)前消息時(shí)間和當(dāng)前時(shí)間做比較蹬音,如果是延遲消息,那么就會(huì)阻塞當(dāng)前線程休玩,等阻塞時(shí)間到著淆,在執(zhí)行該消息
2、為什么我們能在主線程直接使用Handler拴疤,而不需要?jiǎng)?chuàng)建Looper永部?
答:主線程已經(jīng)創(chuàng)建了Looper,并開啟了消息循環(huán)
3呐矾、如果想要在子線程創(chuàng)建Handler苔埋,需要做什么準(zhǔn)備?
答:需要先創(chuàng)建Looper蜒犯,并開啟消息循環(huán)
4组橄、一個(gè)線程有幾個(gè)Handler?
答:可以有任意多個(gè)
5罚随、一個(gè)線程有幾個(gè)Looper玉工?如何保證?
答:一個(gè)線程只有一個(gè)Looper淘菩,通過ThreadLocal來保證
6遵班、Handler發(fā)送消息的時(shí)候,時(shí)間為啥要取SystemClock.uptimeMillis() + delayMillis,可以把SystemClock.uptimeMillis() 換成System.currentTimeMillis()嗎狭郑?
答:不可以
SystemClock.uptimeMillis() 這個(gè)方法獲取的時(shí)間腹暖,是自系統(tǒng)開機(jī)到現(xiàn)在的一個(gè)毫秒數(shù),這個(gè)時(shí)間是個(gè)相對(duì)的
System.currentTimeMillis() 這個(gè)方法獲取的是自1970-01-01 00:00:00 到現(xiàn)在的一個(gè)毫秒數(shù)愿阐,這是一個(gè)和系統(tǒng)強(qiáng)關(guān)聯(lián)的時(shí)間微服,而且這個(gè)值可以做修改
1、使用System.currentTimeMillis()可能會(huì)導(dǎo)致延遲消息失效
2缨历、最終這個(gè)時(shí)間會(huì)被設(shè)置到Message的when屬性以蕴,而Message的when屬性只是需要一個(gè)時(shí)間差來表示消息的先后順序,使用一個(gè)相對(duì)時(shí)間就行了辛孵,沒必要使用一個(gè)絕對(duì)時(shí)間
7丛肮、為什么Looper死循環(huán),卻不會(huì)導(dǎo)致應(yīng)用卡死魄缚?
答:因?yàn)楫?dāng)Looper處理完所有消息的時(shí)候宝与,會(huì)調(diào)用Linux的epoll機(jī)制進(jìn)入到阻塞狀態(tài),當(dāng)有新的Message進(jìn)來的時(shí)候會(huì)打破阻塞繼續(xù)執(zhí)行冶匹。
應(yīng)用卡死即ANR: 全稱Applicationn Not Responding习劫,中文意思是應(yīng)用無響應(yīng),當(dāng)我發(fā)送一個(gè)消息到主線程嚼隘,Handler經(jīng)過一定時(shí)間沒有執(zhí)行完這條消息诽里,那么這個(gè)時(shí)候就會(huì)拋出ANR異常
Looper死循環(huán): 循環(huán)執(zhí)行各種事務(wù),Looper死循環(huán)說明線程還活著飞蛹,如果沒有Looper死循環(huán)谤狡,線程結(jié)束,應(yīng)用就退出了卧檐,當(dāng)Looper處理完所有消息的時(shí)候會(huì)調(diào)用Linux的epoll機(jī)制進(jìn)入到阻塞狀態(tài)墓懂,當(dāng)有新的Message進(jìn)來的時(shí)候會(huì)打破阻塞繼續(xù)執(zhí)行
8、Handler內(nèi)存泄露原因? 如何解決霉囚?
內(nèi)存泄漏的本質(zhì)是長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的引用捕仔,導(dǎo)致短生命周期的對(duì)象無法被回收,從而導(dǎo)致了內(nèi)存泄漏
下面我們就看個(gè)導(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ā)送一個(gè)延遲消息盈罐,10分鐘后在執(zhí)行
mHandler.sendEmptyMessageDelayed(0x001,10*60*1000);
}
}
上述代碼:
1逻澳、我們通過匿名內(nèi)部類的方式創(chuàng)建了一個(gè)Handler的實(shí)例
2、在onCreate
方法里面通過Handler實(shí)例發(fā)送了一個(gè)延遲10分鐘執(zhí)行的消息
我們發(fā)送的這個(gè)延遲10分鐘執(zhí)行的消息它是持有Handler的引用的暖呕,根據(jù)Java特性我們又知道,非靜態(tài)內(nèi)部類會(huì)持有外部類的引用苞氮,因此當(dāng)前Handler又持有Activity的引用湾揽,而Message又存在MessageQueue中,MessageQueue又在當(dāng)前線程中,因此會(huì)存在一個(gè)引用鏈關(guān)系:
當(dāng)前線程->MessageQueue->Message->Handler->Activity
因此當(dāng)我們退出Activity的時(shí)候库物,由于消息需要在10分鐘后在執(zhí)行霸旗,因此會(huì)一直持有Activity,從而導(dǎo)致了Activity的內(nèi)存泄漏
通過上面分析我們知道了內(nèi)存泄漏的原因就是持有了Activity的引用戚揭,那我們是不是會(huì)想诱告,切斷這條引用,那么如果我們需要用到Activity相關(guān)的屬性和方法采用弱引用的方式不就可以了么民晒?我們實(shí)際操作一下精居,把Handler寫成一個(gè)靜態(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ā)送一個(gè)延遲消息,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定義成了一個(gè)靜態(tài)內(nèi)部類靴姿,并持有當(dāng)前Activity的弱引用,弱引用會(huì)在Java虛擬機(jī)發(fā)生gc的時(shí)候把對(duì)象給回收掉
經(jīng)過上述改造磁滚,我們解決了Activity的內(nèi)存泄漏佛吓,此時(shí)的引用鏈關(guān)系為:
當(dāng)前線程->MessageQueue->Message->Handler
我們會(huì)發(fā)現(xiàn)Message還是會(huì)持有Handler的引用,從而導(dǎo)致Handler也會(huì)內(nèi)存泄漏垂攘,所以我們應(yīng)該在Activity銷毀的時(shí)候维雇,在他的生命周期方法里,把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ā)送一個(gè)延遲消息吱型,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)用生命周期是一樣長(zhǎng)的仪芒,因此也就不存在內(nèi)存泄漏了唁影,完美。
所以解決Handler內(nèi)存泄漏最好的方式就是:將Handler定義成靜態(tài)內(nèi)部類掂名,內(nèi)部持有Activity的弱引用据沈,并在Activity銷毀的時(shí)候移除所有消息
9、線程維護(hù)的Looper饺蔑,在消息隊(duì)列無消息時(shí)的處理方案是什么锌介?有什么用?
答:當(dāng)消息隊(duì)列無消息時(shí)猾警,Looper會(huì)阻塞當(dāng)前線程孔祸,釋放cpu資源,提高App性能
我們知道Looper的loop
方法中有個(gè)死循環(huán)一直在讀取MessageQueue中的消息发皿,其實(shí)是調(diào)用了MessageQueue中的next
方法崔慧,這個(gè)方法會(huì)在無消息時(shí),調(diào)用Linux的epoll機(jī)制穴墅,使得線程進(jìn)入阻塞狀態(tài)惶室,當(dāng)有新消息到來時(shí)温自,就會(huì)將它喚醒,next方法里會(huì)判斷當(dāng)前消息是否是延遲消息皇钞,如果是則阻塞線程悼泌,如果不是,則會(huì)返回這條消息并將其從優(yōu)先級(jí)隊(duì)列中給移除
10夹界、MessageQueue什么情況下會(huì)被喚醒馆里?
答:需要分情況
1、發(fā)送消息過來可柿,此時(shí)MessageQueue中無消息或者當(dāng)前發(fā)送過來的消息攜帶的when為0或者有延遲執(zhí)行的消息鸠踪,那么需要喚醒
2、當(dāng)遇到同步屏障且當(dāng)前發(fā)送過來的消息為異步消息趾痘,判斷該異步消息是否插入在所有異步消息的隊(duì)首慢哈,如果是則需要喚醒,如果不是永票,則不喚醒
11卵贱、線程什么情況下會(huì)被阻塞?
答:分情況
1侣集、當(dāng)MessageQueue中沒有消息的時(shí)候键俱,這個(gè)時(shí)候會(huì)無限阻塞,
2世分、當(dāng)前MessageQueue中全部是延遲消息编振,阻塞時(shí)間為(當(dāng)前延遲消息時(shí)間 - 當(dāng)前時(shí)間),如果這個(gè)阻塞時(shí)間超過來Integer類型的最大值臭埋,則取Integer類型的最大值
12踪央、我們可以使用多個(gè)Handler往消息隊(duì)列中添加數(shù)據(jù),那么可能存在發(fā)消息的Handler存在不同的線程瓢阴,那么Handler是如何保證MessageQueue并發(fā)訪問安全的呢畅蹂?
答:循環(huán)加鎖,配合阻塞喚醒機(jī)制
我們可以發(fā)現(xiàn)MessageQueue其實(shí)是“生產(chǎn)者-消費(fèi)者”模型荣恐,Handler不斷地放入消息液斜,Looper不斷地取出,這就涉及到死鎖問題叠穆。如果Looper拿到鎖少漆,但是隊(duì)列中沒有消息,就會(huì)一直等待硼被,而Handler需要把消息放進(jìn)去示损,鎖卻被Looper拿著無法入隊(duì),這就造成了死鎖嚷硫。Handler機(jī)制的解決方法是循環(huán)加鎖屎媳。在MessageQueue的next方法中:
Message next() {
...
for (;;) {
...
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
}
}
}
我們可以看到他的等待是在鎖外的夺溢,當(dāng)隊(duì)列中沒有消息的時(shí)候,他會(huì)先釋放鎖烛谊,再進(jìn)行等待,直到被喚醒嘉汰。這樣就不會(huì)造成死鎖問題了丹禀。
那在入隊(duì)的時(shí)候會(huì)不會(huì)因?yàn)殛?duì)列已經(jīng)滿了然后一邊在等待消息處理一邊拿著鎖呢?這一點(diǎn)不同的是MessageQueue的消息沒有上限鞋怀,或者說他的上限就是JVM給程序分配的內(nèi)存双泪,如果超出內(nèi)存會(huì)拋出異常,但一般情況下是不會(huì)的密似。
13焙矛、Handler是如何進(jìn)行線程切換的呢?
答:使用不同線程的Looper處理消息
我們通常處理消息是在Handler的handleMessage
方法中残腌,那么這個(gè)方法是在哪里回調(diào)的呢村斟?看下面這段代碼
public static void loop() {
//開啟死循環(huán)讀取消息
for (;;) {
// 調(diào)用Message對(duì)應(yīng)的Handler處理消息
msg.target.dispatchMessage(msg);
}
}
上述代碼中msg.target
其實(shí)就是我們發(fā)送消息的Handler,因此他會(huì)回調(diào)Handler的dispatchMessage
方法抛猫,而dispatchMessage
這個(gè)方法我們?cè)谏弦黄兄攸c(diǎn)分析過蟆盹,其中有一部分邏輯就是會(huì)回調(diào)到Handler的handleMessage
方法,我們還可以發(fā)現(xiàn)闺金,Handler的handleMessage
方法所在的線程是由Looper的loop
方法決定的逾滥。平時(shí)我們使用的時(shí)候,是從異步線程發(fā)送消息到 Handler败匹,而這個(gè) Handler 的 handleMessage()
方法是在主線程調(diào)用的寨昙,因?yàn)長(zhǎng)ooper是在主線程創(chuàng)建的,所以消息就從異步線程切換到了主線程掀亩。
14舔哪、我們?cè)谑褂肕essage的時(shí)候,應(yīng)該如何去創(chuàng)建它归榕?
答:Android 給 Message 設(shè)計(jì)了回收機(jī)制尸红,官方建議是通過Message.obtain
方法來獲取,而不是直接new一個(gè)新的對(duì)象刹泄,所以我們?cè)谑褂玫臅r(shí)候應(yīng)盡量復(fù)用 Message 外里,減少內(nèi)存消耗,方式有二:
1特石、調(diào)用 Message 的一系列靜態(tài)重載方法 Message.obtain
獲取
2盅蝗、通過 Handler 的公有方法 handler.obtainMessage
,實(shí)際上handler.obtainMessage
內(nèi)部調(diào)用的也是Message.obtain
的重載方法
15姆蘸、Handler里面藏著的CallBack能做什么墩莫?
答: 利用此CallBack攔截Handler的消息處理
在上一篇中我們分析到芙委,dispatchMessage
方法的處理步驟:
1、首先狂秦,檢查Message的callback是否為null灌侣,不為null就通過handleCallBack
來處理消息,Message的callback是一個(gè)Runnable對(duì)象裂问,實(shí)際上就是Handler的post
系列方法所傳遞的Runnable參數(shù)
2侧啼、其次,檢查Handler里面藏著的CallBack是否為null堪簿,不為null就調(diào)用mCallback的handleMessage
方法來處理消息痊乾,并判斷其返回值:為true,那么 Handler 的 handleMessage(msg)
方法就不會(huì)被調(diào)用了椭更;為false哪审,那么就意味著一個(gè)消息可以同時(shí)被 Callback 以及 Handler 處理。
3虑瀑、最后湿滓,調(diào)用Handler的handleMessage
方法來處理消息
通過上面分析我們知道Handler處理消息的順序是:Message的Callback > Handler的Callback > Handler的handleMessage
方法
使用場(chǎng)景: Hook ActivityThread.mH , 在 ActivityThread 中有個(gè)成員變量 mH
缴川,它是個(gè) Handler茉稠,又是個(gè)極其重要的類,幾乎所有的插件化框架都使用了這個(gè)方法把夸。
16而线、Handler阻塞喚醒機(jī)制是怎么一回事?
答: Handler的阻塞喚醒機(jī)制是基于Linux的阻塞喚醒機(jī)制恋日。
這個(gè)機(jī)制也是類似于handler機(jī)制的模式膀篮。在本地創(chuàng)建一個(gè)文件描述符,然后需要等待的一方則監(jiān)聽這個(gè)文件描述符岂膳,喚醒的一方只需要修改這個(gè)文件誓竿,那么等待的一方就會(huì)收到文件從而打破喚醒。和Looper監(jiān)聽MessageQueue谈截,Handler添加message是比較類似的筷屡。具體的Linux層知識(shí)讀者可通過這篇文章詳細(xì)了解(傳送門)
17、什么是Handler的同步屏障簸喂?
答: 同步屏障是一種使得異步消息可以被更快處理的機(jī)制
18毙死、能不能讓一個(gè)Message被加急處理?
答:可以喻鳄,添加加同步屏障扼倘,并發(fā)送異步消息
19、什么是IdleHandler除呵?
答: IdleHandler是MessageQueue中一個(gè)靜態(tài)函數(shù)型接口再菊,它在主線程執(zhí)行完所有的View事務(wù)后爪喘,回調(diào)一些額外的操作,且不會(huì)阻塞主線程
總結(jié)
Handler消息機(jī)制在Android系統(tǒng)源碼中進(jìn)行了大量的使用纠拔,可以說是涉及了Android的方方面面秉剑,比如我們四大組件的啟動(dòng),Application的創(chuàng)建等等绿语,學(xué)好Handler相關(guān)的知識(shí)秃症,可以幫助我們更好的去閱讀Android源碼,而且Handler在我們?nèi)粘i_發(fā)中直接或間接的會(huì)被用到吕粹。同時(shí)通過對(duì)Handler源碼的學(xué)習(xí),讓我感受到了代碼設(shè)計(jì)的背后岗仑,蘊(yùn)藏著工程師大量的智慧匹耕,心里直呼666,哈哈荠雕。
到了這里稳其,關(guān)于Handler相關(guān)的知識(shí)就都講完了,如果你還有什么問題炸卑,評(píng)論區(qū)告訴我吧既鞠。
參考和推薦
Android全面解析之Handler機(jī)制(終篇):常見問題匯總
全文到此五续,原創(chuàng)不易洒敏,歡迎點(diǎn)贊,收藏疙驾,評(píng)論和轉(zhuǎn)發(fā)凶伙,你的認(rèn)可是我創(chuàng)作的動(dòng)力