糾正編碼錯(cuò)誤篇 (2)Handler的Warning你解決了嗎

前言#

首先必須要聲明中文中間插入英文不是我的愛(à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:

這里寫(xiě)圖片描述

看的出來(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() );
    }
}
這里寫(xiě)圖片描述

好尷尬,和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:

這里寫(xiě)圖片描述

果然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)指正解寝,拜拜~

Demo點(diǎn)擊下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屯换,隨后出現(xiàn)的幾起案子编丘,更是在濱河造成了極大的恐慌与学,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘉抓,死亡現(xiàn)場(chǎng)離奇詭異索守,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)抑片,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門卵佛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人敞斋,你說(shuō)我怎么就攤上這事截汪。” “怎么了植捎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵衙解,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我焰枢,道長(zhǎng)蚓峦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任济锄,我火速辦了婚禮暑椰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荐绝。我一直安慰自己一汽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布低滩。 她就那樣靜靜地躺著召夹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪委造。 梳的紋絲不亂的頭發(fā)上戳鹅,一...
    開(kāi)封第一講書(shū)人閱讀 51,578評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音昏兆,去河邊找鬼枫虏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛爬虱,可吹牛的內(nèi)容都是我干的隶债。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼跑筝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼死讹!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起曲梗,我...
    開(kāi)封第一講書(shū)人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赞警,失蹤者是張志新(化名)和其女友劉穎妓忍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體愧旦,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡世剖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笤虫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旁瘫。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖琼蚯,靈堂內(nèi)的尸體忽然破棺而出酬凳,到底是詐尸還是另有隱情,我是刑警寧澤遭庶,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布宁仔,位于F島的核電站,受9級(jí)特大地震影響罚拟,放射性物質(zhì)發(fā)生泄漏台诗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一赐俗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弊知,春花似錦阻逮、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至漫雷,卻和暖如春瓜富,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背降盹。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工与柑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蓄坏。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓价捧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親涡戳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子结蟋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容