用Java實現(xiàn)一個類似AndroidHandler的消息循環(huán)

Android的Handler是Android應(yīng)用層App運行的核心乞巧,App除了InputEventReceiver外,其他的代碼運行摊鸡,比如生命周期绽媒,UI繪制都是被封裝成了Message消息的形式被Looper循環(huán)處理的。消息循環(huán)的休眠喚醒是依賴linux的epoll機(jī)制實現(xiàn)的免猾。最終效果就是app在有任務(wù)的時候被喚醒執(zhí)行任務(wù)是辕,沒有任務(wù)的時候就休眠,App不會在沒有任務(wù)的時候空轉(zhuǎn)CPU消耗資源猎提。

上面的不明白的可以參考下其他文章获三。

好,現(xiàn)在明白了Handler的整個原理锨苏,那如果讓我們自己來實現(xiàn)一個類似Android的Handler的框架結(jié)構(gòu)疙教,應(yīng)該怎么實現(xiàn)呢?
首先一定會有一個數(shù)據(jù)結(jié)構(gòu)來存儲所有需要處理的消息伞租,然后每處理一個消息就把消息從隊列移除贞谓。所以這里需要用隊列。定義一個Queue肯夏。

private final Queue<Runnable> mQueue = new LinkedList<>();

private void addRunnable(Runnable runnable) {
    mQueue.add(runnable);
}

public Runnable getRunnable(){
    if (!mQueue.isEmpty()) {
        return mQueue.poll();
    }
    return null;
}

然后會有一個執(zhí)行代碼的線程经宏,肯定會有一個死循環(huán)不斷從queue里拿Runnable出來。然后運行runnable驯击。

while (true) {
    Runnable runnable = getRunnable();
    if (runnable != null) {
        runnable.run();
    }
}

再梳理下思路烁兰。Queue保存所有需要處理的任務(wù),我們這里任務(wù)先用Runnable表示徊都』φ澹可以在任意一個線程調(diào)用Queue的add方法來添加Runnable。然后另外有個線程里是下面這個while(true)暇矫,不斷從Queue里取runable來執(zhí)行主之。
好,這里突然發(fā)現(xiàn)李根,添加runnable和運行runnable涉及到跨線程了槽奕。所以這里需要處理下線程安全問題。所以把代碼改造成下面這樣房轿。

private static final Queue<Runnable> mQueue = new LinkedList<>();

private static void addRunnable(Runnable runnable) {
    synchronized (mQueue) {
        mQueue.add(runnable);
    }
}

private static Runnable getRunnable() {
    synchronized (mQueue) {
        if (!mQueue.isEmpty()) {
            return mQueue.poll();
        }
    }
    return null;
}

public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("創(chuàng)建Runnable的線程id = " + Thread.currentThread().getId());
            addRunnable(new Runnable() {
            @Override
            public void run() {
                System.out.println("執(zhí)行Runnable的線程id = " + Thread.currentThread().getId());
            }
        });
        }
     }).start();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    while (true) {
        Runnable runnable = getRunnable();
        if (runnable != null) {
            runnable.run();
        }
     }
}

運行上面的代碼控制臺會打釉猎堋:

創(chuàng)建Runnable的線程id = 11
執(zhí)行Runnable的線程id = 1

添加了線程同步所森,這里不會因為跨線程導(dǎo)致的數(shù)據(jù)同步問題了。再梳理下思路夯接,線程11向queue addRunnable焕济,這時候假如有個其他線程正在add或者get,也不要緊盔几,synchronized會自動幫我們處理線程同步晴弃,保證add一定不會和其他線程發(fā)生沖突。不會導(dǎo)致add丟失逊拍。這時候add操作是在線程1完成的上鞠。然后線程1無限getRunnable,拿到后run執(zhí)行runnable芯丧。
這么想似乎勉強(qiáng)實現(xiàn)了消息隊列旗国,也實現(xiàn)了runnable在其他線程創(chuàng)建,然后被線程1執(zhí)行注整。
但是一運行就會發(fā)現(xiàn)一個嚴(yán)重的問題。因為線程1是個while(true)度硝,而且循環(huán)體內(nèi)沒有任何線程休眠肿轨,這會導(dǎo)致線程1,即使在沒有runnable需要執(zhí)行的時候蕊程,依然在不斷從queue里取數(shù)據(jù)出來椒袍,不斷的取,無限消耗CPU資源藻茂。這肯定是不行的驹暑。硬件資源都給這哥們用來空跑了。
那我們有沒有什么辦法能讓線程1智能一點辨赐,讓線程1能在沒有runnable的時候線程休眠不消耗CPU优俘,然后等有任務(wù)來了,再喚醒去執(zhí)行任務(wù)掀序,讓CPU資源只用來處理任務(wù)帆焕,而不是空轉(zhuǎn)?
關(guān)鍵點來了不恭,怎么讓線程1知道自己什么時候休眠什么時候喚醒呢叶雹?Android里這里利用的是Linux的epoll機(jī)制來實現(xiàn)的。效果就是線程1沒有任務(wù)了就休眠不消耗資源换吧,任務(wù)來了折晦,被喚醒去執(zhí)行任務(wù)。
Java里Object有兩個方法final native方法wait和notifyAll沾瓦,如果用過满着,熟悉這兩個方法的話谦炒,這里就有思路了。當(dāng)線程1沒有任務(wù)的時候進(jìn)入wait不再消耗cpu漓滔,然后其他任意線程addRunnable的時候notifyAll喚醒線程1编饺,然后線程1喚醒執(zhí)行runnable,然后再runnable執(zhí)行完成之后再次進(jìn)入wait狀態(tài)响驴。
我們把上面代碼改造成下面這樣:

private static final Queue<Runnable> mQueue = new LinkedList<>();

private static void addRunnable(Runnable runnable) {
    synchronized (mQueue) {
        mQueue.notifyAll();
        mQueue.add(runnable);
    }
}

private static Runnable getRunnable() {
    synchronized (mQueue) {
        if (!mQueue.isEmpty()) {
            return mQueue.poll();
        } else {
            try {
                mQueue.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    return null;
}

public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("創(chuàng)建Runnable的線程id = " + Thread.currentThread().getId());
            addRunnable(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執(zhí)行Runnable的線程id = " + Thread.currentThread().getId());
                }
            });
            }
        }).start();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    int index = 0;
    while (true) {
        System.out.println("第" + (++index) + "次循環(huán)");
        Runnable runnable = getRunnable();
        if (runnable != null) {
            runnable.run();
        }
    }
}

運行結(jié)果:

創(chuàng)建Runnable的線程id = 11
第1次循環(huán)
執(zhí)行Runnable的線程id = 1
第2次循環(huán)

循環(huán)第一次執(zhí)行任務(wù)透且,循環(huán)第二次就掛起等待新任務(wù)到來。到這里基本上Handler的基礎(chǔ)東西都有了豁鲤。剩下的我們可以仿造AndroidHandler的Api重新包裝一個Handler-Message-Looper是非常容易的秽誊。下面的就不多解釋了,可以直接參考這里的源碼琳骡。
https://github.com/aesean/MessageQueue

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锅论,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子楣号,更是在濱河造成了極大的恐慌最易,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件炫狱,死亡現(xiàn)場離奇詭異藻懒,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)视译,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門嬉荆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人酷含,你說我怎么就攤上這事鄙早。” “怎么了椅亚?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵限番,是天一觀的道長。 經(jīng)常有香客問我什往,道長扳缕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任别威,我火速辦了婚禮躯舔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘省古。我一直安慰自己粥庄,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布豺妓。 她就那樣靜靜地躺著惜互,像睡著了一般布讹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上训堆,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天描验,我揣著相機(jī)與錄音,去河邊找鬼坑鱼。 笑死膘流,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲁沥。 我是一名探鬼主播呼股,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼画恰!你這毒婦竟也來了彭谁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤允扇,失蹤者是張志新(化名)和其女友劉穎缠局,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體考润,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡甩鳄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了额划。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡档泽,死狀恐怖俊戳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情馆匿,我是刑警寧澤抑胎,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站渐北,受9級特大地震影響阿逃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赃蛛,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一恃锉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呕臂,春花似錦破托、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽州既。三九已至,卻和暖如春萝映,著一層夾襖步出監(jiān)牢的瞬間吴叶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工序臂, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留蚌卤,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓贸宏,卻偏偏與公主長得像造寝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吭练,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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