Android Handler的使用方式和注意事項

今天給大家講解的是在Android開發中如何使用Handler和使用Handler時要注意的一些細節問題。本篇文章是作為我上一篇文章《Android源碼分析--Handler機制的實現與工作原理》的補充。雖然是補充,但是兩篇文章所講的內容不同:一個是原理的分析,一個是使用的講解。如果你還沒有看過上一篇文章,建議你先去看一看,只有了解了Handler的原理,才能更好的使用它。而且我們今天所講的內容也是建立在上一篇文章的基礎上的。
Handler的最大作用就是線程的切換,至于Handler切換線程的原理和實現,上一篇文章已有詳細的講解,這里就不多說了。下面我們看如何把一個消息從一個線程發送到另一個線程。

    //在主線程創建一個Handler對象。
    //重寫Handler的handleMessage方法,這個就是接收并處理消息的方法。
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //msg就是子線程發送過來的消息。
        }
    };
    
    //開啟一個子線程
    new Thread(new Runnable() {
            @Override
            public void run() {
                //在子線程發送一個消息。
                Message msg = new Message();
                handler.sendMessage(msg);
            }
        }).start();

上面就是一個簡單的Handler使用例子。我們在主線程創建一個Handler,他的handleMessage()方法是運行在主線程的,當我們在子線程發送一個消息的時候,handleMessage()會接收到消息,這樣我們就把消息由子線程發送到了主線程。上面的代碼中,Handler發送的消息是一個空消息,什么數據也沒有,如果我們只是單純的發送一個空消息,可以使用Handler自己的發送空消息的方法:

handler.sendEmptyMessage(what);

這兩種發送空消息的效果是一樣的。至于這里的what是什么我們后面會說到。
Handler也可以發送帶有數據的消息。Message對象有一個Object類型的obj屬性,就是用來攜帶消息數據的。我們只需要把要發送的數據賦值給obj就可以了,然后在處理消息的時候再把數據取出來。

    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            Log.i(TAG,"Handler 發送過來的消息是:" + msg.obj);
        }
    };

    new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.obj = "我是消息數據";
                handler.sendMessage(msg);

            }
        }).start();

上面的代碼中,msg攜帶一個字符串數據:"我是消息數據",在handleMessage()方法中把這個數據取出來。除了obj以外,Message還有兩個int類型的屬性:arg1、arg2可以用來發送一些簡單的數據。
現在我們看到的所有的消息都是在Handler的handleMessage()方法中處理,如果我要發送很多個消息,每個消息的數據都不一樣,消息的處理邏輯也不一樣,那么在handleMessage()方法中如何去判斷哪個消息是哪個呢?這時Message的what屬性就派上用場了,what屬性就是上面發送空消息時我們看到的what,它是一int類型,它是用來標識消息的。

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {

            switch (msg.what) {
                case 1:
                    Log.i(TAG, "第一個消息是:" + msg.obj);
                    break;

                case 2:
                    Log.i(TAG, "第二個消息是:" + msg.obj);
                    break;
            }
        }
    };

    new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg1 = new Message();
                msg1.obj = "第一個消息";
                msg1.what = 1;
                handler.sendMessage(msg1);

                Message msg2 = new Message();
                msg2.obj = "第二個消息";
                msg2.what = 2;
                handler.sendMessage(msg2);
            }
        }).start();

上面我們在子線程中發送了兩個消息,并且給兩個消息設置了它的what,在處理消息的時候就可以通過msg的what來判斷是哪個消息了。
除了用sendMessage發送Message消息以外,Handler還可以post一個Runnable。

    new Thread(new Runnable() {
            @Override
            public void run() {
                //在子線程post一個Runnable對象
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        //這里是消息處理的方法
                        //這里運行在主線程。
                    }
                });
            }
        }).start();

其實post()方法和sendMessage()方法的邏輯是一樣的,post()方法中的Runnable會被封裝成Message,然后發送出去。下面看它的源碼:

    public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

從源碼中我們看到,Runnable會被封裝成Message,然后還是用sendMessage的方式發送出去。而且封裝的邏輯也很簡單,直接把Runnable賦值給Message的callback就可以了。在上一篇文章中我介紹了消息處理的三個方法,Message自己的callback優先級是最高的,所以這個消息是由自己的callback也就是Runnable的run()方法處理的,而不再是Handler的handleMessage()方法。
前面我們所舉的例子中,都是消息的處理是在主線程的。其實不然,消息的處理事實上是運行在負責管理消息隊列(MessageQueue)的Looper所在的線程的,而不一定是主線程。這一點我在上一篇文章也反復的提到了,如果還不了解這一點的請認真閱讀一下我的上一篇文章。
如果我們創建一個Handler對象而沒有給它指定它的Looper,那么它默認會使用當前線程的Looper。前面我們所舉的例子都是在主線程直接創建Handler對象的,所以它的Looper就是主線程的Looper,它的消息自然也就是在主線程處理了。那么我們也可以在子線程使用Handler,下面可一個例子:

    //聲明Handler;
    Handler handler;
    new Thread(new Runnable() {
        @Override
        public void run() {
        //創建當前線程的Looper
            Looper.prepare();
            //在子線程創建handler對象
            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                //這里是消息處理,它是運行在子線程的
                }
           };
           //開啟Looper的消息輪詢
           Looper.loop();
       }
   }).start();

   mBanner.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
       //在主線程發送一個消息到子線程
           Message msg = new Message();
           handler.sendMessage(msg);
       }
   });

在上面的例子中,我們在子線程創建handler對象,handler的Looper就是子線程的Looper,所以消息的處理也就是在子線程處理的。這就是在子線程使用Handler的方式。在這里有幾個需要注意的點:(很重要)
1、在子線程使用Handler前一定要先為子線程創建Looper,創建的方式是直接調用Looper.prepare()方法。前面我們說過創建Handler對象時如果沒有給它指定Looper,那么它默認會使用當前線程的Looper,而線程默認是沒有Looper的,所以使用前一定要先創建Looper。
2、在同一個線程里,Looper.prepare()方法不能被調用兩次。因為同一個線程里,最多只能有一個Looper對象。
3、只有調用了Looper.loop()方法,Handler機制才能正常工作。 Looper負責管理MessageQueue,它的loop()方法負責從MessageQueue里取出消息并交給Handler處理,所以如果沒有調用Looper.loop()方法,消息就不會被取出和處理。
4、Looper.loop()方法一定要在調用了Looper.prepare()方法之后調用。那是因為如果當前線程還沒有Looper,是不能調用Looper.loop()方法開啟消息輪詢的,否則會報錯。
5、不要在主線程調用Looper.prepare()方法。這是因為在Android系統創建主線程的時候就已經調用了Looper.prepare()方法和Looper.loop()方法,這也是為什么我們在主線程使用Handler時不需要調用這兩個方法的原因。
6、當我們在子線程使用Handler時,如果Handler不再需要發送和處理消息,那么一定要退出子線程的消息輪詢。因為Looper.loop()方法里是一個死循環,如果我們不主動結束它,那么它就會一直運行,子線程也會一直運行而不會結束。退出消息輪詢的方法是:

    Looper.myLooper().quit();
    Looper.myLooper().quitSafely();

上面的例子都是用線程自己的Looper來創建Handler,我們也可以用指定的Looper來創建Handler。

    new Thread(new Runnable() {
        @Override
        public void run() {
            //獲取主線程的Looper
            Looper looper = Looper.getMainLooper();
            //用主線程的Looper創建Handler
            handler = new Handler(looper) {
                @Override
                public void handleMessage(Message msg) {
                //這里是運行在主線程的
                }
            };
        }
    }).start();

上面的例子中,我們雖然是在子線創建Handler,但因為用的是主線程的Looper,所以消息的處理是在主線程的,這跟在主線程創建Handler是一樣的。因為這里并沒有使用到子線程的Looper,所以不要調用Looper.prepare()和Looper.loop()方法。
以上我們所說的是Handler切換線程的使用。Handler除了提供post()方法和sendMessage()方法以外,還提供了一系列的發送消息的方法。比如延時發送消息和定時發送消息:

    //延時發送消息
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    public final boolean postDelayed(Runnable r, long delayMillis);

    //定時發送消息
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    public final boolean postAtTime(Runnable r, long uptimeMillis);
    public final boolean postAtTime(Runnable r, Object token, long uptimeMillis);

通過這些方法,可以實現延時執行方法和定時執行方法的功能。如下面的例子:

    Handler handler = new Handler();
     handler.postDelayed(new Runnable() {
         @Override
         public void run() {
             Log.i(TAG, "延時1000毫秒打印");
         }
     },1000);

上面的例子并沒有涉及到線程的切換,只是利用了Handler延時發送消息的功能達到延時打印。所以Handler的使用不僅僅是切換線程。更多的方法使用就不一一舉例了。
小知識點:
1、使用Message.obtain()來獲取一個消息。前面我們的例子中獲取一個消息都是用new的方式直接創建,我這樣做只是為了方便大家的理解而已。在使用中不推薦用這種方式來獲取一個消息,而是使用Message.obtain()方法。

Message msg = Message.obtain();

Android會為Message提供一個緩存池,把使用過的消息緩存起來,方便下次使用。我們用Message.obtain()方法獲取一個消息時,會先從緩存池獲取,如果緩存池沒有消息,才會去創建消息。這樣做可以優化內存。
2、同一個Message不要發送兩次。如下面的代碼是有問題的:

    //同一個Message發送了兩次
    Message msg = Message.obtain();
    handler.sendMessage(msg);
    handler.sendMessage(msg);

這是因為所以的消息都是發送到MessageQueue存放,然后等待處理的。如果一個Message對象在MessageQueue里,再次把它存到MessageQueue時就會報錯。
3、Android已經提供了很多實現了Handler的類和方法,方便我們使用。如Activity類的runOnUiThread()方法,View的post()方法,HandlerThread類等,關于這些知識,大家可以查閱相關資料,這里就不做講解了,因為他們的實現其實跟我們前面說的是一樣的。

文章已同步到我的CSDN博客http://blog.csdn.net/u010177022/article/details/63278070

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容