Handler機(jī)制實(shí)現(xiàn)原理

面試的時(shí)候經(jīng)常會(huì)問handler原理啥的,前段時(shí)間剛好看了一個(gè)老師講handle機(jī)制酗失,老師講得很仔細(xì)清晰,這里我自己也用代碼模擬安卓handler實(shí)現(xiàn)一個(gè)基本線程通信。
說到handler就不得不說消息處理的五大組成部分:Message心赶,Handler,Message Queue缺猛,Looper和ThreadLocal缨叫。首先簡(jiǎn)要的了解這些對(duì)象的概念椭符;

Message:message就是一個(gè)數(shù)據(jù)模型吧,它的作用僅限于線程之間通信的時(shí)候傳遞消息耻姥,他可以攜帶少量數(shù)據(jù)销钝,用于線程之間傳遞信息,常用的四個(gè)字段target琐簇,what蒸健,obj,arg;

target:消息回調(diào)后的作用域類婉商,通常是一個(gè)handler似忧。
what:是一個(gè)區(qū)分不同消息的標(biāo)識(shí)符。
obj:這是obj是一個(gè)對(duì)象類型丈秩,可以攜帶自定義的類盯捌。
arg:int類型,攜帶的參數(shù)蘑秽。
下面貼出Message代碼:

public class Message {
    Handler target;
    public int what;
    public Object obj;

    @Override
    public String toString() {
        return obj.toString();
    }
}
handler:它主要用于發(fā)送和接收消息饺著,有三個(gè)主要方法,這里實(shí)現(xiàn)基本功能肠牲,不探討具體細(xì)節(jié)

sendMessage()幼衰;
dispatchMessage();
handleMessage()缀雳;

它主要用于發(fā)送和處理消息的發(fā)送消息一般使用sendMessage()方法,還有其他的一系列sendXXX的方法渡嚣,但最終都是調(diào)用了sendMessageAtTime方法,把消息壓入消息隊(duì)列中。
而發(fā)出的消息經(jīng)過一系列的輾轉(zhuǎn)處理后俏险,最終會(huì)傳遞到Handler的handleMessage方法中严拒。這里我們handleMessage()方法在外部重寫,內(nèi)部實(shí)現(xiàn)調(diào)用竖独。

public class Handler {

    private MessageQueue mQueue;
    private Looper mLooper;

    // Handler的初始化在主線程中完成
    public Handler(){
        //獲取主線程的looper對(duì)象
        mLooper = Looper.myLooper();
        this.mQueue = mLooper.mQueue;
    }

    // 發(fā)送消息裤唠,壓入隊(duì)列
    public void sendMessage(Message msg){
        msg.target = this;
        mQueue.enqueueMessage(msg);
    }

    //內(nèi)部調(diào)用,外部實(shí)現(xiàn)
    public void handleMessage(Message msg){

    }

    // 轉(zhuǎn)發(fā)
    public void dispatchMessage(Message msg){
        handleMessage(msg);
    }
}
MessageQueue是消息隊(duì)列莹痢,主要存放所有handler發(fā)送過來的消息种蘸,這些消息會(huì)一直存放消息隊(duì)列中,等待被處理竞膳,每一個(gè)線程只有一直MessageQueue隊(duì)列航瞭。

這里實(shí)現(xiàn)的消息隊(duì)列里面有一個(gè)入棧和出棧函數(shù),這兩個(gè)函數(shù)的關(guān)系是一個(gè)生產(chǎn)者和消費(fèi)者的關(guān)系坦辟,我們?cè)贚ooper.loop方法中通過while死循環(huán)方法不斷檢測(cè)生產(chǎn)者方法刊侯,一旦消息隊(duì)列不為空,立即取出消息并處理锉走,同時(shí)消息的總數(shù)量也相應(yīng)需要改變滨彻。

public class MessageQueue {

    //通過數(shù)組的結(jié)構(gòu)存儲(chǔ)message對(duì)象
    Message[] items;

    //入隊(duì)和出隊(duì)元素索引位置
    int putIndex;
    int takeIndex;

    // 計(jì)數(shù)器
    int count;


    // synchronized (msg){} 代碼塊加鎖
    // 互斥鎖
    Lock lock;
    // 條件變量
    Condition notEmpty;
    Condition notFull;

    public MessageQueue(){
        this.items = new Message[50];

        this.lock = new ReentrantLock();   //這個(gè)鎖和sychronize還是有些區(qū)別的藕届,ReentrantLock 類實(shí)現(xiàn)了 Lock ,它擁有與synchronized 相同的并發(fā)性和內(nèi)存語義亭饵,但是添加了類似輪詢鎖休偶、定時(shí)鎖等候和可中斷鎖等候的一些特性。此外辜羊,它還提供了在激烈爭(zhēng)用情況下更佳的性能踏兜。(換句話說,當(dāng)許多線程都想訪問共享資源時(shí)八秃,JVM 可以花更少的時(shí)候來調(diào)度線程碱妆,把更多時(shí)間用在執(zhí)行線程上。)
        this.notEmpty = lock.newCondition();
        this.notFull = lock.newCondition();
    }

    // 加入隊(duì)列
    // 生產(chǎn)
    public void enqueueMessage(Message msg){
        System.out.println("加入隊(duì)列");

        try {
            lock.lock();

            //消息隊(duì)列滿了喜德,子線程停止發(fā)送消息山橄,阻塞
            while (count == items.length){
                try {
                    notFull.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            items[putIndex] = msg;
            //循環(huán)取值
            putIndex = (++ putIndex == items.length)?0:putIndex;
            count ++;

            //生產(chǎn)出來新的message對(duì)象垮媒,通知主線程
            notEmpty.signalAll();

        }finally {
            lock.unlock();
        }
    }

    // 出隊(duì)列
    // 消費(fèi)
    public Message next(){

        //消息隊(duì)列空了舍悯,子線程停止發(fā)送消息,阻塞
        Message msg = null;
        try {
            lock.lock();

            while (count == 0){
                try {
                    notEmpty.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            msg = items[takeIndex];
            items[takeIndex] = null;   //元素重置空
            takeIndex = (++takeIndex == items.length) ? 0 : takeIndex;
            count --;

            //使用列一個(gè)message對(duì)象睡雇,通知子線程萌衬,可以繼續(xù)生產(chǎn)
            notFull.signalAll();

        }finally {
            lock.unlock();
        }

        return msg;
    }
}
Looper:每個(gè)線程通過Handler發(fā)送的消息都保存在,MessageQueue中它抱,Looper通過調(diào)用loop()的方法秕豫,就會(huì)進(jìn)入到一個(gè)無限循環(huán)當(dāng)中,然后每當(dāng)發(fā)現(xiàn)Message Queue中存在一條消息观蓄,就會(huì)將它取出混移,并傳遞到Handler的handleMessage()方法中。每個(gè)線程中只會(huì)有一個(gè)Looper對(duì)象侮穿。

/**
 *  一個(gè)線程對(duì)應(yīng)一個(gè)looper對(duì)象歌径,一個(gè)looper對(duì)應(yīng)一個(gè)消息隊(duì)列
 */

public final class Looper {

    //每一個(gè)主線程都有一個(gè)looper對(duì)象
    //Looper 對(duì)象保存在threadlocal中,保證列線程數(shù)據(jù)的隔離
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    // 一個(gè)looper對(duì)象 對(duì)應(yīng)一個(gè)消息隊(duì)列
    MessageQueue mQueue;

    private Looper(){
        mQueue = new MessageQueue();
    }

    //Looper對(duì)象初始化
    public static void prepare(){
        if (sThreadLocal.get()!=null){
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

    //獲取當(dāng)前線程的looper對(duì)象
    public static Looper myLooper(){
        return sThreadLocal.get();
    }

    //輪詢消息隊(duì)列
    public static void loop(){
        Looper me = myLooper();
        if (me == null){
            throw new RuntimeException("not Looper; Looper.prepare() wait call");
        }
        MessageQueue queue = me.mQueue;
        for (;;){
            Message msg = queue.next();
            if (msg == null){
                continue;
            }
            // 轉(zhuǎn)發(fā)給handler
            msg.target.dispatchMessage(msg);
        }
    }
}
在Main方法中寫個(gè)測(cè)試?yán)?/h5>
    public void test(){
        //輪詢器初始化
        Looper.prepare();

        // 主線程當(dāng)中
        final Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                System.out.println(Thread.currentThread().getName() + ",receive:"+msg.toString());
            }
        };

        for (int i=0;i<10;i++){
            new Thread(){
                @Override
                public void run() {
                    while (true){
                        Message msg = new Message();
                        msg.what = 1;
                        synchronized (UUID.class){
                            msg.obj = Thread.currentThread().getName()+",send message"+ UUID.randomUUID().toString();
                        }
                        System.out.println(msg);
                        handler.sendMessage(msg);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }

        //開啟輪詢
        Looper.loop();
    }

打印結(jié)果如下:

// 08-18 19:13:28.149 8051-8129/com.dcw.handler I/System.out: Thread-5,send message41e677cd-0eb0-467b-a633-36a2e9c0cdc0
// 08-18 19:13:28.149 8051-8129/com.dcw.handler I/System.out: 加入隊(duì)列
// 08-18 19:13:28.149 8051-8136/com.dcw.handler I/System.out: Thread-12,send messageebd27bf8-b157-466a-bab5-8d6057334ed2
// 08-18 19:13:28.150 8051-8136/com.dcw.handler I/System.out: 加入隊(duì)列
// 08-18 19:13:28.150 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-5,send message41e677cd-0eb0-467b-a633-36a2e9c0cdc0
// 08-18 19:13:28.150 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-12,send messageebd27bf8-b157-466a-bab5-8d6057334ed2
// 08-18 19:13:28.154 8051-8137/com.dcw.handler I/System.out: Thread-13,send message3c1ae78b-8940-4a12-bb60-815442917edc
// 08-18 19:13:28.155 8051-8137/com.dcw.handler I/System.out: 加入隊(duì)列
// 08-18 19:13:28.155 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-13,send message3c1ae78b-8940-4a12-bb60-815442917edc
// 08-18 19:13:28.159 8051-8134/com.dcw.handler I/System.out: Thread-10,send messagef6beb349-975c-4e17-a475-d3cc7c5bd94f
// 08-18 19:13:28.159 8051-8134/com.dcw.handler I/System.out: 加入隊(duì)列
// 08-18 19:13:28.159 8051-8135/com.dcw.handler I/System.out: Thread-11,send message8d8ecf6e-3c76-4bad-b7af-dc916cb96b39
// 08-18 19:13:28.159 8051-8135/com.dcw.handler I/System.out: 加入隊(duì)列
// 08-18 19:13:28.160 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-10,send messagef6beb349-975c-4e17-a475-d3cc7c5bd94f
// 08-18 19:13:28.160 8051-8051/com.dcw.handler I/System.out: main,receive:Thread-11,send message8d8ecf6e-3c76-4bad-b7af-dc916cb96b39
// 08-18 19:13:29.149 8051-8130/com.dcw.handler I/System.out: Thread-6,send messagec7afaa49-e6b0-49a9

結(jié)果分析可以看到:只要子線程中一加入消息亲茅,那么looper就會(huì)輪詢從massagequeue中取出消息并且通過dispatchMessage發(fā)送到主線程中取執(zhí)行回铛。我個(gè)人覺得所謂主線程,只不過比子線程中多了一個(gè)looper克锣,我們的UI線程在只能在主線中刷新茵肃,就是應(yīng)為線程的loop方法不斷輪詢繪制的原因,子線程之所有不能刷新UI袭祟,是因?yàn)樽泳€程沒有l(wèi)oop方法验残,如果我們把子線程中設(shè)置一個(gè)looper,那么子線程也是可以刷新繪制UI的巾乳。
以上是我個(gè)人見解您没,歡迎大家斧正故俐。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市紊婉,隨后出現(xiàn)的幾起案子药版,更是在濱河造成了極大的恐慌,老刑警劉巖喻犁,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件槽片,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡肢础,警方通過查閱死者的電腦和手機(jī)还栓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來传轰,“玉大人剩盒,你說我怎么就攤上這事】埽” “怎么了辽聊?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)期贫。 經(jīng)常有香客問我跟匆,道長(zhǎng),這世上最難降的妖魔是什么通砍? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任玛臂,我火速辦了婚禮,結(jié)果婚禮上封孙,老公的妹妹穿的比我還像新娘迹冤。我一直安慰自己,他們只是感情好虎忌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布泡徙。 她就那樣靜靜地躺著,像睡著了一般呐籽。 火紅的嫁衣襯著肌膚如雪锋勺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天狡蝶,我揣著相機(jī)與錄音庶橱,去河邊找鬼。 笑死贪惹,一個(gè)胖子當(dāng)著我的面吹牛苏章,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼枫绅,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼泉孩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起并淋,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤寓搬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后县耽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體句喷,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年兔毙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了唾琼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡澎剥,死狀恐怖锡溯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哑姚,我是刑警寧澤祭饭,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蜻懦,受9級(jí)特大地震影響甜癞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宛乃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蒸辆。 院中可真熱鬧征炼,春花似錦、人聲如沸躬贡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拂玻。三九已至酸些,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間檐蚜,已是汗流浹背魄懂。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闯第,地道東北人市栗。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親填帽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛛淋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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

  • 【Android Handler 消息機(jī)制】 前言 在Android開發(fā)中,我們都知道不能在主線程中執(zhí)行耗時(shí)的任務(wù)...
    Rtia閱讀 4,835評(píng)論 1 28
  • 預(yù)熱 在寫這篇文章前我不止一次的問自己篡腌,網(wǎng)上分析Handler機(jī)制原理的文章那么多褐荷,為啥還要畫蛇添足啊嘹悼?不是說前人...
    吳七禁閱讀 2,525評(píng)論 7 23
  • 去密云的古北水鎮(zhèn)看看诚卸,是公司作為整個(gè)忙忙碌碌的五月工作的一次嘉獎(jiǎng),也是一次在計(jì)劃之外的旅行绘迁。我與同事五人一行合溺,開開...
    繾綣東吳丶閱讀 253評(píng)論 0 1
  • 有人來就有人走棠赛,有人走就有人來,這就是部隊(duì)的紀(jì)律膛腐,無論干部戰(zhàn)士睛约,都有面臨這一天的到來。算不上建議哲身,也是這些年和退...
    cc96b6dab081閱讀 386評(píng)論 0 1
  • 七個(gè)音符帶給我們的東西太美妙了辩涝,可惜很多歌我都已經(jīng)聽過,現(xiàn)在不知道聽什么了勘天,歌慌怔揩,真的。 流行樂脯丝,聽多了商膊,都會(huì)覺得...
    嶸書閱讀 130評(píng)論 0 0