Handler機(jī)制詳解

ITJL博客

一效览、什么是Handler機(jī)制

定義:

Handler是用來(lái)結(jié)合線程的消息隊(duì)列來(lái)發(fā)送免胃、處理"Message對(duì)象"和"Runnable對(duì)象"的工具。

通常的來(lái)說(shuō),就是我們?cè)诰€程之間處理消息通知及任務(wù)調(diào)度的工具。

二胯杭、Handler機(jī)制原理

先捋一遍源碼,畫(huà)個(gè)時(shí)序圖受啥,然后等下逐一說(shuō)明做个。

image-20210115163424214
  • 相關(guān)類(lèi)

    Handler機(jī)制的實(shí)現(xiàn)離不開(kāi)與之相關(guān)的其他三個(gè)類(lèi),Message是Handler發(fā)送的消息實(shí)體腔呜,大部分的消息都是通過(guò)Message來(lái)封裝傳遞的叁温;MessageQueue是用來(lái)將消息按順序排隊(duì)的隊(duì)列;Looper本質(zhì)就是一個(gè)循環(huán)核畴,不停的從MessageQueue中取出消息然后處理膝但。

  • 執(zhí)行過(guò)程

    首先,如上圖所示任務(wù)的開(kāi)始是由創(chuàng)建一個(gè)Message開(kāi)始的谤草,Message創(chuàng)建完畢后交給Handler對(duì)象發(fā)送跟束,sendMessagesendMessageDelay最終都是在底層調(diào)用了sendMessageAtTime()方法,將Message對(duì)象放入MessageQueue中的丑孩。

    之后冀宴,由Looperloop()方法循環(huán)從MessageQueue中取出Message對(duì)象,調(diào)用message.getTarget ()獲取到發(fā)送消息的Handler對(duì)象温学,然后再調(diào)用handler.dispatchMessage()方法將信息分發(fā)給對(duì)應(yīng)handler執(zhí)行略贮。

    最后,Handler在dispatchMessage()方法中判斷是否有callback 存在,存在則執(zhí)行callback的onMessageHandler()逃延,最終交由Message.callback執(zhí)行览妖;否則則執(zhí)行handler的onMessageHandler()方法。

    
    public interface Callback {// callback接口
        public boolean handleMessage(Message msg);
    }
    
    public void handleMessage(Message msg) {
    
    }
    
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            /* 
            * callback是msg中的一個(gè)字段揽祥,是一個(gè)Runnable對(duì)象讽膏,
            * 當(dāng)通過(guò)handler.post方法發(fā)送一個(gè)runnable的時(shí)候就會(huì)被封裝到這個(gè)msg中
            */
            handleCallback(msg); 
            /*
            * 此方法是Handler中的一個(gè)靜態(tài)方法,方法體:message.callback.run();
            * 只有這一句拄丰,可以看出是直接調(diào)用的run()方法府树,沒(méi)有新建線程,
            * 否則也不符合這里線程通信了料按。
            */
        } else {
            if (mCallback != null) { // mCallback就是上面接口的對(duì)象
                if (mCallback.handleMessage(msg)) {
                    /*
                    * 如果返回true直接return結(jié)束方法奄侠,
                    * 不再調(diào)用handler中的handleMessage方法。
                    */
                    return;
                }
            }
            handleMessage(msg);// handler中的消息處理方法
        }
    }   
    
    

三载矿、其他的相關(guān)問(wèn)題

  1. 一個(gè)線程幾個(gè) Looper遭铺,幾個(gè) Handler,Looper 如何確定是哪個(gè) Handler恢准?

    一個(gè)線程只有一個(gè)Looper來(lái)處理MessageQueue中的消息;Handler可以有多個(gè)甫题,用來(lái)發(fā)送及處理消息馁筐。Looper不需要確定是那個(gè)Handler發(fā)送過(guò)來(lái)的消息,因?yàn)镸essage有個(gè)targ字段封裝了發(fā)送他的handler,系統(tǒng)直接通過(guò)message.getTarg().hanldMessage();就可以調(diào)用到handler的處理消息方法坠非。

  2. 兩個(gè)均不是主線的線程如何用Handler通信

    在闡述這個(gè)問(wèn)題時(shí)難免又要回到Handler的機(jī)制說(shuō)明上敏沉;線程間的通信關(guān)鍵是Handler的創(chuàng)建,而Handler的創(chuàng)建本質(zhì)需要一個(gè)Looper對(duì)象的存在炎码。

    Android默認(rèn)在主線程中初始化了一個(gè)Looper(詳見(jiàn)android.app.ActivityThread->Looper.prepareMainLooper())盟迟,其他線程都需要自己手動(dòng)創(chuàng)建Looper;一個(gè)線程能夠使用Handler處理消息潦闲,是必須要有Looper和MessageQueue的攒菠。

    下面回到具體問(wèn)題,兩個(gè)都不是主線程的線程需要通過(guò)Handler通信改如何操作歉闰?根據(jù)上面的分析我們知道:

    1. 需要在處理邏輯的線程Thread1創(chuàng)建Looper辖众,有了Looper之后才能創(chuàng)建Handler;
    2. 之后在需要發(fā)消息的線程Thread2使用Thread1的handler對(duì)象發(fā)送Message和敬;

    有了理論凹炸,之后擼一把代碼:

    
    public class MainActivity extends ComponentActivity {
        private static final String TAG = "MainActivity";
        private Looper looper2;
        private Thread1 thread1;
        private Thread thread2;
        private Handler child1Handler, child2Handler;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            initView();
        }
    
        private void initView() {
            setContentView(R.layout.activity_main);
            thread1 = new Thread1("ChildThread1");
            thread2 = new Thread2("ChildThread2");
            thread1.start();
            thread2.start();
        }
    
        private void initHandler() {
            child1Handler = new Handler(thread1.getLooper()) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    String ms = (String) msg.obj;
                    Log.e(TAG, "當(dāng)前線程:" + Thread.currentThread().getName() + "  消息:" + ms);
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Message message = Message.obtain();
                    message.obj = "你好,我是" + Thread.currentThread().getName() + "!";
                    child2Handler.sendMessage(message);
                }
            };
    
            child2Handler = new Handler(looper2) {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    String ms = (String) msg.obj;
                    Log.w(TAG, "當(dāng)前線程:" + Thread.currentThread().getName() + "  消息:" + ms);
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Message message = Message.obtain();
                    message.obj = "你好,我是" + Thread.currentThread().getName() + "!";
                    child1Handler.sendMessage(message);
                }
            };
        }
    
        class Thread1 extends HandlerThread {
            public Thread1(String name) {
                super(name);
            }
        }
    
        class Thread2 extends Thread {
            public Thread2(String name) {
                super(name);
            }
    
            @Override
            public void run() {
                Looper.prepare();
                looper2 = Looper.myLooper();
                initHandler();
                Message message = Message.obtain();
                message.obj = "你好,我是" + Thread.currentThread().getName() + "!";
                child2Handler.sendMessage(message);
                Looper.loop();
            }
        }
    }
    
    

四、Handler的內(nèi)存泄露

Handler是引發(fā)內(nèi)存泄露的大戶昼弟,其根源還是在于Handler機(jī)制的設(shè)計(jì)結(jié)構(gòu)上啤它;

  1. 原因:

    Message對(duì)象必須持有一個(gè)Handler的引用才能順利分發(fā)任務(wù)。而如果MessageMessageQueue中不能及時(shí)被取出執(zhí)行,則會(huì)導(dǎo)致MessageQueue長(zhǎng)時(shí)間的持有Message對(duì)象变骡;一般情況下我們會(huì)在Activity中以內(nèi)部類(lèi)的形式創(chuàng)建Handler离赫;這就導(dǎo)致了一系列的連鎖反應(yīng):

    Activity 被 Handler持有,Handler被Message持有锣光,Message被MessageQueue持有笆怠,MessageQueue中任務(wù)積壓,或者是本來(lái)就是個(gè)延時(shí)任務(wù)誊爹;這就導(dǎo)致了內(nèi)存溢出蹬刷。

  2. 解決辦法:

    根據(jù)上述的原因我們只需要能在Handler和Activity之間斷開(kāi)聯(lián)系,既能保證內(nèi)存不再泄露频丘;

    1)內(nèi)部類(lèi)是在類(lèi)對(duì)象創(chuàng)建后作為對(duì)象的一個(gè)成員存在的办成,而靜態(tài)內(nèi)部類(lèi)則是在類(lèi)class文件加載時(shí)就創(chuàng)建了,不再與外部類(lèi)對(duì)象關(guān)聯(lián)搂漠;因此我們可以將handler聲明為靜態(tài)內(nèi)部類(lèi)迂卢。

    2)我們都知道Hander還有個(gè)內(nèi)部接口Callback和與之對(duì)應(yīng)的構(gòu)造函數(shù)Handler(Callback callback)

    
    /**
     * 實(shí)現(xiàn)回調(diào)弱引用的Handler
     * 防止由于內(nèi)部持有導(dǎo)致的內(nèi)存泄露 
     * 傳入的Callback不能使用匿名實(shí)現(xiàn)的變量桐汤,必須與使用這個(gè)Handle的對(duì)象的生命周期一致而克,
     * 否則會(huì)被立即釋放掉了
     */
    public class WeakRefHandler extends Handler {
        private WeakReference<Callback> mWeakReference;
    
        public WeakRefHandler(Callback callback) {
            mWeakReference = new WeakReference<Handler.Callback>(callback);
        }
    
        public WeakRefHandler(Callback callback, Looper looper) {
            super(looper);
            mWeakReference = new WeakReference<Handler.Callback>(callback);
        }
    
        @Override
        public void handleMessage(Message msg) {
            if (mWeakReference != null && mWeakReference.get() != null) {
                Callback callback = mWeakReference.get();
                callback.handleMessage(msg);
            }
        }
    }
    
    

    接下來(lái)就是使用,由于是弱引用怔毛,當(dāng)該類(lèi)需要被回收時(shí)员萍,就可以直接被回收掉:

    
    private Handler.Callback mCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch(msg.what){
               return true;
            }
        };
    private Handler mHandler = new WeakRefHandler(mCallback);
    
    

ITJL博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市拣度,隨后出現(xiàn)的幾起案子碎绎,更是在濱河造成了極大的恐慌,老刑警劉巖抗果,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筋帖,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡冤馏,警方通過(guò)查閱死者的電腦和手機(jī)日麸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逮光,“玉大人赘淮,你說(shuō)我怎么就攤上這事∧丽” “怎么了梢卸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)副女。 經(jīng)常有香客問(wèn)我蛤高,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任戴陡,我火速辦了婚禮塞绿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恤批。我一直安慰自己异吻,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布喜庞。 她就那樣靜靜地躺著诀浪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪延都。 梳的紋絲不亂的頭發(fā)上雷猪,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音晰房,去河邊找鬼求摇。 笑死,一個(gè)胖子當(dāng)著我的面吹牛殊者,可吹牛的內(nèi)容都是我干的与境。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼猖吴,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼嚷辅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起距误,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎扁位,沒(méi)想到半個(gè)月后准潭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡域仇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年刑然,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片暇务。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泼掠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出垦细,到底是詐尸還是另有隱情择镇,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布括改,位于F島的核電站腻豌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吝梅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一虱疏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧苏携,春花似錦做瞪、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至国旷,卻和暖如春矛物,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背跪但。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工履羞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屡久。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓忆首,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親被环。 傳聞我的和親對(duì)象是個(gè)殘疾皇子糙及,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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