手把手帶你實(shí)現(xiàn)Handler

在開始實(shí)現(xiàn)之前我們需要一個(gè)Handler模型進(jìn)行推導(dǎo)

handler.png

ThreadLocal:

呵呵呵,沒有我你們能干啥?

synchronized:笑而不語

Lock:樓上sb?還沒被開除?

Condition:2樓sb

Handler:氣氛Hi起來!動(dòng)次打次!

Message:朋友圈幫我集個(gè)贊!

Looper:.......

MessageQueue:退群了

旁白:我們通過上圖的模型可以推導(dǎo)出,要想實(shí)現(xiàn)Handler機(jī)制少不了以下的一些隊(duì)友的配合

1.Message

TA就是一個(gè)對(duì)象,封裝了一些信息,使用者可以將自己想要的信息傳到里面去!里面只有幾個(gè)成員變量,沒別的!

private int what;//用過Handler的肯定經(jīng)常用吧

private Object;//跟上面的成員變量一個(gè)意思

private Hander target;//這是個(gè)重點(diǎn),為什么要封裝一個(gè)Handler對(duì)象呢?因?yàn)樽罱K處理消息的是Handler啊.如果沒有這個(gè)對(duì)象,我們?nèi)绾?調(diào)用)把消息傳遞給Handler的handleMessage方法呢?

2.MessageQueue

a.MessageQueue 是一個(gè)鏈表結(jié)構(gòu)的消息隊(duì)列(數(shù)組實(shí)現(xiàn))

b.主要提供存,取,對(duì)存取的線程進(jìn)行鎖控制,避免多線程并發(fā)訪問的問題.

c.MessageQueue可提供的功能列表:

void enqueueMessage(Message message);//將消息對(duì)象添加到鏈表中

Message next();//從鏈表中取出消息對(duì)象

boolean hasNext();//消息隊(duì)列中的數(shù)據(jù)是否為空

3.Looper

死循環(huán)!死循環(huán)!死循環(huán)!死循環(huán)!死循環(huán)!死循環(huán)!死循環(huán)!死循環(huán)!

Looper中可提空的功能列表:

void prepare();//初始化Looper,將Looper綁定到ThreadLocal

void loop();//開始死循環(huán)的從MessageQueue中取出消息對(duì)象.

Looper myLooper();//獲取ThreadLocal中存儲(chǔ)的Looper對(duì)象

4.Handler

負(fù)責(zé)發(fā)送消息,處理消息

可提供的功能列表:

void handleMessage(Message message);

void sendMessage(Message message);//發(fā)送消息就是將你的Message對(duì)象放到MessageQueue中去

void dispatchMessage(Message message);//沒別的,就是調(diào)用了一下handleMessage,之所以會(huì)有這個(gè)方法是因?yàn)槲覀兏侠淼倪M(jìn)行封裝.當(dāng)Looper.loop方法循環(huán)取消息時(shí)會(huì)調(diào)用這個(gè)方法.

5.ThreadLocal

在線程中扮演了很重要的角色

引:ThreadLocal是解決線程安全問題一個(gè)很好的思路急侥,它通過為每個(gè)線程提供一個(gè)獨(dú)立的變量副本解決了變量并發(fā)訪問的沖突問題。在很多情況下坏怪,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問題更簡單绊茧,更方便,且結(jié)果程序擁有更高的并發(fā)性.

在Handler中使用ThreadLocal來進(jìn)行數(shù)據(jù)隔離,目的就是更好的隔離Looper對(duì)象,畢竟我們的Looper是存儲(chǔ)在了ThreadLocal 的map中.

6.Lock,Condition

為了解決線程鎖,隊(duì)列為空,或者滿員的問題,當(dāng)隊(duì)列為空或者隊(duì)列滿員的情況我們要通過Lock去阻塞(消息不能被添加到隊(duì)列中,取出消息的時(shí)候也同理),直到隊(duì)列中有消息被消費(fèi)了(處理了).畢竟我們得隊(duì)列長度是有限的嘛,要不然就內(nèi)存溢出了嘛,老天爺會(huì)懲罰你的嘛.

Condition:與Lock配對(duì)使用,我們這里之所以用Condition而不用synchronized的原因就是Condition 與Lock配合,可以指定"誰"來解鎖.顯而易見的用synchronized來實(shí)現(xiàn)是非常麻煩的,Lock和Condition 更適合復(fù)雜的并發(fā)場(chǎng)景.

OK!我們經(jīng)過了上面的小圖片的推導(dǎo),大體的結(jié)構(gòu)就已經(jīng)出來了

首先Handler中的代碼:

public class Handler {

    private MessageQueue mMessageQueue;
    private Looper myLooper;
    public void handleMessage(Message message){

    }
    //handler 在主線程中初始化
    public Handler(){
        //拿到Looper
        myLooper = Looper.myLooper();
        //拿到Looper中的消息隊(duì)列

        mMessageQueue = myLooper.messageQueue;

    }
    public void dispathMessage(Message message){
        handleMessage(message);
    }
    public void sendMessage(Message message){
        //發(fā)送消息就是將消息實(shí)體入隊(duì)到消息隊(duì)列中
        message.target = this;
        mMessageQueue.enqueueMessage(message);

    }

}

Looper中的代碼:

public class Looper {


    MessageQueue messageQueue;
    public static void prepare(){
        if (mThreadLocal.get() != null){
            throw new RuntimeException("Only on Looper may be created per thread");
        }
        mThreadLocal.set(new Looper());
    }
    public Looper(){
        messageQueue = new MessageQueue();
    }
    private static final ThreadLocal<Looper> mThreadLocal = new ThreadLocal<>();

    public static void loop(){
       Looper me =  myLooper();
       if (me ==null){
           throw  new RuntimeException("thread looper is null.call Looper.prepare()");
       }
       //獲取到loop中的消息隊(duì)列
        MessageQueue messageQ = me.messageQueue;
        for (;;){

            if (!messageQ.hasNext()){
                continue;
            }else {
                Message message = messageQ.next();
                //如果消息為null,繼續(xù)處理下一個(gè)
                if (message ==null){
                    continue;
                }
                //拿到消息,將消息發(fā)送給Handler
                message.target.dispathMessage(message);
            }
        }
    }
    public static Looper myLooper(){
        if (mThreadLocal.get() ==null){

            throw new RuntimeException("not call Looper.prepare()");
        }
        return mThreadLocal.get();
    }
}

Message中的代碼:

public class Message {
     Handler target;//消息的處理對(duì)象
    public int what;
    public Object object;

    @Override
    public String toString() {
        return object.toString()+":what:"+what;
    }
}

MessageQueue中的代碼

public class MessageQueue {

    /**
     * 到底能不能再子線程中更新UI?
     * Android 通過判斷當(dāng)前線程是否是ViewRootImpl的創(chuàng)建線程來檢查是否可以更新UI
     * 調(diào)用View.addView的時(shí)候會(huì)調(diào)用初始化ViewRootImpl,也就是說如果我在子線程中addView,
     * 那么我就可以在子線程中更新View.
     */
    private int MAX_QUEUE = 50;
    /**
     *  消息隊(duì)列應(yīng)該有上限,否則會(huì)造成內(nèi)存溢出
        當(dāng)隊(duì)列沒有消息的時(shí)候需要阻塞,直到有消息進(jìn)來
        當(dāng)隊(duì)列消息滿的時(shí)候需要阻塞,直到有消息被處理
     */
    Message[] messageQueue;

    private Lock mLock;//鎖對(duì)象,為了可以控制消息隊(duì)列什么時(shí)候可以繼續(xù)出隊(duì)或者入隊(duì)
    private Condition popCondition;//可以選擇性的解鎖
    private Condition pushCondition;

    private int messageQueueCout;//隊(duì)列中的消息的當(dāng)前是數(shù)量
    private int pushIndex;//入隊(duì)時(shí)的索引
    private int popIndex;//出隊(duì)時(shí)的索引
    //出隊(duì)消息:運(yùn)行在子線程中

    public void enqueueMessage(Message message){
        try {
            //加鎖類似于synchronized (),因?yàn)镃ondition可以選擇性解鎖
            mLock.lock();
            //當(dāng)消息隊(duì)列滿了,子線程停止發(fā)送消息,阻塞
            //為了避免多個(gè)子線程同級(jí)喚醒,導(dǎo)致的單次判斷問題.使用while 不斷進(jìn)行檢查.
            while (messageQueueCout == MAX_QUEUE){
                pushCondition.await();
            }
            //將消息推進(jìn)數(shù)組中
            messageQueue[pushIndex] = message;
            pushIndex++;
            if (pushIndex == MAX_QUEUE){
                pushIndex = 0;
            }
            messageQueueCout++;
            //如果有產(chǎn)品被生產(chǎn),通知消費(fèi)者可以繼續(xù)消費(fèi)Message
            popCondition.signalAll();//??
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }

    }
    //出隊(duì)消息:運(yùn)行在主線程中(消費(fèi)隊(duì)列中的消息)
    public Message next(){
        Message message = null;
        try {
            //當(dāng)消息隊(duì)列為空的時(shí)候阻塞消費(fèi)者,讓消費(fèi)者不能繼續(xù)消費(fèi)消息
            mLock.lock();
            while (messageQueueCout ==0){
                popCondition.await();
            }

            message = messageQueue[popIndex];
            messageQueue[popIndex] = null;//當(dāng)一個(gè)數(shù)據(jù)被出隊(duì)時(shí),釋放內(nèi)存
            popIndex++;
            if (popIndex == MAX_QUEUE){
                popIndex=0;
            }
            //當(dāng)隊(duì)列數(shù)量不滿時(shí)通知子線程可以繼續(xù)入隊(duì)消息
            messageQueueCout--;

            //通知生產(chǎn)者可以繼續(xù)生產(chǎn)Message
            pushCondition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            mLock.unlock();
        }

        return message;
    }
    public boolean hasNext(){
        return messageQueue.length!=0;
    }
    public MessageQueue(){
        //初始化消息隊(duì)列
        messageQueue = new Message[MAX_QUEUE];
        mLock = new ReentrantLock();//初始化鎖對(duì)象
        this.pushCondition = mLock.newCondition();
        this.popCondition = mLock.newCondition();

    }

}

好的!我們來寫個(gè)Main方法調(diào)用一下!

public class Main {

    public static void main(String[] args) {

        Looper.prepare();
        Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message message) {
                System.out.println(message);
            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message m = new Message();
                m.object="hello";
                mHandler.sendMessage(m);
            }
        }).start();
        Looper.loop();
    }
}

這就是為什么在Activity main方法中會(huì)調(diào)用 Looper.prepare()...Looper.loop();
因?yàn)槟阋獙⒆约撼志没絻?nèi)存中去嘛!要不然就死掉了嘛!
通過一個(gè)死循環(huán)+Handler
當(dāng)我們?cè)谧泳€程中創(chuàng)建Handler的時(shí)候,也要模仿Activity的Handler創(chuàng)建方式才對(duì)嘛!
在哪個(gè)線程中創(chuàng)建了Handler 對(duì)于Handler本身來說誰就是主線程.因?yàn)閷?duì)于Handler來說線程是相對(duì)關(guān)系的!

  public class Activity{
    public static main(String[] args){
      ......
      Looper.prepareMainLoop();
      ............
      Looper.loop();
    }
  }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末亡笑,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子仑乌,更是在濱河造成了極大的恐慌百拓,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件衙传,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蓖捶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門腺阳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人穿香,你說我怎么就攤上這事绎速。” “怎么了纹冤?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長萌京。 經(jīng)常有香客問我,道長知残,這世上最難降的妖魔是什么靠瞎? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任乏盐,我火速辦了婚禮,結(jié)果婚禮上父能,老公的妹妹穿的比我還像新娘。我一直安慰自己何吝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布爱榕。 她就那樣靜靜地躺著,像睡著了一般呆细。 火紅的嫁衣襯著肌膚如雪型宝。 梳的紋絲不亂的頭發(fā)上絮爷,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音坑夯,去河邊找鬼。 笑死柜蜈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的淑履。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秘噪,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了指煎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤至壤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后像街,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晋渺,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡葫掉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俭厚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片户魏。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叼丑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸠信,到底是詐尸還是另有隱情,我是刑警寧澤论寨,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站绰垂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏火焰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一昌简、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧纯赎,春花似錦、人聲如沸址否。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仗考。三九已至,卻和暖如春秃嗜,著一層夾襖步出監(jiān)牢的瞬間权均,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工叽赊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人必指。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像塔橡,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子葛家,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

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