前言#
首先必須要聲明中文中間插入英文不是我的愛(ài)好强窖,這一點(diǎn)別吐槽我,因?yàn)橛行﹩卧~我想不到特別準(zhǔn)備的翻譯游沿,就跟人家說(shuō)讀英文書(shū)和讀翻譯書(shū)完全是兩個(gè)感覺(jué)饰抒,重點(diǎn)是意會(huì),你懂得诀黍。
上次聊了聊inflater袋坑,這次來(lái)聊聊Handler,他倆絕對(duì)是我們最常用的左青龍眯勾,右白虎枣宫,屌爆了。
正文#
<h2>Handler 的作用</h2>
再熟悉的東西都要簡(jiǎn)單介紹一下吃环,都是一種禮貌和尊敬也颤,Handler主要是為我們提供子線程和UI線程之間進(jìn)行操作上的切換,還提供了延時(shí)任務(wù)郁轻、定時(shí)功能等非常強(qiáng)大的功能翅娶,他還有兩個(gè)好基友Looper 和MessageQueue,幫助他去實(shí)現(xiàn)上面的功能,這些知識(shí)大家都懂故觅,面試必考內(nèi)容就不多說(shuō)了厂庇,感興趣的朋友可以自己去查看一下相關(guān)資料渠啊。
<h2>This Handler class should be static or leaks might occur (anonymous android.os.Handler) less... </h2>
這個(gè)熟悉的警告你還記得嗎输吏,說(shuō)不得的人肯定是沒(méi)走心,尤其是寫(xiě)過(guò)下面這段代碼的人:
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.e("lzp", "我是一個(gè)屌爆了的handler" );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
相信很多人還保持著這種寫(xiě)法替蛉,對(duì)于右側(cè)提出的warning視而不見(jiàn)贯溅,覺(jué)得無(wú)傷大雅,反正是警告躲查,編譯運(yùn)行都很完美它浅,例如我之前就是這樣的,那么我希望你能夠自我批評(píng)一下镣煮。
這段警告大概就是這個(gè)意思:
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.
由于Handlder被聲明為內(nèi)部類姐霍,他可能阻止外部類被垃圾回收。如果Handler是采用新的Looper 或 MessageQueue典唇,而不是主線程镊折,那么就沒(méi)有問(wèn)題。如果Handlder是采用主線程的Looper 或 MessageQueue介衔,你需要修正這個(gè)Handler的聲明如下:聲明Handler作為一個(gè)靜態(tài)類恨胚;在外部類中,實(shí)例化Hander時(shí)炎咖,在他的內(nèi)部也實(shí)例化一個(gè)WeakReference(弱引用)赃泡,并傳入他自己;確保這個(gè)外部類中的所有成員都使用WeakReference(弱引用)中的這個(gè)對(duì)象乘盼。
這段翻譯是我自己翻譯的升熊,感慨我的英文水平屌爆了的同時(shí),也直接明白了這段警告的意思:
要么給Handler創(chuàng)建一個(gè)新的Handler绸栅,要不就使用靜態(tài)類级野,并且弱引用,防止對(duì)垃圾回收進(jìn)行影響阴幌。
那直接創(chuàng)建一個(gè)新的Looper 不就完事了嗎勺阐?很遺憾,Looper的構(gòu)造方法是私有的()矛双,我們無(wú)法去手動(dòng)創(chuàng)建一個(gè)新的Looper:
看的出來(lái)Looper創(chuàng)建的同時(shí)渊抽,也同時(shí)創(chuàng)建了MessageQueue,而且綁定了相應(yīng)的線程议忽。
為什么不把構(gòu)造方法公開(kāi)呢懒闷?我個(gè)人覺(jué)得可能是設(shè)計(jì)者不希望開(kāi)發(fā)者濫用Looper。因?yàn)長(zhǎng)ooper涉及到跨線程的問(wèn)題,大家都懂的跨線程需要考慮很到安全操作愤估,就顯得比較棘手帮辟,所以開(kāi)放了一個(gè)靜態(tài)方法:Looper.prepare() 和 Looper.loop()配合使用,并且Handler已經(jīng)滿足了大部分應(yīng)用場(chǎng)景玩焰,估計(jì)是想讓開(kāi)發(fā)者盡量用Handler吧由驹,我就是小小的意淫一下設(shè)計(jì)者的想法。
在傷心和痛苦中突然發(fā)現(xiàn)Looper.myLooper()方法昔园,好像抓住了一個(gè)救命稻草蔓榄,于是我充滿期待的做了下面的測(cè)試:
public class MainActivity extends AppCompatActivity {
private Handler handler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg) {
Log.e("lzp", "我是一個(gè)屌爆了的handler" + getLooper().hashCode() );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.sendEmptyMessage(0);
Log.e("lzp", "UI handler:" + Looper.myLooper().hashCode() );
}
}
好尷尬,和UI線程是一樣的默刚,仔細(xì)一想很容易理解甥郑,本身我傳進(jìn)去的就是UI線程的Looper,得到肯定還是UI線程的Looper荤西。
Looper.myLooper()返回的是當(dāng)前線程的Looper澜搅,如果你是在線程中創(chuàng)建了looper,返回的就是這個(gè)線程的Looper邪锌,例如我運(yùn)行了下面的代碼:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.e("lzp", "Main Looper:" + Looper.myLooper().hashCode());
new Thread(){
@Override
public void run() {
Looper.prepare();
Toast.makeText(MainActivity.this, "屌爆了哦~", Toast.LENGTH_SHORT).show();
Log.e("lzp", "Thread Looper:" + Looper.myLooper().hashCode());
Looper.loop();
}
}.start();
}
打印的Log:
果然hashcode不一樣勉躺,說(shuō)明已經(jīng)是不同的Looper了。
順便貼一下Looper.prepare()的源碼:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
...
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Looper.prepare()先從ThreadLocal(有點(diǎn)類似于線程池秃流,里面的線程可以復(fù)用)中找到對(duì)應(yīng)的Looper赂蕴,沒(méi)有就創(chuàng)建一個(gè)新的Looper并放入ThreadLocal,對(duì)ThreadLocal不了解的朋友可以自己去百度舶胀。
那就沒(méi)辦法了概说,只能去研究第二種方法了:靜態(tài)類,弱引用嚣伐。
廢話沒(méi)有糖赔,直接看代碼:
public class MainActivity extends AppCompatActivity {
private TestHandler handler = new TestHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.postDelayed(new Runnable() {
@Override
public void run() {
}
}, 2000);
}
private void handleMessage(Message msg){
Toast.makeText(this, "hahaha", Toast.LENGTH_LONG).show();
}
static class TestHandler extends Handler{
private WeakReference<Activity> mActivity;
TestHandler(Activity activity){
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) mActivity.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
}
剛才扯了那么多高大上的蛋,一看代碼大概就明白差不多的意思了:
靜態(tài)類不會(huì)和外部類直接引用(就是強(qiáng)引用)轩端,并且TestHandler和activity 的關(guān)系是弱引用放典,所以就不會(huì)對(duì)外部類造成垃圾回收上的影響。
上面這種寫(xiě)法是比較流行的寫(xiě)法基茵,因?yàn)殪o態(tài)類無(wú)法直接聲明奋构,所以只能用靜態(tài)內(nèi)部類。我是把TestHandler寫(xiě)到BaseActivity中拱层,并且在BaseActivity中設(shè)置一個(gè)方法handlerMessage()弥臼,給TestHandler調(diào)用,我會(huì)把自己的用法放在最后供大家下載參考根灯。
<h2>Handler使用不當(dāng)径缅,會(huì)出現(xiàn)什么問(wèn)題掺栅?</h2>
說(shuō)了這么多,寫(xiě)了這么多纳猪,到底Handler使用不當(dāng)會(huì)出現(xiàn)啥問(wèn)題把跷浴?
其實(shí)主要是內(nèi)存上的問(wèn)題氏堤,如果僅僅是更新了UI界面沙绝,那其實(shí)是無(wú)所謂的,但是誰(shuí)也不會(huì)為了更新UI還特地去使用Handler丽猬,直接UI線程就好了宿饱。
問(wèn)題就是出線程上,大部分都是配合Thread使用脚祟,下面舉幾個(gè)例子:
1、網(wǎng)絡(luò)請(qǐng)求或者是耗時(shí)任務(wù)强饮,成功或者失敗更新UI由桌,但是還沒(méi)完成,Activity被銷毀了邮丰;
2行您、有一個(gè)延時(shí)/定時(shí)任務(wù),還沒(méi)有觸發(fā)剪廉,Activity銷毀了娃循,并且沒(méi)有removeCallback。
這兩個(gè)情況非常常見(jiàn)斗蒋,Handler保持著對(duì)Activity的強(qiáng)引用捌斧,在任務(wù)完成之前,是無(wú)法被回收的泉沾,這就可能出現(xiàn)內(nèi)存溢出等情況捞蚂,并且Activity被回收再去更新UI很明顯是沒(méi)有意義的。
總結(jié)#
又到了總結(jié)的時(shí)間了跷究,Handler最推薦的使用方法就結(jié)束了姓迅,可能過(guò)多的新技術(shù)新花樣吸引了我們太多的目光,而忽略了不斷的扎實(shí)自己的基礎(chǔ)俊马,所以在不停學(xué)習(xí)新招式的時(shí)候丁存,別忘了練習(xí)和加強(qiáng)我們的基本功。
到此結(jié)束柴我,有問(wèn)題或者有講解有誤的地方歡迎批評(píng)指正解寝,拜拜~