破繭(二)Handler機(jī)制

經(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é)。

一、前言

本次參考了以下幾篇文章,感謝各位作者?。?!

你真的懂Handler嗎?Handler問(wèn)答

Handler用法及解析

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);
    }
}
  1. 外部類(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)存泄漏。
  2. 在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)存泄漏。

解決方案

  1. 將非靜態(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)引用。
  2. 改成靜態(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)題。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容