01殴胧、Handler的那些事

HandlerPic.png

版權(quán)聲明:本文為博主原創(chuàng)文章渗稍,未經(jīng)博主允許不得轉(zhuǎn)載。

PS:轉(zhuǎn)載請(qǐng)注明出處
作者: TigerChain
地址: http://www.reibang.com/p/73e5fd7eb7da
本文出自 TigerChain 簡(jiǎn)書 Android 系列

教程簡(jiǎn)介

  • 1团滥、閱讀對(duì)象

本篇教程適合新手閱讀竿屹,老手直接略過(guò)

  • 2、教程難度

    初級(jí)

正文

摘要:

Handler 在 Android 的作用主要是線程間通訊的灸姊,現(xiàn)在也有各種文章在講解 Handler 的作用以及源碼分析拱燃,但是必定這些都是別人自己的總結(jié)和整理,和自己總結(jié)還是有區(qū)別的力惯,為了加深自己的記憶所以自己也來(lái)分析一下 Handler 以及它的小伙伴們碗誉。

什么是 Handler

什么是 Handler?

再華麗的解釋也不過(guò)是官方的解解釋吧:先看官網(wǎng)上的一段話

A Handler allows you to send and process Message and Runnable objects associated with
a thread's MessageQueue. Each Handler instance is associated with a single thread 
and that thread's message queue. When you create a new Handler, it is bound to the 
thread / message queue of the thread that is creating it -- from that point on, 
it will deliver messages and runnables to that message queue and execute them 
as they come out of the message queue.

大概意思就是 Handler 可以發(fā)送 Message 和 Runnable 對(duì)象發(fā)送到 Handler 所關(guān)聯(lián)的線程隊(duì)列中去,每個(gè) Handler 的實(shí)例都會(huì)綁定到創(chuàng)建它的線程中父晶。

Handler 的作用

再來(lái)看官網(wǎng)雜說(shuō)的

There are two main uses for a Handler:
(1) to schedule messages and runnables to be executed as some point in the future
(2) to enqueue an action to be performed on a different thread than your own.

大概意思就是說(shuō)哮缺,Handler 有兩個(gè)用途:

(1)、在某一時(shí)刻執(zhí)行某些事情
(2)甲喝、在不同的線程中執(zhí)行操作 (也就是線程間通信)

Handler 常用的方法

方法名 描述
boolean post (Runnable r) 立即執(zhí)行操作
postAtTime(Runnable, long) 在指定的時(shí)間執(zhí)行某些操作
postDelayed(Runnable, long) 延時(shí)執(zhí)行某些操作
sendEmptyMessage(int) 發(fā)送一個(gè)含有what的信息
sendMessage(Message) 發(fā)送一個(gè)Message
sendMessageAtTime(Message, long) 在指定的時(shí)間發(fā)送消息
sendMessageDelayed(Message, long) 延時(shí)發(fā)送消息

Handler 默認(rèn)是運(yùn)行在 UI 線程中的蝴蜓,默認(rèn)默認(rèn)默認(rèn)...哦 一定記得,至于為什么俺猿,我們后面會(huì)說(shuō)的茎匠。

Handler 常用的使用方式

1、解決 ANR押袍,子線程更新UI線程

在Android應(yīng)用程序中我們最常見的異步莫過(guò)于 ANR 了(主線程執(zhí)行耗時(shí)操作了,發(fā)起網(wǎng)絡(luò)請(qǐng)求诵冒,或操作 IO 等),通常情況下在 Activity 是5秒鐘響應(yīng)就會(huì)報(bào)這個(gè)錯(cuò)谊惭,在 BroadCast 中是 10 秒鐘汽馋,使用 Handler 可以很容易的解決這個(gè)問(wèn)題。

解決方法:

  • 1圈盔、定義 Handler 并重寫 handleMessage 方法
 //在UI線程中
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    //取得傳遞過(guò)來(lái)的 msg
                    String passedChangeTv = (String) msg.obj;
                    tv.setText(passedChangeTv);
                    break ;
            }
        }
    } ;

  • 2豹芯、在子線程中去執(zhí)行耗時(shí)操作
 //比如請(qǐng)求服務(wù)器,然后更新 TextView
        new Thread(new Runnable() {
            @Override
            public void run() {
                //執(zhí)行耗時(shí)操作
                /**
                 * 1 請(qǐng)求服務(wù)器
                 * 2 解析數(shù)據(jù)
                 * 3 獲取數(shù)據(jù)
                 */
                //比如從服務(wù)器取得的數(shù)據(jù)是" Handler 應(yīng)用"
                String changeTv = "Handler應(yīng)用" ;
                Message message = new Message() ;
                message.what = 1 ;
                message.obj = changeTv ;
                mHandler.sendMessage(message) ;
            }
        }).start();

這樣就可以解決 ANR

2、可以用作輪詢驱敲,或是定時(shí)器

  • 1铁蹈、舉個(gè)栗子,我們要一秒鐘打印一個(gè)字符串,我們可以使用 Handler 的 postDelayed 方法
 private Handler mtimeTaskHandler  = new Handler() ;
    private void timerTaskHandler() {
        //調(diào)用Handler自帶的
        mtimeTaskHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Log.e("===","TAG:"+ System.currentTimeMillis()/1000) ;
                mtimeTaskHandler.postDelayed(this,1000) ;
            }
        },1000) ;
    }
  • 2众眨、一秒鐘請(qǐng)求一次服務(wù)器握牧,然后把服務(wù)器的最新數(shù)據(jù)顯示在 TextView 容诬,在這里我們使用 Handler 的sendMessageDelayed(Message msg,Long deayTime)方法
 private Handler preSecondHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    //取得傳遞過(guò)來(lái)的 msg
                    int passedChangeTv = (int) msg.obj;
                    tv.setText(passedChangeTv+"");
                    //再次調(diào)用獲取服務(wù)器最新數(shù)據(jù)方法
                    preSecondGetServer() ;
                    break ;
            }
        }
    } ;
    private int i ;
    private void preSecondGetServer() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //假設(shè)這里從服務(wù)器取回來(lái)的 i
                i++ ;
                Message message = new Message() ;
                message.what = 1 ;
                message.obj = i ;
            preSecondHandler.sendMessageDelayed(message,1000) ;
            }
        }).start();

這樣就可以一秒請(qǐng)求一次服務(wù)器,并且把結(jié)果顯示在 TextView 上了

3沿腰、兩個(gè)子線程進(jìn)行通信

Handler的作用就是是進(jìn)行線程間通信的览徒,上面說(shuō)的都是UI線程(主線程)和子線程之間的通信,那么兩個(gè)工作線程(子線程)可以通信嗎颂龙,答案是肯定 的习蓬。

  • 1、先聲明 Handler 所在的線程
  //成員變量 子線程中的 Handler 
private Handler threadHandler ;
class MyThread extends Thread{
      {
          //一實(shí)例化就開啟線程 代碼塊
          start();
      }
      @Override
      public void run() {
          super.run(); // Handler 所在的線程默認(rèn)是沒(méi)有 looper 的(除了 Activity )
          Looper.prepare();
          threadHandler = new Handler(){
              @Override
              public void handleMessage(Message msg) {
                  super.handleMessage(msg);
                  //取得傳遞過(guò)來(lái)的 msg
                  String passedChangeTv = (String) msg.obj;
                  //設(shè)置顯示
                  tv.setText(passedChangeTv);
              }
          } ;
          Looper.loop();
      }
  }

注意:子線程默認(rèn)是沒(méi)有 Looper 的所以我們?cè)谧泳€程中一定要聲明 Looper.prepare() 和 Looper.loop() 在它們之間去初始化 Handler

  • 2措嵌、在另一個(gè)子線程中發(fā)送消息
private void threadHandlerTask() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                String changeTv = "Handler應(yīng)用" ;
                Message message = new Message() ;
                message.what = 1 ;
                message.obj = changeTv ;
                threadHandler.sendMessage(message) ;
            }
        }).start() ;
    }
  • 3友雳、最后在合適的地方調(diào)用,在這里我們?yōu)榱搜菔揪驮?Activity 的onCreate() 方法中調(diào)用
 MyThread t = new MyThread() ;
 threadHandlerTask() ;

以上就完成了兩個(gè)子線程通過(guò) Handler 來(lái)通信。

注意:上面的子線程能信有問(wèn)題铅匹,不知道細(xì)心的朋友發(fā)現(xiàn)了沒(méi)有押赊,我故意留了一個(gè) bug,忽略此 bug 不提包斑,我們上面的程序性能是有問(wèn)題的流礁,我們一一解決。

1罗丰、上面代碼存在的 bug

細(xì)心的朋友會(huì)說(shuō)我靠神帅,擦,F(xiàn)K,竟然在子線程中更新 UI萌抵,我們要以清楚的看到 threadHandler 是在子線程中的找御,那么我們是不能更新 UI的。會(huì) ANR 的好不好呀绍填,沒(méi)錯(cuò)就是這個(gè)問(wèn)題霎桅,不怕,程序員就是發(fā)現(xiàn)問(wèn)題并解決問(wèn)題的讨永,我們來(lái)解決這個(gè)問(wèn)題滔驶,修改代碼如下。

  • 1卿闹、首先在 MainActivity 中聲明一個(gè) threadHander( threadHander 一定是在主線程中的)
 private Handler threadHander = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //取得傳遞過(guò)來(lái)的 msg
                String passedChangeTv = (String) msg.obj;
                tv.setText(passedChangeTv+"");
            }
        } ;

所以上面代碼是可以更新 UI 的沒(méi)有問(wèn)題

  • 2揭糕、聲明子線程
class MyThread extends Thread{
        private Looper mLooper ;
        private Handler mHandler ;

        /**
         * 代碼塊或是構(gòu)造方法中開啟線程都可以
         */

        public MyThread(){
            Log.e("t所在的線程",this.getName()) ;
            start();
        }
//        {
//            //一實(shí)例化就開啟線程 代碼塊
//            start();
//        }
        @Override
        public void run() {
            super.run(); // Handler 所在的線程默認(rèn)是沒(méi)有 looper 的(除了Activity)
            Looper.prepare();
            mLooper = Looper.myLooper() ;
            mHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    //取得傳遞過(guò)來(lái)的 msg
                    String passedChangeTv = (String) msg.obj;
                    //設(shè)置顯示  這里的 Handler 運(yùn)行在子線程中去的,所以不能更新UI
                   // tv.setText(passedChangeTv);
                    threadHander.sendMessage(threadHander.obtainMessage(1,passedChangeTv)) ;

                }
            } ;
            Looper.loop();
        }

         public Handler getThreadHandler(){
            return mHandler!=null?mHandler:null ;
        }
    }
  • 3、在另一個(gè)線程中去發(fā)送消息
 private void threadHandlerTask() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                String changeTv = "Handler應(yīng)用" ;
                Message message = new Message() ;
                message.what = 1 ;
                message.obj = changeTv ;
                t.getThreadHandler().sendMessage(message) ;
            }
        }).start() ;
    }

此處的t就是 MyThread 的實(shí)例锻霎,聲明成成員變量著角,不用糾結(jié)

我們來(lái)捋一捋流程,首先調(diào)用

t.getThreadHandler().sendMessage(message)

將消息發(fā)送到 MyThread 的 Handler 中旋恼,然后在 MyThread 的 Handler (子線程中)中將消息發(fā)送到 MainActivity 的 threadHander(UI線程中)吏口,這樣就完成了,子線程更新UI線程的過(guò)程

注:這里是為了顯示更新UI線程所以多次發(fā)送,如果只是為了兩個(gè)線程間通信锨侯,那么 threadHander 是可以不需要的

我們把改后的代碼運(yùn)行一下,并且我添加了一些線程的日志冬殃,看效果

thread_handler.gif

從圖中我們可以看到囚痴,子線程間通信是完全沒(méi)有問(wèn)題的,但是我們退出應(yīng)用再進(jìn)入應(yīng)用的時(shí)候發(fā)現(xiàn)审葬,線程數(shù)量在不停的增加深滚,我靠如果打開 100 次豈不是又多了 100 個(gè)線程,1000... 次呢涣觉,想都不敢想痴荐。如何解決呢,往下看官册。

2生兆、手動(dòng)調(diào)用 Looper 需要手動(dòng)釋放

接著上面繼續(xù)說(shuō),問(wèn)題出現(xiàn)在那里呢膝宁,就是手動(dòng)調(diào)用 Looper.perpare() 和 Looper.loop() 的時(shí)候一定要記得在任務(wù)完成以后鸦难,或是合適的地方要釋放掉 looper,要調(diào)用 looper.quit()员淫,結(jié)束消息隊(duì)列合蔽,進(jìn)而結(jié)束線程。如果不這么做介返,thread 會(huì)長(zhǎng)時(shí)間存在不銷毀這拴事。這就是上面所說(shuō)的性能問(wèn)題,不斷的開辟線程圣蝎,線程的開辟是需要開銷的刃宵,所以我們來(lái)解決這一問(wèn)題

  • 1、我們改造MyThread類
class MyThread extends Thread{
        private Looper mLooper ;
        private Handler mHandler ;

        /**
         * 代碼塊或是構(gòu)造方法中開啟線程都可以
         */

        public MyThread(){
            Log.e("t所在的線程",this.getName()) ;
            start();
        }
//        {
//            //一實(shí)例化就開啟線程 代碼塊
//            start();
//        }
        @Override
        public void run() {
            super.run(); //Handler所在的線程默認(rèn)是沒(méi)有l(wèi)ooper的(除了Activity)
            Looper.prepare();
            mLooper = Looper.myLooper() ;
            mHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    //取得傳遞過(guò)來(lái)的 msg
                    String passedChangeTv = (String) msg.obj;
                    //設(shè)置顯示  這里的Handler運(yùn)行在子線程中去的,所以不能更新UI
                   // tv.setText(passedChangeTv);
                    threadHander.sendMessage(threadHander.obtainMessage(1,passedChangeTv)) ;
                }
            } ;
            Looper.loop();
        }
         //這里是新添加的方法
        public void exit(){
            if(mLooper!=null){
                mLooper.quit();
                mLooper = null ;
            }
        }

        public Handler getThreadHandler(){
            return mHandler!=null?mHandler:null ;
        }
    }

我們只是在 MyThread 類中添加了一個(gè) exit 方法徘公,其它的都沒(méi)有變.

  • 2组去、為了方便測(cè)試,我們?cè)?onDestory() 方法中調(diào)用
 @Override
        protected void onDestroy() {
            super.onDestroy();
            Log.e("onDestroy",t.getName()+"銷毀") ;
            t.exit();
            t=null ;
        }

我們重新跑一下應(yīng)用程序步淹,來(lái)觀察結(jié)果

remove_thread_handler.gif

從效果圖中我們可以看出从隆,當(dāng)我們手動(dòng)調(diào)用 Looper.prepare() 和 Looper.loop() 的時(shí)候創(chuàng)建一個(gè)線程,但是們退出應(yīng)用的時(shí)候會(huì)退出 Looper缭裆,線程會(huì)銷毀键闺,我們需要你的時(shí)候讓你存在,不需要的時(shí)候讓你銷毀澈驼,這就大大提高了性能辛燥。我們看始終激活的線程數(shù)量都是5,這是我們所期望的

總結(jié):在子線程中,如果手動(dòng)為其創(chuàng)建了 Looper挎塌,那么在所有的事情完成后應(yīng)該調(diào)用 quit 方法來(lái)終止消息循環(huán)

Handler 和它兄弟們

Handler 不能單獨(dú)工作徘六,必須和它的兄弟們一起協(xié)同才可以工作。而它的兄弟們就是以下三個(gè)

1榴都、Looper

Looper 字面意思是一個(gè)輪詢器待锈,是用來(lái)檢測(cè) MessageQueue 中是否有消息,如果有消息則把消息分發(fā)出去嘴高,沒(méi)有消息就阻塞在那里直到有消息過(guò)來(lái)為止竿音。
一個(gè)線程對(duì)應(yīng)一個(gè) Looper,一個(gè) Looper 對(duì)應(yīng)一個(gè) MessageQueue.

注意:默認(rèn)情況下,線程是沒(méi)有 Looper 的拴驮,所以要調(diào)用 Looper.prepare() 來(lái)給線程創(chuàng)建消息隊(duì)列春瞬,然后再通過(guò),Looper.loop() 來(lái)不停(死循環(huán))的處理消息消息隊(duì)列的消息

2套啤、MessageQueue

是用來(lái)存放消息的宽气,通過(guò) Looper.prepare() 來(lái)初始化消息隊(duì)列。MessageQueue 從字面意思來(lái)看是一個(gè)消息隊(duì)列潜沦,但是內(nèi)部實(shí)現(xiàn)并不是基于隊(duì)列的抹竹。是基于一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表的,鏈表插入和刪除優(yōu)勢(shì)很明顯止潮。

3窃判、Message

傳遞信息的載體,可以攜帶一些信息喇闸,類似于 Intent袄琳,Bundle 一樣。兩個(gè)線程通過(guò) Message 聯(lián)系在一起燃乍。

Handler 源碼分析

重點(diǎn)來(lái)了唆樊,下面我們一起來(lái)分析一下Handler的源碼

1、sendMessage() 發(fā)生了什么

當(dāng)我們調(diào)用 Handler 的 sendMessage 方法的時(shí)候到底發(fā)生了什么刻蟹,我們從源碼去看一下

 public final boolean sendMessage(Message msg) {
        return sendMessageDelayed(msg, 0);
    }

我們可以看到調(diào)用sendMessage方法調(diào)用sendMessageDelayed(msg, 0)

public final boolean sendMessageDelayed(Message msg, long delayMillis){
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

而 sendMessageDelayed 方法又調(diào)用 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis) 方法

 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
         //取得MessageQueue
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        //把消息存放到MessageQueue中
        return enqueueMessage(queue, msg, uptimeMillis);
    }

在這里不是不有些小激動(dòng)呢逗旁,我們看到 sendMessage 最終調(diào)用的是 enqueueMessage(queue, msg, uptimeMillis) 這個(gè)方法,我們來(lái)看看這個(gè)方法

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

我勒個(gè)插舆瘪,還在調(diào)用片效,這里代碼我們基本上也能看懂,就是不知道m(xù)sg.target 是個(gè)毛線英古,不過(guò)他是 Message 的一個(gè)屬性淀衣,我們進(jìn)去看一下發(fā)現(xiàn) msg.target 是一個(gè)Handler,好我們?cè)倏磓ueue.enqueueMessage(msg, uptimeMillis) 方法

 boolean enqueueMessage(Message msg, long when) {
 //msg.target 這么熟悉召调,我去不就是 Handler 嗎膨桥,要發(fā)送一個(gè)消息肯定要有 Handler 呀蛮浑,不然發(fā)個(gè)毛毛呢
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //消息是否正在使用
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
  //消息插入鏈表頭,條件是如果MessageQueue為空只嚣,或是消息發(fā)送的時(shí)刻為0或者消息發(fā)送的時(shí)刻小于鏈表頭的派發(fā)時(shí)刻沮稚,就把消息插入鏈表頭
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
            //否則就找個(gè)合適的位置把消息插到鏈表中
                 Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

總之這個(gè)方法就是把消息插入到消息隊(duì)列中,我們現(xiàn)在可以歸納了,當(dāng)調(diào)用handler.sendMessage方法的時(shí)候做了一件重要的事:
把消息插入到MessageQueue中

2册舞、Looper 該上場(chǎng)了

根據(jù)前面所述蕴掏,我們知道 Looper 的作用是創(chuàng)建消息隊(duì)列,和處理消息环础。

  • 1囚似、Looper.prepare() 方法就是用來(lái)創(chuàng)建消息隊(duì)列的剩拢,我們看源碼
 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));
 }

我們看到 prepare 方法調(diào)用 prepare(true) 方法线得,此方法中我們目前也沒(méi)有看不懂的語(yǔ)句,無(wú)非就是 sThreadLocal 是什么東東徐伐,不要緊贯钩,我們暫且當(dāng)它是一個(gè) List 一樣用來(lái)存取 Looper 的,接下來(lái)我們看 new Looper(quitAllowed) 方法

 private Looper(boolean quitAllowed) {
     //初始化消息隊(duì)列
     mQueue = new MessageQueue(quitAllowed);
     //取得當(dāng)線線程
     mThread = Thread.currentThread();
 }

看到了吧办素,確實(shí)在上面方法中初始化了 MessageQueue角雷。所以我們這里總結(jié) Looper.prepare() 方法就是創(chuàng)建了一個(gè)消息隊(duì)列

根據(jù)上面的分析,我們有了消息隊(duì)列性穿,也知道 sendMessage 是把消息插入到消息隊(duì)列中去了勺三,那么如何管理 MessageQueue中 的消息呢,Looper.loop() 該上場(chǎng)了需曾。

  • 2吗坚、Looper.loop(): 我們直接看源碼
 public static void loop() {
       //取得Looper
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //拿到 MessageQueue
        final MessageQueue queue = me.mQueue;
          ...省略部分代碼
        for (;;) { //這里是一個(gè)死循環(huán),取出每個(gè)消息呆万,然后分發(fā)
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
             ... 省略部分代碼
             //msg.target(Handler)這里調(diào)用 Handler 的dispatchMessage方法
            msg.target.dispatchMessage(msg);
        }
    }

綜上代碼商源,我們可以得出結(jié)論 Lopper.loop() 方法主要作用是取出 MessageQueue 中消息然后調(diào)用 Handler 的 dispatchMessage 方法把消息分發(fā)出去(前提是 Message 中要有消息)

接下來(lái)我們看 Handler 的 dispatchMessage 方法

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            //如果msg.callback不為空,則調(diào)用handleCallback
            handleCallback(msg);
        } else { 否則谋减,mCallback不為空
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //調(diào)用handleMessage
            handleMessage(msg);
        }
    }

似乎看到熟悉的方法了牡彻,對(duì)就是 handleMessage(msg),就是 Handler 的回調(diào)方法出爹,就是處理消息的方法庄吼。

這樣我們大體了解到,Handler的處理流程了严就,大致如下:

Looper.parpare() ;  //先準(zhǔn)備好消息隊(duì)列

Handler handler = new Handler(){
  @Override
  handleMessage(Message msg){
        //如理發(fā)送過(guò)來(lái)的消息
        ...
        ...
  }
}


Looper.loop() ; //是一個(gè)死循環(huán)霸褒,一直監(jiān)聽著 MessageQueue ,從消息隊(duì)列中取消息,然后調(diào)用 handler. dispatchMessage 方法盈蛮,最后調(diào)用的就是 handler 的 handleMessage(Message msg) 方法來(lái)處理消息

那么如何把消息插入到消息隊(duì)列呢废菱,就靠 Handler 的 sendMessage() 方法了技矮,所以當(dāng)調(diào)用了 Handler 的 sendMessage(),Looper.loop() 監(jiān)聽到 MessageQueue 中有消息殊轴,就調(diào)用 dispatchMessage 方法分發(fā)消息衰倦,最終調(diào)用 handler 的 handleMessage(Message msg) 方法來(lái)處理消息。

Handler的原理如下圖所示

handler.png

原創(chuàng)圖:轉(zhuǎn)載注明出處

Activity 默認(rèn)有 Looper

Activity 中的 Looper 從何來(lái)

我們知道使用 Handler 的時(shí)候旁理,Handler 一定要存在于有Looper.parpare() 和 Looper.loop() 的線程中可是我們都知道 Activity 中使用 Handler 的時(shí)候不用寫 Looper.parpare() 和 Looper.loop()樊零,但是為什么不用寫有沒(méi)有想過(guò),那么我們來(lái)分析一下

Activity 所在線程也叫 UI 線程孽文,為什么叫 UI 線程呢驻襟,其實(shí)它是通過(guò) ActivityThread 來(lái)管理的,我們知道 Android 應(yīng)用層是用 Java 開發(fā)的芋哭,那么 Java 肯定是有一個(gè)入口方法即 main 方法沉衣,main 方法到底在那里呢?
答案就是在 ActivityThread.java 中减牺,我們來(lái)看一下源碼

public final class ActivityThread {
   
   ... 省略一大坨代碼
   final Looper mLooper = Looper.myLooper();
   //H就是一個(gè)Handler
   final H mH = new H();
    ... 省略若干代碼
   private class H extends Handler {
        省略一大堆
        String codeToString(int code) {
            if (DEBUG_MESSAGES) {
                switch (code) {
                    case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
                    case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
                    case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING";
                    case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW";
                    ...省略大量代碼
                 }
            }
            return Integer.toString(code);
        }
        public void handleMessage(Message msg) {
         
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
                case RELAUNCH_ACTIVITY: 
                    ...            
                case PAUSE_ACTIVITY:
                    ...
                    break;
                    
                ... 省略部分代碼
                
                case RESUME_ACTIVITY:
                    ...
                    break;
                case SEND_RESULT:
                    ...
                    break;
                case DESTROY_ACTIVITY:
                    ...
                    break;
                    
                ... 省略部分代碼

                case CREATE_SERVICE:
                    ...
                    break;
                case BIND_SERVICE:
                    ...
                    break;
                case UNBIND_SERVICE:
                    ...
                    break;
                    
                  ... 省略又臭又長(zhǎng)的代碼   
               }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }

       }
       
       public static void main(String[] args) {

         ...省略部分代碼

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ...省略部分代碼
        
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}  
  

從上面代碼中可以隱約的看到 Activity 的 oncreate 豌习,onresume... Service 的 oncreate bindService 等等方法都在這里處理了,再?gòu)纳厦婵吹搅?main 方法并且調(diào)用了 Looper.prepareMainLooper(); 和 Looper.loop(); 由此就知道了拔疚,為什么我們的 Activity 默認(rèn)是有 Looper 的肥隆。

結(jié)論

Activity 叫 UI 線程,其實(shí)指的就是 ActivityThread稚失,Activity 是由ActivityThread 管理啟動(dòng)栋艳,暫停等,并且 ActivityThread 的入口方法中開啟了 Looper.prepareMainLooper() 和 Looper.loop()句各,所以我們的 Activity 默認(rèn)就會(huì)有一個(gè) Looper吸占,Service 同理

Handler引起的內(nèi)存泄露

引子

我們先來(lái)看一個(gè)例子

public class MainActivity extends AppCompatActivity {

    private TextView tv ;

    //在UI線程中
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            ...
            ...
        }
    } ;
  }

乍一看,好像沒(méi)有什么問(wèn)題诫钓,但是我告訴你旬昭,這段代碼可能會(huì)存在內(nèi)存泄露的,這是為什么呢菌湃?答案是非靜態(tài)內(nèi)存部會(huì)持有外部類的引用(Java的特性)问拘,在這里Handler是一個(gè)非靜態(tài)的內(nèi)存部類,會(huì)隱式的持有 MainActivity 的引用的惧所。

解決辦法

解決這個(gè)問(wèn)題的辦法就是避免使用非靜態(tài)內(nèi)部類骤坐。

  static class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
          
      }
  }

如果要操作Activity的一些對(duì)象,這里使用弱引用

static class MyHandler extends Handler {
        // WeakReference to the outer class's instance.
        private WeakReference<MyActivity> mOuter;

        public MyHandler(MyActivity activity) {
            mOuter = new WeakReference<MyActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MyActivity outer = mOuter.get();
            if (outer != null) {
                // Do something with outer as your wish.
            }
        }
  }

完整的代碼

public class MyActivity extends AppCompatActivity {
    private MyHandler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mHandler = new MyHandler(this);
    }

    @Override
    protected void onDestroy() {
        // Remove all Runnable and Message.
        mHandler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }

    static class MyHandler extends Handler {
        // WeakReference to the outer class's instance.
        private WeakReference<MyActivity> mOuter;

        public MyHandler(MyActivity activity) {
            mOuter = new WeakReference<MyActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MyActivity outer = mOuter.get();
            if (outer != null) {
                // Do something with outer as your wish.
            }
        }
    }
}

當(dāng)然我們可以把Handler單獨(dú)寫到一個(gè)類中下愈,配置 RxBus 或 EventBus來(lái)更新UI或執(zhí)行某些操作纽绍,這里就介紹完了 Handler 的相關(guān)知識(shí)點(diǎn)了

想成為一個(gè)牛 B 的人都會(huì)點(diǎn)喜歡或分享的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市势似,隨后出現(xiàn)的幾起案子拌夏,更是在濱河造成了極大的恐慌僧著,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件障簿,死亡現(xiàn)場(chǎng)離奇詭異盹愚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)站故,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門皆怕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人西篓,你說(shuō)我怎么就攤上這事愈腾。” “怎么了岂津?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵虱黄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我寸爆,道長(zhǎng)礁鲁,這世上最難降的妖魔是什么盐欺? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任赁豆,我火速辦了婚禮,結(jié)果婚禮上冗美,老公的妹妹穿的比我還像新娘魔种。我一直安慰自己,他們只是感情好粉洼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布节预。 她就那樣靜靜地躺著,像睡著了一般属韧。 火紅的嫁衣襯著肌膚如雪安拟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天宵喂,我揣著相機(jī)與錄音糠赦,去河邊找鬼。 笑死锅棕,一個(gè)胖子當(dāng)著我的面吹牛拙泽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播裸燎,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼顾瞻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了德绿?” 一聲冷哼從身側(cè)響起荷荤,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤退渗,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后蕴纳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氓辣,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年袱蚓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了钞啸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喇潘,死狀恐怖纫版,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吮龄,我是刑警寧澤陌宿,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站忱屑,受9級(jí)特大地震影響蹬敲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜莺戒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一伴嗡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧从铲,春花似錦瘪校、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至伸辟,卻和暖如春麻惶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背信夫。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工窃蹋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忙迁。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓脐彩,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親姊扔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惠奸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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