經(jīng)過(guò)上篇文章我發(fā)現(xiàn),自己寫(xiě)出一篇技術(shù)文章比看書(shū)、看視頻還要學(xué)習(xí)的更好、更快、更深刻,畢竟你沒(méi)有吃透這個(gè)技術(shù)點(diǎn),你就無(wú)法詳細(xì)有序的寫(xiě)出這個(gè)技術(shù)點(diǎn)的每一個(gè)環(huán)節(jié),所以要再接再厲!
這次重新深入了解一下常用的Handler機(jī)制;雖然常用,但也有許多不為人知的細(xì)節(jié)。
一、前言
本次參考了以下幾篇文章,感謝各位作者?。?!
Android基礎(chǔ)夯實(shí)--你了解Handler有多少?
二、了解Handler
在Android體系中,UI操作并不是線程安全的,并且這些操作必須在UI線程執(zhí)行,于是Android封裝了一套消息封裝、傳遞、處理機(jī)制,這就是Handler。Handler主要用于異步消息處理,涉及到的類(lèi)有:Looper、MessageQueue、Message。
Looper:每一個(gè)線程只有一個(gè)Looper,每個(gè)線程在初始化Looper之后,Looper會(huì)維護(hù)好該線程的消息隊(duì)列MessageQueue,用來(lái)存放Handler發(fā)送的Message;其中Loopler.looper方法是一個(gè)死循環(huán),不斷地從MessageQueue取消息,如果有消息就發(fā)給Handler處理消息,沒(méi)有消息就阻塞;
Looper的特點(diǎn)是它跟它的線程是綁定的,處理消息也是在Looper所在的線程去處理,所以當(dāng)我們?cè)谥骶€程創(chuàng)建Handler時(shí),它就會(huì)跟主線程唯一的Looper綁定,從而我們使用Handler在子線程發(fā)消息時(shí),最終也是在主線程處理,達(dá)到了異步的效果。MessageQueue:這是一個(gè)消息隊(duì)列,用來(lái)存放Handler發(fā)送的消息。每個(gè)線程最多只有一個(gè)MessageQueue。MessageQueue通常都是由Looper來(lái)管理,而主線程創(chuàng)建時(shí),會(huì)創(chuàng)建一個(gè)默認(rèn)的Looper對(duì)象,而Looper對(duì)象的創(chuàng)建,將自動(dòng)創(chuàng)建一個(gè)MessageQueue;其他非主線程,不會(huì)自動(dòng)創(chuàng)建Looper;列隊(duì)中的消息是根據(jù)消息的時(shí)間來(lái)排序的。
-
Message:消息對(duì)象,存放在MessageQueue中,一個(gè)MessageQueu可以包括多個(gè)Message;當(dāng)我們需要發(fā)送一個(gè)Message時(shí),不建議直接new Message(),Message內(nèi)部保存了一個(gè)緩存的消息池,用Message.obtain()的方式來(lái)從緩存池中獲取實(shí)例可以大大減少當(dāng)有大量Message對(duì)象而產(chǎn)生的垃圾回收問(wèn)題。
image.png
三、Handler發(fā)送消息的方法
//發(fā)送消息
new Handler().sendMessage(Message msg);
//延時(shí)發(fā)送消息(比如填1000表示延遲1秒)
new Handler().sendMessageDelayed(Message msg, long delayMillis);
//延時(shí)發(fā)送消息(第二個(gè)參數(shù)為:相對(duì)系統(tǒng)開(kāi)機(jī)時(shí)間的絕對(duì)時(shí)間,填SystemClock.uptimeMillis()+1000表示延遲一秒)
new Handler().sendMessageAtTime(Message msg, uptimeMillis);
//發(fā)送帶標(biāo)記的空消息(內(nèi)部創(chuàng)建了message,并設(shè)置msg.what);
new Handler().sendEmptyMessage(int what);
//延時(shí)發(fā)送帶標(biāo)記的延時(shí)空消息
new Handler().sendEmptyMessageDelayed(int what, long delayMillis);
new Handler().sendEmptyMessageAtTime(int what, long uptimeMillis);
//發(fā)送消息
new Handler().post(Runnable r);
//發(fā)送延時(shí)消息
new Handler().postDelayed(Runnable r, long delayMillis);
new Handler().postAtTime(Runnable r, long uptimeMillis);
- 問(wèn)題來(lái)了sendMessage和post有什么區(qū)別?
通過(guò)查看源碼能夠發(fā)現(xiàn),post的方法內(nèi)也是通過(guò)sendMessage來(lái)實(shí)現(xiàn)消息傳遞的,它是發(fā)送了一個(gè)沒(méi)有標(biāo)記的Message,簡(jiǎn)化了消息發(fā)送、處理的過(guò)程;當(dāng)我們需要通過(guò)標(biāo)識(shí)來(lái)執(zhí)行多個(gè)不同的動(dòng)作的時(shí)候,使用sendMessage,當(dāng)我們只用執(zhí)行一個(gè)動(dòng)作的時(shí)候使用post更加方便快捷。
四、如何在子線程中使用Handler
Handler的使用離不開(kāi)Looper,在Main線程中會(huì)自動(dòng)生成Looper,而子線程則需要自己新建Looper,并跟Handler綁定,才能正常使用Handler;
- 通過(guò)先調(diào)用 Looper.prepare() 在當(dāng)前線程初始化一個(gè) Looper
//初始化一個(gè)Looper
Looper.prepare();
Handler handler = new Handler();
// Handler調(diào)用完成后需要進(jìn)行這一步
Looper.loop();
- 也可以通過(guò)HandlerThread在子線程中使用Handler,HandlerThread自動(dòng)幫我們創(chuàng)建了Looper,通過(guò)getLooper()可以獲得Looper
HandlerThread downloadBThread = new HandlerThread("downloadBThread");
downloadBThread.start();
Handler downloadBHandler = new Handler(downloadBThread.getLooper());
// 通過(guò)postDelayed模擬耗時(shí)操作
downloadBHandler.postDelayed(new Runnable() {
@Override
public void run() {
Toast.makeText(getApplicationContext(), "下載B完成", Toast.LENGTH_SHORT).show();
mainHandler.post(new Runnable() {
@Override
public void run() {
tv_B.setText("B任務(wù)已經(jīng)下載完成");
}
});
}
}, 1000 * 7);
五、Handler的泄露事件
先來(lái)看看下面的代碼
public class MainActivity extends AppCompatActivity {
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
......
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//activity被執(zhí)行時(shí),被延遲的這個(gè)消息存于主線程消息隊(duì)列中1分鐘,
//此消息包含handler引用,而handler由匿名內(nèi)部類(lèi)創(chuàng)建,持有activity引用,
//activity便不能正常銷(xiāo)毀,從而泄露
handler.postDelayed(new Runnable() {
@Override
public void run() {
......
}
}, 1000 * 60);
}
}
- 外部類(lèi)Activity中定義了一個(gè)非靜態(tài)內(nèi)部類(lèi)Handler,非靜態(tài)內(nèi)部類(lèi)默認(rèn)持有對(duì)外部類(lèi)的引用。如果外部Activity突然關(guān)閉了,但是MessageQueue中的消息還沒(méi)處理完,那么Handler就會(huì)一直持有對(duì)外部Activty的引用,垃圾回收器無(wú)法回收Activity,從而導(dǎo)致內(nèi)存泄漏。
- 在postDelayed中,我們?cè)趨?shù)中傳入一個(gè)非靜態(tài)內(nèi)部類(lèi)Runnable,這同樣會(huì)造成內(nèi)存泄漏,假如此時(shí)關(guān)閉了Activity,那么垃圾回收器在接下來(lái)的1000000ms內(nèi)都無(wú)法回收Activity,造成內(nèi)存泄漏。
解決方案:
- 將非靜態(tài)內(nèi)部類(lèi)Handler和Runnable轉(zhuǎn)為靜態(tài)內(nèi)部類(lèi),因?yàn)榉庆o態(tài)內(nèi)部類(lèi)(匿名內(nèi)部類(lèi))都會(huì)默認(rèn)持有對(duì)外部類(lèi)的強(qiáng)引用。
- 改成靜態(tài)內(nèi)部類(lèi)后,對(duì)外部類(lèi)的引用設(shè)為弱引用,因?yàn)樵诶厥諘r(shí),會(huì)自動(dòng)將弱引用的對(duì)象回收。
避免內(nèi)存泄漏的例子:
public class MainActivity extends AppCompatActivity {
//創(chuàng)建靜態(tài)內(nèi)部類(lèi)
private static class MyHandler extends Handler{
//持有弱引用MainActivity,GC回收時(shí)會(huì)被回收掉.
private final WeakReference<MainActivity> mAct;
public MyHandler(MainActivity mainActivity){
mAct =new WeakReference<MainActivity>(mainActivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainAct=mAct.get();
super.handleMessage(msg);
if(mainAct!=null){
//執(zhí)行業(yè)務(wù)邏輯
}
}
}
private static final Runnable myRunnable = new Runnable() {
@Override
public void run() {
//執(zhí)行我們的業(yè)務(wù)邏輯
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyHandler myHandler=new MyHandler(this);
//延遲5分鐘后發(fā)送
myHandler.postDelayed(myRunnable, 1000 * 60 * 5);
}
}
六、Handler其他可能發(fā)生的錯(cuò)誤
- 錯(cuò)誤1:如果Activity被關(guān)閉,但是handler剛好還在處理消息,需要用的資源已被釋放,會(huì)出現(xiàn)空指針異常;需要在ondestory中去remove掉我們要處理的事件。
//避免內(nèi)存泄露的方法:
//移除標(biāo)記的消息
new Handler().removeMessages(int what);
//移除回調(diào)的消息
new Handler().removeCallbacks(Runnable runnable);
//移除回調(diào)和所有message
new Handler().removeCallbacksAndMessages(null);
- 錯(cuò)誤2:有時(shí)候:removeCallbacks會(huì)失效,不能從消息隊(duì)列中移除;出現(xiàn)這情況是activity切入后臺(tái),再回到前臺(tái),此時(shí)的runnable由于被重定義,和原先的runnable并非同一個(gè)對(duì)象;給runnable加上static可以解決這個(gè)問(wèn)題。
