自己動手擼一個Handler

一、關(guān)于Handler

Handler對于我們Android開發(fā)者來說應(yīng)該是再熟悉不過了,這也是在Android中最重要的消息機制次兆,特別是在面試筆試時,Handler機制也是最常問到的話題锹锰。今天我們就來動手擼一個自己寫的Handler芥炭,用java層代碼方式來實現(xiàn),進一步來了解Handler在線程通信過程中的作用恃慧。

二园蝠、問題

Handler機制也可以理解為線程間的消息機制,如果我們自己來設(shè)計Handler實現(xiàn)線程間通信痢士,需要怎么做呢彪薛?我們知道,在Handler機制中怠蹂,最重要的幾個類:Handler善延、LooperMessageQueue城侧、Message挚冤、ThreadLocal。那它們在具體實現(xiàn)中又有什么作用呢赞庶?

三训挡、思考

首先,從使用者角度來看歧强,他的操作只有兩步:

  1. 在主線程創(chuàng)建Handler實例澜薄,并重寫handleMessage方法處理消息。
  1. 在子線程獲取Handler的引用調(diào)用sendMessage方法發(fā)送消息摊册,在handleMessage中即可處理該消息肤京。

那從設(shè)計者角度來看,我們要分清HandlerLooper忘分、MessageQueue棋枕、MessageThreadLocal這幾個類都擔(dān)當了什么職責(zé):

  1. Handler 負責(zé)發(fā)送和處理消息
  2. Looper 消息泵妒峦,也就是負責(zé)取出消息交給Handler來處理重斑。
  3. MessageQueue 消息隊列,負責(zé)存取消息肯骇。
  4. Message 具體發(fā)送的消息窥浪。
  5. ThreadLocal 它主要用于做線程間的數(shù)據(jù)隔離用的,這里它在每個線程中存放各自對應(yīng)的Looper笛丙。

好了漾脂,簡單分析完各個類的作用,那我們開始挽起袖子擼代碼吧胚鸯。

四骨稿、實現(xiàn)

原理圖

1、 Handler的實現(xiàn)

由于Handler主要負責(zé)發(fā)送和處理消息姜钳,那我們主要實現(xiàn)它的sendMessage坦冠、sendMessagedispatchMessage三個方法傲须,來處理消息的發(fā)送和接收:

public class Handler {
      //消息隊列
     MessageQueue mQueue;
      //Looper
     Looper mLooper;

     public Handler() {
         mLooper = Looper.myLooper();
         if (mLooper == null) {
             throw new RuntimeException(
                 "Can't create handler inside thread that has not called Looper.prepare()");
         }
         mQueue = mLooper.mQueue;
    }
     public final void sendMessage(Message msg){
         MessageQueue queue = mQueue;
         if (queue != null) {
             msg.target = this;
              queue.enqueueMessage(msg);
         }else {
             RuntimeException e = new RuntimeException(
                 this + " sendMessage() called with no mQueue");
             throw e;
         }
     }

    /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
    
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        handleMessage(msg);
    }
}

我們在Handler的構(gòu)造函數(shù)中獲取當前線程對應(yīng)的looper,并取出Looper中對應(yīng)的消息隊列保存在成員變量中趟脂。sendMessage方法中我們給Messagetarget變量賦值為this泰讽,也就是表明了Message是由當前的Handler來負責(zé)處理的,之后調(diào)用enqueueMessage方法將消息存入消息隊列中昔期。而dispatchMessage方法我們實現(xiàn)比較簡單已卸,負責(zé)調(diào)用handleMessage來處理消息。

2硼一、 Looper的實現(xiàn)

Looper主要負責(zé)取出消息交由Handler處理累澡,我們主要來實現(xiàn)prepareloop方法:

public class Looper {

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    MessageQueue mQueue;

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

    public static Looper myLooper() {
        return sThreadLocal.get();
    }

    public static void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException(
                    "Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }

    public static void loop() {

        Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException(
                    "No Looper; Looper.prepare() wasn't called on this thread.");
        }
        
        MessageQueue queue = me.mQueue;
         
        for (;;) {
            Message msg = queue.next();
            if (msg == null || msg.target == null)
                continue;
            //轉(zhuǎn)發(fā)給handler
            msg.target.dispatchMessage(msg);
        }
    }
}

Looper的構(gòu)造函數(shù)中我們創(chuàng)建了對應(yīng)的消息隊列來存取消息般贼,并且在prepare方法中存入ThreadLocal當前線程的Looper愧哟,loop方法從當前線程的Looper的消息隊列中取出消息,最終調(diào)用msg.target.dispatchMessage(msg)交友之前發(fā)送消息的Handler來處理消息哼蛆。

3蕊梧、Message的實現(xiàn)

Message的實現(xiàn)比較簡單:

public final class Message {
    //處理該消息的Handler 
    Handler target;
    public int what;
    
    public Object obj;
    
    @Override
    public String toString() {
        return obj.toString();
    }
}

4、MessageQueue消息隊列的實現(xiàn)

在消息隊列的實現(xiàn)中我們主要考慮幾個問題:

  1. 用什么數(shù)據(jù)結(jié)構(gòu)存放消息腮介,存放數(shù)據(jù)大小有限制肥矢。
  2. next()方法取出消息時,消息隊列沒有消息叠洗,該方法應(yīng)阻塞甘改。
  3. enqueueMessage方法存放消息時旅东,消息大于存放消息限制大小,應(yīng)阻塞十艾。
//消息隊列
public class MessageQueue {
    //互斥鎖
    Lock lock;
    //條件變量
    Condition mEmptyQueue;
    Condition mFullQueue;
    //消息
    Message[] mMessages;
    //裝入  和取出消息的下標
    int putIndex;
    int takeIndex;
    //記錄數(shù)   用于判斷是否繼續(xù)生產(chǎn)和消費
    int count;
    public MessageQueue(){
        //初始化50個消息
        mMessages = new Message[50];
        lock = new ReentrantLock();
        //標示
        mEmptyQueue = lock.newCondition();
        mFullQueue = lock.newCondition();
    }
    //生產(chǎn)者 子線程
    final void enqueueMessage(Message msg){
        //添加至消息隊列
        try{
            lock.lock();
            while(count == mMessages.length){
                try {
                    mFullQueue.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            mMessages[putIndex] = msg;
            putIndex = (++putIndex == mMessages.length ? 0 : putIndex);
            count++;
            //通知主線程繼續(xù)執(zhí)行
            mEmptyQueue.signalAll();
        }finally{
            lock.unlock();
        }
        
    }
    //消費者  主線程
    final Message next(){
        //取出消息
        Message message = null;
        try{
            lock.lock();
            //取到最后一個
            while (count == 0) {
                try {
                    mEmptyQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
             message = mMessages[takeIndex];
             mMessages[takeIndex] = null;
            takeIndex = (++takeIndex == mMessages.length ? 0 : takeIndex);
            count--;
            //通知子線程   
            mFullQueue.signalAll();
        }finally{
            lock.unlock();
        }
        return message;
    }
}

這里的nextenqueueMessage是典型的生產(chǎn)者抵代、消費者的關(guān)系,為防止出現(xiàn)錯亂我們給兩個方法都加上Lock鎖疟羹,當enqueueMessage方法存放消息時如果當前隊列消息滿了主守,則調(diào)用mFullQueue.await();進行等待消息處理,當向消息隊列中存放消息后榄融,也就是說消息隊列不為空了参淫,調(diào)用mEmptyQueue.signalAll();通知next()方法來處理消息。

至此愧杯,我們的Handler消息處理過程已經(jīng)基本完成了涎才,下面我們測試下看看:

5、測試

public class Test {

    public static void main(String[] args) {
        //初始化Looper
        Looper.prepare();
        
        final Handler hander = new Handler(){
            public void handleMessage(Message msg) {
                System.out.println(Thread.currentThread().getName() + "--receiver--" + msg.toString());
            };
        };
        
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                public void run() {
                    while (true) {
                        Message msg = new Message();
                        msg.what = 0;
                        synchronized (UUID.class) {
                            msg.obj = Thread.currentThread().getName()+"--send---"+UUID.randomUUID().toString();
                        }
                        System.out.println(msg);
                        hander.sendMessage(msg);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        //開始消息循環(huán)
        Looper.loop();
    }
}

看下測試結(jié)果:

Thread-0--send---e4ed9a81-3477-4f5d-a663-1d30626d93b5
Thread-8--send---4c403131-ec14-406c-b57b-a4943dfa93ac
Thread-7--send---9752a85d-9517-4607-a54f-92c2342b7a28
Thread-6--send---7d4ee443-3ab5-4c4e-aac5-eeafc26b78d9
Thread-9--send---70ba7292-1ff4-404d-974e-dfedb1a3fa71
Thread-4--send---614e07e6-bc39-45be-93b0-6996de7f159e
Thread-2--send---7bfaa831-a31b-457a-82cd-145a9d98d351
Thread-5--send---8ffd7327-6ddb-4088-93e6-1304fc926814
Thread-1--send---f6d5e373-88b0-44e9-ab51-f95808acb068
main--receiver--Thread-0--send---e4ed9a81-3477-4f5d-a663-1d30626d93b5
main--receiver--Thread-8--send---4c403131-ec14-406c-b57b-a4943dfa93ac
main--receiver--Thread-7--send---9752a85d-9517-4607-a54f-92c2342b7a28
main--receiver--Thread-6--send---7d4ee443-3ab5-4c4e-aac5-eeafc26b78d9
main--receiver--Thread-9--send---70ba7292-1ff4-404d-974e-dfedb1a3fa71
main--receiver--Thread-4--send---614e07e6-bc39-45be-93b0-6996de7f159e
main--receiver--Thread-2--send---7bfaa831-a31b-457a-82cd-145a9d98d351
main--receiver--Thread-5--send---8ffd7327-6ddb-4088-93e6-1304fc926814
Thread-3--send---56f8b613-99fa-4ef2-a4b9-c762c4d0cd27
main--receiver--Thread-1--send---f6d5e373-88b0-44e9-ab51-f95808acb068
main--receiver--Thread-3--send---56f8b613-99fa-4ef2-a4b9-c762c4d0cd27

測試成功A拧耍铜!我們自己的Handler也可以正常處理消息啦~

五、總結(jié)

Handler源碼的實現(xiàn)過程要比我們自己的復(fù)雜很多跌前,特別是消息處理的細節(jié)棕兼,調(diào)用了底層C++的代碼。但實現(xiàn)的整體思路和我們是一樣的抵乓,通過動手實踐一次伴挚,加深對Handler的理解,對我們認識和處理消息機制的問題大有裨益灾炭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末茎芋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蜈出,更是在濱河造成了極大的恐慌田弥,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铡原,死亡現(xiàn)場離奇詭異偷厦,居然都是意外死亡,警方通過查閱死者的電腦和手機燕刻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門沪哺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酌儒,你說我怎么就攤上這事辜妓。” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵籍滴,是天一觀的道長酪夷。 經(jīng)常有香客問我,道長孽惰,這世上最難降的妖魔是什么晚岭? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮勋功,結(jié)果婚禮上坦报,老公的妹妹穿的比我還像新娘。我一直安慰自己狂鞋,他們只是感情好片择,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骚揍,像睡著了一般字管。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上信不,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天嘲叔,我揣著相機與錄音,去河邊找鬼抽活。 笑死硫戈,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的下硕。 我是一名探鬼主播丁逝,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼卵牍!你這毒婦竟也來了果港?” 一聲冷哼從身側(cè)響起沦泌,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤糊昙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后谢谦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體释牺,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年回挽,在試婚紗的時候發(fā)現(xiàn)自己被綠了没咙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡千劈,死狀恐怖祭刚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤涡驮,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布暗甥,位于F島的核電站,受9級特大地震影響捉捅,放射性物質(zhì)發(fā)生泄漏撤防。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一棒口、第九天 我趴在偏房一處隱蔽的房頂上張望寄月。 院中可真熱鬧,春花似錦无牵、人聲如沸漾肮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽初橘。三九已至,卻和暖如春充岛,著一層夾襖步出監(jiān)牢的瞬間保檐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工崔梗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留夜只,地道東北人。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓蒜魄,卻偏偏與公主長得像扔亥,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谈为,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

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