5分鐘完全理解android handler

Handler機制簡介

Handler是android中最重要組成部分之一鞭铆,Handler機制可以看做是一個消息阻塞隊列撵溃,APP啟動后很快就進入死循環(huán)(while循環(huán)),不斷的讀取消息隊列中的消息,每個線程最多只有一個消息隊列,沒有消息時就阻塞了牛,有就立馬執(zhí)行。所有消息排隊執(zhí)行辰妙,因為是一個線程,所以同時只能執(zhí)行一個消息甫窟。android的view繪制密浑,事件響應(點擊,觸摸屏幕等)都是把消息發(fā)送到了主線程的消息隊列粗井,等待android APP的執(zhí)行(這點可以通過手動拋出異常查看錯誤堆棧來驗證)尔破。包括自己在主線程new 的handler最終也是把消息插入到了主線程消息隊列中。從以上來看android主線程大部分時間是空閑的浇衬。當點擊屏幕后手機能立馬響應也可以看出android主線程大部分時間是空閑的懒构。雖然主線程要處理的事情狠多,很雜耘擂,很瑣碎(view布局胆剧、繪制,事件分發(fā)等等),但處理時間都很短暫秩霍,可以保證很快處理完畢篙悯,然后等待下一個消息的到來。android handler機制簡可以實現(xiàn)所有view相關的操作都在主線程進行铃绒,從而避免了使用 鎖 鸽照。具體實現(xiàn)代碼 如下。

java工程實現(xiàn)Handler機制代碼

下面的代碼跟android的handler機制主要原理完全一致颠悬,但不依賴android系統(tǒng)矮燎。


import com.handler.Handler;
import com.handler.Looper;
import com.handler.Message;

public class Main {

    public static void main(String[] args) {

        new Main().start();
        
    }
    
    private void start(){
        //創(chuàng)建該線程唯一的消息隊列,線程安全的阻塞隊列
        Looper.prepare();

        onCreate();
        
        //死循環(huán)赔癌,阻塞式诞外,執(zhí)行下面代碼后主線程就會去獲取消息隊列里的消息,沒有消息時就阻塞届榄,有就執(zhí)行浅乔。執(zhí)行Looper.loop前即使消息隊列里有消息,消息也不會執(zhí)行铝条,因為主線程還沒有去檢查消息隊列靖苇。
        Looper.loop();
        
        //下面 的代碼通常不會執(zhí)行,除非手動讓主線程消息隊列退出班缰。退出主線程消息隊列后android的view布局贤壁、繪制,事件分發(fā)就不執(zhí)行了埠忘,所以android APP也沒必要繼續(xù)執(zhí)行了脾拆,所以android采用了拋出異常的方式結束APP。
        System.out.println("exit........");
        throw new RuntimeException("Main thread loop unexpectedly exited");

    }
    private void onCreate() {
        //////////////////////////////////////////////////////////
        ////// 下面的操作相當于運行在android的UI線程中 ////////////
        //////////////////////////////////////////////////////////

        final Thread thread = Thread.currentThread();
        System.out.println("main thread=" + thread);

        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //若thread == Thread.currentThread()莹妒,則證明已經(jīng)運行在主線程中了
                System.out.println("current thread is main thread? " + (thread == Thread.currentThread()));
                System.out.println(msg);
                System.out.println();
            }
        };
        // 測試1       主線程創(chuàng)建handler名船,子線程使用該handler發(fā)送消息 
        new Thread() {
            public void run() {
                try {//模擬耗時操作
                    Thread.sleep(1000 * 2);
                } catch (InterruptedException e) {
                }
                Message message = new Message();
                message.obj = "new Thread" + Thread.currentThread();
                message.what = (int) System.currentTimeMillis();
                //在子線程中發(fā)送消息 
                handler.sendMessage(message);
                
                try {
                    Thread.sleep(1000 * 2);
                } catch (InterruptedException e) {
                }
                
                message = new Message();
                message.obj = "hanler...waht==1" ;
                message.what = 1;
                //在子線程中發(fā)送消息 
                handler.sendMessage(message);
                

                message = new Message();
                message.obj = "hanler...waht==2" ;
                message.what = 2;
                //在子線程中發(fā)送消息 
                handler.sendMessage(message);
                
                message = new Message();
                message.obj = "hanler...waht==3" ;
                message.what = 3;
                //在子線程中發(fā)送消息 
                handler.sendMessage(message);
                
            };
        }.start();

        // 測試2 在thread內部創(chuàng)建handler,結果會拋出異常
        new Thread() {
            public void run() {
                try {
                    sleep(1000 * 3);
                } catch (InterruptedException e) {
                }
                /*
                 * 在線程內部使用默認構造函數(shù)創(chuàng)建handler會拋出異常旨怠。
                 * android中也可以在子線程中創(chuàng)建Handler渠驼,但要在初始化時傳入Looper,
                 * Looper.getMainLooper()獲取到的就是主線程的Looper鉴腻,所以可以這樣創(chuàng)建
                 * 
                 * new Handler(Looper.getMainLooper()){
                        @Override
                        public void handleMessage(Message msg) {
                            //運行在主線程中
                        }
                    };
                 */
                Handler h = new Handler() {
                    public void handleMessage(Message msg) {

                        System.out.println("haneler msg...." + msg);
                    };
                };

                Message message = new Message();
                message.obj = "handler in new Thread";
                message.what = (int) System.currentTimeMillis();
                //在子線程中發(fā)送消息 
                h.sendMessage(message);

            };
        }.start();

        //////////////////////////////////////////////////////////
        ////// 上面的操作相當于運行在android的UI線程中 ////////////
        //////////////////////////////////////////////////////////

    }
}


運行結果


main thread=Thread[main,5,main]  
current thread is main thread? true  
what=18175614 obj=new ThreadThread[Thread-0,5,main]  
  
Exception in thread "Thread-1" java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()  
    at com.handler.Handler.<init>(Handler.java:14)  
    at Main$3$1.<init>(Main.java:103)  
    at Main$3.run(Main.java:103)  
current thread is main thread? true  
what=1 obj=hanler...waht==1  
  
current thread is main thread? true  
what=2 obj=hanler...waht==2  
  
current thread is main thread? true  
what=3 obj=hanler...waht==3  

Handler代碼


package com.handler;  
  
  
public class Handler {  
  
  
    private MessageQueue messageQueue;  
      
    public Handler() {  
  
        Looper looper=Looper.myLooper();  
          
        if (looper==null) {  
             throw new RuntimeException(  
                        "Can't create handler inside thread that has not called Looper.prepare()");  
                 
        }  
          
        this.messageQueue=looper.messageQueue;  
    }  
  
    public void sendMessage(Message msg) {  
          
        //Looper循環(huán)中發(fā)現(xiàn)message后迷扇,調用message.targer就得到了當前handler,使用taget.handleMessage  
        //就把消息轉發(fā)給了發(fā)送message時的handler的handleMessage函數(shù)  
        msg.target=this;  
          
        messageQueue.enqueueMessage(msg);  
          
    }  
      
    public void handleMessage(Message msg) {  
    }  
}  

Looper代碼


package com.handler;

public class Looper {


    private static final ThreadLocal<Looper> threadLocal=new ThreadLocal<>();
    /**
     * 存儲Message的隊列爽哎,阻塞式蜓席,沒有消息則一直等待
     */
    final MessageQueue messageQueue;
    
    
    private Looper() {
        messageQueue=new MessageQueue();
    }

    /**為該線程創(chuàng)建Looper,
     * 若該線程已經(jīng)有Looper了則不需要再次調用prepare
     */
    public  static void prepare() {
        if (threadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        threadLocal.set(new Looper() );
    }
    
    public static void loop() {
        Looper looper=myLooper();
        if (looper == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        MessageQueue messageQueue=looper.messageQueue;
        
        for(;;){
            Message message=messageQueue.next();
            message.target.handleMessage(message);
        }
    }
    
    /**
     * 獲取當先線程的Looper
     * @return
     */
    public static Looper myLooper() {
        return threadLocal.get();
    }
}


MessageQueued代碼


package com.handler;  
  
import java.util.concurrent.BlockingQueue;  
import java.util.concurrent.LinkedBlockingQueue;  
  
public class MessageQueue {  
  
  
    private BlockingQueue<Message>blockingQueue=new LinkedBlockingQueue<>();  
      
    /** 
     * 阻塞式课锌,沒有消息則一直等待 
     * @return 
     */  
    public Message next() {  
        try {  
            return blockingQueue.take();  
        } catch (InterruptedException e) {  
            throw new RuntimeException();  
        }  
    }  
      
    /** 
     * 插入到消息隊列尾部 
     * @param message 
     */  
    void enqueueMessage(Message message) {  
        try {  
            blockingQueue.put(message);  
        } catch (InterruptedException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
        }  
    }  
}  

ThreadLocal簡單實現(xiàn)

ThreadLocal內部原理和下面實現(xiàn)方式不同厨内,但達到的效果是相同的,本篇主要介紹Handler機制,簡化了ThreadLocal


package com.handler;  
  
import java.util.HashMap;  
import java.util.Map;  
/** 
 * ThreadLocal簡單實現(xiàn) 
 * @author Young 
 * 
 * @param <T> 
 */  
public class ThreadLocal<T> {  
  
  
    private Map<Thread,T>map;  
  
    public ThreadLocal() {  
        map=new HashMap<>();  
    }  
      
    public void set(T obj) {  
        map.put(Thread.currentThread(),obj);  
    }  
      
    public T get() {  
        return map.get(Thread.currentThread());  
    }  
      
}  

Message代碼


package com.handler;  
  
public class Message {  
  
    Handler target;  
    public Object obj;  
    public int what;  
  
    @Override  
    public String toString() {  
        return   "what="+what+" obj="+obj.toString();  
    }  
      
}  


以上就是android Handler機制原理代碼了隘庄。

android還提供了HandlerThread踢步,其實是對Handler和Thread的封裝。

先看一下HandlerThread使用方式


Handler myHandler;
 new HandlerThread("Compress-Thread") {  
            @Override  
            protected void onLooperPrepared() {  
                super.onLooperPrepared();  
                myHandler = new Handler();  
                myHandler.post(new Runnable(){
                   @Override  
                    public void run() {  
                                //在HandlerThread線程執(zhí)行
                    } 
                    
                });
            }  
        }.start();

//不要在這使用myHandler發(fā)送消息丑掺,因為myHandler是在onLooperPrepared中創(chuàng)建的获印,onLooperPrepared又是運行在HandlerThread線程的,所以剛執(zhí)行到這時HandlerThread線程可能還沒有創(chuàng)建完街州,onLooperPrepared也就不會執(zhí)行兼丰,myHandler自然是null

每次new HandlerThread都會創(chuàng)建一個新線程,當我們使用myHandler發(fā)送消息時消息就會在HandlerThread線程執(zhí)行唆缴。HandlerThread線程內部也是一個死循環(huán)鳍征,通常不會退出,當通過myHandler為其發(fā)送消息時就會從阻塞中醒來執(zhí)行消息面徽,執(zhí)行完消息隊列里的消息后就又阻塞艳丛。HandlerThread可以排隊執(zhí)行消息,保證能按加入消息的先后順序執(zhí)行趟紊。比如我們需要壓縮很多圖片時氮双,就可以使用HandlerThread,主線程直接把圖片通過myHandler發(fā)送到HandlerThread霎匈,HandlerThread就可以執(zhí)行圖片壓縮戴差,所有壓縮任務都按添加順序依次執(zhí)行。

來自我的博客

http://blog.csdn.net/qingchunweiliang/article/details/50448365

END

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末铛嘱,一起剝皮案震驚了整個濱河市暖释,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌墨吓,老刑警劉巖球匕,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異帖烘,居然都是意外死亡谐丢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門蚓让,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人讥珍,你說我怎么就攤上這事历极。” “怎么了衷佃?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵趟卸,是天一觀的道長。 經(jīng)常有香客問我,道長锄列,這世上最難降的妖魔是什么图云? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮邻邮,結果婚禮上竣况,老公的妹妹穿的比我還像新娘。我一直安慰自己筒严,他們只是感情好丹泉,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鸭蛙,像睡著了一般摹恨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上娶视,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天晒哄,我揣著相機與錄音,去河邊找鬼肪获。 笑死寝凌,一個胖子當著我的面吹牛,可吹牛的內容都是我干的贪磺。 我是一名探鬼主播硫兰,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寒锚!你這毒婦竟也來了劫映?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤刹前,失蹤者是張志新(化名)和其女友劉穎泳赋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喇喉,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡祖今,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拣技。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片千诬。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖膏斤,靈堂內的尸體忽然破棺而出徐绑,到底是詐尸還是另有隱情,我是刑警寧澤莫辨,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布傲茄,位于F島的核電站毅访,受9級特大地震影響,放射性物質發(fā)生泄漏盘榨。R本人自食惡果不足惜喻粹,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望草巡。 院中可真熱鬧守呜,春花似錦、人聲如沸捷犹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萍歉。三九已至侣颂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間枪孩,已是汗流浹背憔晒。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蔑舞,地道東北人拒担。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像攻询,于是被迫代替她去往敵國和親从撼。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內容