Handler機(jī)制

Android消息循環(huán)流程圖如下所示:

image

主要涉及的角色如下所示:

message:消息體疯潭,用于裝載需要發(fā)送的對(duì)象。

MessageQueue:消息隊(duì)列,負(fù)責(zé)消息的存儲(chǔ)與管理唤锉,負(fù)責(zé)管理由 Handler 發(fā)送過來的 Message。讀取會(huì)自動(dòng)刪除消息别瞭,單鏈表維護(hù)窿祥,在插入和刪除上有優(yōu)勢(shì)。無限循環(huán)判斷next()方法中是否有消息蝙寨,有就返回這條消息并移除晒衩。

Looper:消息循環(huán)器,負(fù)責(zé)關(guān)聯(lián)線程以及消息的分發(fā)墙歪,在該線程下從 MessageQueue獲取 Message听系,分發(fā)給Handler,Looper創(chuàng)建的時(shí)候會(huì)創(chuàng)建一個(gè)MessageQueue虹菲,調(diào)用loop()方法的時(shí)候消息循環(huán)開始靠胜,其中會(huì)不斷調(diào)用messageQueue的next()方法,當(dāng)有消息就處理,否則阻塞在messageQueue的next()方法中浪漠。當(dāng)Looper的quit()被調(diào)用的時(shí)候會(huì)調(diào)用messageQueue的quit()陕习,此時(shí)next()會(huì)返回null,然后loop()方法也就跟著退出址愿。

Handler:消息處理器该镣,負(fù)責(zé)發(fā)送并處理消息,面向開發(fā)者响谓,提供 API损合,并隱藏背后實(shí)現(xiàn)的細(xì)節(jié)。

整個(gè)消息的循環(huán)流程還是比較清晰的娘纷,具體說來:

1嫁审、Handler通過sendMessage()發(fā)送消息Message到消息隊(duì)列MessageQueue。

2失驶、Looper通過loop()不斷提取觸發(fā)條件的Message土居,并將Message交給對(duì)應(yīng)的target handler來處理。

3嬉探、target handler調(diào)用自身的handleMessage()方法來處理Message擦耀。

事實(shí)上,在整個(gè)消息循環(huán)的流程中涩堤,并不只有Java層參與眷蜓,很多重要的工作都是在C++層來完成的。我們來看下這些類的調(diào)用關(guān)系胎围。

image

注:虛線表示關(guān)聯(lián)關(guān)系吁系,實(shí)線表示調(diào)用關(guān)系。

在這些類中MessageQueue是Java層與C++層維系的橋梁白魂,MessageQueue與Looper相關(guān)功能都通過MessageQueue的Native方法來完成汽纤,而其他虛線連接的類只有關(guān)聯(lián)關(guān)系,并沒有直接調(diào)用的關(guān)系福荸,它們發(fā)生關(guān)聯(lián)的橋梁是MessageQueue蕴坪。

總結(jié)

Handler 發(fā)送的消息由 MessageQueue 存儲(chǔ)管理,并由 Looper 負(fù)責(zé)回調(diào)消息到 handleMessage()敬锐。

線程的轉(zhuǎn)換由 Looper 完成背传,handleMessage() 所在線程由 Looper.loop() 調(diào)用者所在線程決定。

Handler 引起的內(nèi)存泄露原因以及最佳解決方案

Handler 允許我們發(fā)送延時(shí)消息台夺,如果在延時(shí)期間用戶關(guān)閉了 Activity径玖,那么該 Activity 會(huì)泄露。 這個(gè)泄露是因?yàn)?Message 會(huì)持有 Handler颤介,而又因?yàn)?Java 的特性梳星,內(nèi)部類會(huì)持有外部類赞赖,使得 Activity 會(huì)被 Handler 持有,這樣最終就導(dǎo)致 Activity 泄露丰泊。

解決:將 Handler 定義成靜態(tài)的內(nèi)部類薯定,在內(nèi)部持有 Activity 的弱引用,并在Acitivity的onDestroy()中調(diào)用handler.removeCallbacksAndMessages(null)及時(shí)移除所有消息瞳购。

為什么我們能在主線程直接使用 Handler,而不需要?jiǎng)?chuàng)建 Looper 亏推?

通常我們認(rèn)為 ActivityThread 就是主線程学赛。事實(shí)上它并不是一個(gè)線程,而是主線程操作的管理者吞杭。在 ActivityThread.main() 方法中調(diào)用了 Looper.prepareMainLooper() 方法創(chuàng)建了 主線程的 Looper ,并且調(diào)用了 loop() 方法盏浇,所以我們就可以直接使用 Handler 了。

因此我們可以利用 Callback 這個(gè)攔截機(jī)制來攔截 Handler 的消息芽狗。如大部分插件化框架中Hook ActivityThread.mH 的處理绢掰。

主線程的 Looper 不允許退出

主線程不允許退出,退出就意味 APP 要掛童擎。

Handler 里藏著的 Callback 能干什么滴劲?

Handler.Callback 有優(yōu)先處理消息的權(quán)利 ,當(dāng)一條消息被 Callback 處理并攔截(返回 true)顾复,那么 Handler 的 handleMessage(msg) 方法就不會(huì)被調(diào)用了班挖;如果 Callback 處理了消息,但是并沒有攔截芯砸,那么就意味著一個(gè)消息可以同時(shí)被 Callback 以及 Handler 處理萧芙。

創(chuàng)建 Message 實(shí)例的最佳方式

為了節(jié)省開銷,Android 給 Message 設(shè)計(jì)了回收機(jī)制假丧,所以我們?cè)谑褂玫臅r(shí)候盡量復(fù)用 Message 双揪,減少內(nèi)存消耗:

通過 Message 的靜態(tài)方法 Message.obtain();

通過 Handler 的公有方法 handler.obtainMessage()包帚。

子線程里彈 Toast 的正確姿勢(shì)

本質(zhì)上是因?yàn)?Toast 的實(shí)現(xiàn)依賴于 Handler渔期,按子線程使用 Handler 的要求修改即可,同理的還有 Dialog婴噩。

妙用 Looper 機(jī)制

將 Runnable post 到主線程執(zhí)行擎场;

利用 Looper 判斷當(dāng)前線程是否是主線程。

主線程的死循環(huán)一直運(yùn)行是不是特別消耗CPU資源呢几莽?

并不是迅办,這里就涉及到Linux pipe/epoll機(jī)制,簡(jiǎn)單說就是在主線程的MessageQueue沒有消息時(shí)章蚣,便阻塞在loop的queue.next()中的nativePollOnce()方法里站欺,此時(shí)主線程會(huì)釋放CPU資源進(jìn)入休眠狀態(tài)姨夹,直到下個(gè)消息到達(dá)或者有事務(wù)發(fā)生,通過往pipe管道寫端寫入數(shù)據(jù)來喚醒主線程工作矾策。這里采用的epoll機(jī)制磷账,是一種IO多路復(fù)用機(jī)制,可以同時(shí)監(jiān)控多個(gè)描述符贾虽,當(dāng)某個(gè)描述符就緒(讀或?qū)懢途w)逃糟,則立刻通知相應(yīng)程序進(jìn)行讀或?qū)懖僮鳎举|(zhì)是同步I/O蓬豁,即讀寫是阻塞的绰咽。所以說,主線程大多數(shù)時(shí)候都是處于休眠狀態(tài)地粪,并不會(huì)消耗大量CPU資源取募。

handler postDelay這個(gè)延遲是怎么實(shí)現(xiàn)的?

handler.postDelay并不是先等待一定的時(shí)間再放入到MessageQueue中蟆技,而是直接進(jìn)入MessageQueue玩敏,以MessageQueue的時(shí)間順序排列和喚醒的方式結(jié)合實(shí)現(xiàn)的。

一质礼、handler的引入:

我們都知道旺聚,Android UI是線程不安全的,如果在子線程中嘗試進(jìn)行UI操作几苍,程序就有可能會(huì)崩潰翻屈。相信大家在日常的工作當(dāng)中都會(huì)經(jīng)常遇到這個(gè)問題,解決的方案應(yīng)該也是早已爛熟于心妻坝,即創(chuàng)建一個(gè)Message對(duì)象伸眶,然后借助Handler發(fā)送出去,之后在Handler的handleMessage()方法中獲得剛才發(fā)送的Message對(duì)象刽宪,然后在這里進(jìn)行UI操作就不會(huì)再出現(xiàn)崩潰了厘贼。具體實(shí)現(xiàn)代碼如下:

package com.example.androidthreadtest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener {

    public static final int UPDATE_TEXT = 1;

    private TextView text;

    private Button changeText;

    private Handler handler =new Handler() {

    public void handleMessage(Message msg) {
        switch (msg.what) {
            case UPDATE_TEXT:
                text.setText("Nice to meet you");
            break;
            default:
            break;
            }
        }
    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        text = (TextView) findViewById(R.id.text);

        changeText = (Button) findViewById(R.id.change_text);

        changeText.setOnClickListener(this);

    }

    @Override

    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.change_text:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message =new Message();
                        message.what = UPDATE_TEXT;
                        handler.sendMessage(message);
                    }
               }).start();
                break;
            default:
                break;
        }
    }
}

上方第56行代碼,官方建議我們寫成:(這樣的話圣拄,可以由系統(tǒng)自己負(fù)責(zé)message的創(chuàng)建和銷毀)

Message msg = handler.obtainMessage();

或者寫成:

Message msg = Message.obtain();

上面的代碼中嘴秸,我們并沒有在子線程中直接進(jìn)行UI操作,而是創(chuàng)建了一個(gè)Message對(duì)象庇谆,并將它的what字段的值指定為了一個(gè)整形常量UPDATE_TEXT岳掐,用于表示更新TextView這個(gè)動(dòng)作。然后調(diào)用Handler的sendMessage()方法將這條Message發(fā)送出去饭耳。很快串述,Handler就會(huì)收到這條Message,并在handleMessage()方法寞肖,在這里對(duì)具體的Message進(jìn)行處理(需要注意的是纲酗,此時(shí)handleMessage()方法中的代碼是在主線程中運(yùn)行的)衰腌。如果發(fā)現(xiàn)Message的what字段的值等于UPDATE_TEXT,就將TextView顯示的內(nèi)容更新觅赊。運(yùn)行程序后右蕊,點(diǎn)擊按鈕,TextView就會(huì)顯示出更新的內(nèi)容吮螺。

注:如果從源碼的角度理解饶囚,粗略的描述是這樣的:

先是調(diào)用了handler的obtainMessage()方法得到Message對(duì)象。在obtainMessage()方法里做的事情是:調(diào)用了Message.obtain(this)方法规脸,把handler作為對(duì)象傳進(jìn)來坯约。在Message.obtain(this)方法里做的事情是:生成message對(duì)象,把handler作為參數(shù)賦值給message的target屬性莫鸭。總的來說横殴,一個(gè)Handler對(duì)應(yīng)一個(gè)Looper對(duì)象被因,一個(gè)Looper對(duì)應(yīng)一個(gè)MessageQueue對(duì)象,使用Handler生成Message衫仑,所生成的Message對(duì)象的Target屬性梨与,就是該對(duì)象。而一個(gè)Handler可以生成多個(gè)Message文狱,所以說粥鞋,Handler和Message是一對(duì)多的關(guān)系。

二瞄崇、異步消息處理機(jī)制:

Handler是Android類庫提供的用于接受呻粹、傳遞和處理消息或Runnable對(duì)象的處理類,它結(jié)合Message苏研、MessageQueue和Looper類以及當(dāng)前線程實(shí)現(xiàn)了一個(gè)消息循環(huán)機(jī)制等浊,用于實(shí)現(xiàn)任務(wù)的異步加載和處理。整個(gè)異步消息處理流程的示意圖如下圖所示:

根據(jù)上面的圖片摹蘑,我們現(xiàn)在來解析一下異步消息處理機(jī)制

Message:消息體筹燕,用于裝載需要發(fā)送的對(duì)象。

handler:它直接繼承自O(shè)bject衅鹿。作用是:在子線程中發(fā)送Message或者Runnable對(duì)象到MessageQueue中撒踪;在UI線程中接收、處理從MessageQueue分發(fā)出來的Message或者Runnable對(duì)象大渤。發(fā)送消息一般使用Handler的sendMessage()方法制妄,而發(fā)出去的消息經(jīng)過處理后最終會(huì)傳遞到Handler的handlerMessage()方法中。

MessageQueue:用于存放Message或Runnable對(duì)象的消息隊(duì)列兼犯。它由對(duì)應(yīng)的Looper對(duì)象創(chuàng)建忍捡,并由Looper對(duì)象管理集漾。每個(gè)線程中都只會(huì)有一個(gè)MessageQueue對(duì)象。

Looper:是每個(gè)線程中的MessageQueue的管家砸脊,循環(huán)不斷地管理MessageQueue接收和分發(fā)Message或Runnable的工作具篇。調(diào)用Looper的loop()方法后,就會(huì)進(jìn)入到一個(gè)無限循環(huán)中然后每當(dāng)發(fā)現(xiàn)MessageQueue中存在一條消息,就會(huì)將它取出焚鹊,并調(diào)用Handler的handlerMessage()方法硼瓣。每個(gè)線程中也只會(huì)有一個(gè)Looper對(duì)象。

了解這些之后埃疫,我們?cè)趤砜匆幌?strong>他們之間的聯(lián)系:

首先要明白的是,Handler和Looper對(duì)象是屬于線程內(nèi)部的數(shù)據(jù)孩哑,不過也提供與外部線程的訪問接口栓霜,Handler就是公開給外部線程的接口,用于線程間的通信横蜒。Looper是由系統(tǒng)支持的用于創(chuàng)建和管理MessageQueue的依附于一個(gè)線程的循環(huán)處理對(duì)象胳蛮,而Handler是用于操作線程內(nèi)部的消息隊(duì)列的,所以Handler也必須依附一個(gè)線程丛晌,而且只能是一個(gè)線程仅炊。

我們?cè)賮?strong>對(duì)異步消息處理的整個(gè)流程梳理一遍:

當(dāng)應(yīng)用程序開啟時(shí),系統(tǒng)會(huì)自動(dòng)為UI線程創(chuàng)建一個(gè)MessageQueue(消息隊(duì)列)和Looper循環(huán)處理對(duì)象澎蛛。首先需要在主線程中創(chuàng)建一個(gè)Handler對(duì)象抚垄,并重寫handlerMessage()方法。然后當(dāng)子線程中需要進(jìn)行UI操作時(shí)谋逻,就創(chuàng)建一個(gè)Message對(duì)象呆馁,并通過Handler將這條消息發(fā)送出去。之后這條消息就會(huì)被添加到MessageQueue的隊(duì)列中等待被處理斤贰,而Looper則會(huì)一直嘗試從MessageQueue中取出待處理消息智哀,并找到與消息對(duì)象對(duì)應(yīng)的Handler對(duì)象,然后調(diào)用Handler的handleMessage()方法荧恍。由于Handler是在主線程中創(chuàng)建的瓷叫,所以此時(shí)handleMessage()方法中的代碼也會(huì)在主線程中運(yùn)行,于是我們?cè)谶@里就可以安心地進(jìn)行UI操作了送巡。

通俗地來講摹菠,一般我們?cè)趯?shí)際的開發(fā)過程中用的比較多一種情況的就是主線程的Handler將子線程中處理過的耗時(shí)操作的結(jié)果封裝成Message(消息),并將該Message(利用主線程里的MessageQueue和Looper)傳遞到主線程中,最后主線程再根據(jù)傳遞過來的結(jié)果進(jìn)行相關(guān)的UI元素的更新骗爆,從而實(shí)現(xiàn)任務(wù)的異步加載和處理次氨,并達(dá)到線程間的通信。

通過上一小節(jié)對(duì)Handler的一個(gè)初步認(rèn)識(shí)后摘投,我們可以很容易總結(jié)出Handler的主要用途煮寡,下面是Android官網(wǎng)總結(jié)的關(guān)于Handler類的兩個(gè)主要用途:

(1)執(zhí)行定時(shí)任務(wù):

指定任務(wù)時(shí)間虹蓄,在某個(gè)具體時(shí)間或某個(gè)時(shí)間段后執(zhí)行特定的任務(wù)操作,例如使用Handler提供的postDelayed(Runnable r,long delayMillis)方法指定在多久后執(zhí)行某項(xiàng)操作幸撕,比如當(dāng)當(dāng)薇组、淘寶、京東和微信等手機(jī)客戶端的開啟界面功能坐儿,都是通過Handler定時(shí)任務(wù)來完成的律胀。

(2)線程間的通信:

在執(zhí)行較為耗時(shí)的操作時(shí),Handler負(fù)責(zé)將子線程中執(zhí)行的操作的結(jié)果傳遞到UI線程貌矿,然后UI線程再根據(jù)傳遞過來的結(jié)果進(jìn)行相關(guān)UI元素的更新炭菌。(上面已有說明)

我們接下來講一下post。
三逛漫、post:

對(duì)于Handler的Post方式來說黑低,它會(huì)傳遞一個(gè)Runnable對(duì)象到消息隊(duì)列中(這句話稍后會(huì)進(jìn)行詳細(xì)解釋),在這個(gè)Runnable對(duì)象中酌毡,重寫run()方法投储。一般在這個(gè)run()方法中寫入需要在UI線程上的操作。

Post允許把一個(gè)Runnable對(duì)象入隊(duì)到消息隊(duì)列中阔馋。它的方法有:post(Runnable)、postAtTime(Runnable,long)娇掏、postDelayed(Runnable,long)呕寝。詳細(xì)解釋如下:

boolean post(Runnable r):把一個(gè)Runnable入隊(duì)到消息隊(duì)列中,UI線程從消息隊(duì)列中取出這個(gè)對(duì)象后婴梧,立即執(zhí)行下梢。

boolean postAtTime(Runnable r,long uptimeMillis):把一個(gè)Runnable入隊(duì)到消息隊(duì)列中,UI線程從消息隊(duì)列中取出這個(gè)對(duì)象后塞蹭,在特定的時(shí)間執(zhí)行孽江。

boolean postDelayed(Runnable r,long delayMillis):把一個(gè)Runnable入隊(duì)到消息隊(duì)列中,UI線程從消息隊(duì)列中取出這個(gè)對(duì)象后番电,延遲delayMills秒執(zhí)行

void removeCallbacks(Runnable r):從消息隊(duì)列中移除一個(gè)Runnable對(duì)象岗屏。

下面通過一個(gè)Demo,講解如何通過Handler的post方式在新啟動(dòng)的線程中修改UI組件的屬性

package com.example.m03_threadtest01;

import android.app.Activity;

import android.os.Bundle; 

import android.os.Handler;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

public class MainActivity extends Activity {

private Button btnMes1,btnMes2;

private TextView tvMessage;

// 聲明一個(gè)Handler對(duì)象

private static Handler handler=new Handler(); 

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);   

btnMes1=(Button)findViewById(R.id.button1);

btnMes2=(Button)findViewById(R.id.button2);

tvMessage=(TextView)findViewById(R.id.TextView1);

btnMes1.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

// 新啟動(dòng)一個(gè)子線程

new Thread(new Runnable() {                   

@Override

public void run() {

// tvMessage.setText("...");

// 以上操作會(huì)報(bào)錯(cuò)漱办,無法再子線程中訪問UI組件这刷,UI組件的屬性必須在UI線程中訪問

// 使用post方式修改UI組件tvMessage的Text屬性

handler.post(new Runnable() {                    3

@Override

public void run() {

tvMessage.setText("使用Handler.post在工作線程中發(fā)送一段執(zhí)行到消息隊(duì)列中,在主線程中執(zhí)行娩井。");                       

}

});                               

}

}).start();

}

});

btnMes2.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View v) {

new Thread(new Runnable() {                   

@Override

public void run() {

// 使用postDelayed方式修改UI組件tvMessage的Text屬性值

// 并且延遲3S執(zhí)行

handler.postDelayed(new Runnable() {

@Override

public void run() {

tvMessage.setText("使用Handler.postDelayed在工作線程中發(fā)送一段執(zhí)行到消息隊(duì)列中暇屋,在主線程中延遲3S執(zhí)行。");   

}

}, 3000);

} 

}).start();

}

});

}}

點(diǎn)擊按鈕洞辣,運(yùn)行結(jié)果如下:

image
image

有一點(diǎn)值得注意的是:對(duì)于Post方式而言咐刨,它其中Runnable對(duì)象的run()方法的代碼(37行至39行或者58至61行)昙衅,均運(yùn)行在主線程上(雖然看上去是寫在子線程當(dāng)中的),如果我們?cè)谶@段代碼里打印日志輸出線程的名字定鸟,會(huì)發(fā)現(xiàn)輸出的是Main Thread的名字而涉。所以對(duì)于這段代碼而言,不能執(zhí)行在UI線程上的操作仔粥,一樣無法使用post方式執(zhí)行婴谱,比如說訪問網(wǎng)絡(luò)。

我們現(xiàn)在來解釋一下上面藍(lán)色字體的那句話:

這個(gè)Runnable對(duì)象被放到了消息隊(duì)列當(dāng)中去了躯泰,然后主線程中的Looper(因?yàn)镠andler是在主線程中生成的谭羔,所以Looper也在主線程中)將這個(gè)Runnable對(duì)象從消息隊(duì)列中取出來,取出來之后麦向,做了些什么呢瘟裸?為什么在執(zhí)行Post的Runnable對(duì)象的run()方法時(shí),不是重新開啟一個(gè)線程呢诵竭?要了解這個(gè)過程话告,只能求助Android的源代碼:

打開源碼的目錄sdk\sources\android-19\android\os,并找到Handler.java這個(gè)文件卵慰。找到post方法:

public final boolean post(Runnable r)

    {

      return sendMessageDelayed(getPostMessage(r), 0);

    }

上方的代碼中沙郭, 可以看到,post方法其實(shí)就一行代碼(326行)裳朋,里面調(diào)用了sendMessageDelayed()這個(gè)方法病线,里面有兩個(gè)參數(shù)。先看一下第一個(gè)參數(shù)getPostMessage(r):(719行)

private static Message getPostMessage(Runnable r) {

        Message m = Message.obtain();

        m.callback = r;

        return m;

    }

上方的代碼中鲤嫡,將Runnable對(duì)象賦值給Message的callback屬性送挑。注:通過查看Message.java文件的源代碼發(fā)現(xiàn),callback屬性是一個(gè)Runnable對(duì)象:(91行)

/package/Runnable callback;

我們?cè)賮矸治鲆幌律戏絞etPostMessage()這個(gè)方法暖眼,該方法完成了兩個(gè)操作:

一是生成了一個(gè)Message對(duì)象惕耕,二是將r對(duì)象復(fù)制給Message對(duì)象的callback屬性。返回的是一個(gè)Message對(duì)象诫肠。

再回到326行:

public final boolean post(Runnable r)

    {

      return sendMessageDelayed(getPostMessage(r), 0);

    } 

這行代碼相當(dāng)于:

public final boolean post(Runnable r)

{

    Message msg = getPostMessage(r);

    return sendMessage(msg);////如果需要延時(shí)的話司澎,這一行可以改為return sendMessageDelayed(msg,0);其中第二個(gè)參數(shù)改為具體的延時(shí)時(shí)間}

現(xiàn)在應(yīng)該好理解了:

第一個(gè)問題,如何把一個(gè)Runnable對(duì)象放到消息隊(duì)列中:實(shí)際上是生成了一個(gè)Message對(duì)象区赵,并將r賦值給Message對(duì)象的callback屬性惭缰,然后再將Message對(duì)象放置到消息隊(duì)列當(dāng)中。

我們?cè)倏纯匆幌翷ooper做了什么笼才。打開Looper.java的dispatchMessage的方法:(136行)

//一個(gè)Handler對(duì)應(yīng)一個(gè)Looper對(duì)象漱受,一個(gè)Looper對(duì)應(yīng)一個(gè)MessageQueue對(duì)象,

//使用Handler生成Message,所生成的Message對(duì)象的Target屬性昂羡,就是該對(duì)象   

//Message msg = handler.obtainMessage();

//發(fā)送一個(gè)message對(duì)象

 //handler.sendMessage(msg);

msg.target.dispatchMessage(msg); 

這里面調(diào)用了dispatchMessage()方法絮记,打開Handler.java的dispatchMessage()方法:(93至104行)

 Handle system messages here. 

public void dispatchMessage(Message msg) { 

if(msg.callback !=null) { 

            handleCallback(msg); 

}else { 

        if(mCallback !=null) { 

                if (mCallback.handleMessage(msg)) {

                        return;

                 }

          }

           handleMessage(msg);

   }

} 

上方第5行代碼:因?yàn)檫@次已經(jīng)給Message的callback屬性賦值了,所以就不為空虐先,直接執(zhí)行這行代碼怨愤。即執(zhí)行handleCallBack()這個(gè)方法。打開handleCallBack()方法的源碼:(732至734行)

private static void handleCallback(Message message) {

        message.callback.run();

    } 

看到這個(gè)方法蛹批,就真相大白了:message的callback屬性直接調(diào)用了run()方法撰洗,而不是開啟一個(gè)新的子線程。

現(xiàn)在可以明白了:

第二個(gè)問題: Looper取出了攜帶有r對(duì)象的Message對(duì)象以后腐芍,做的事情是:取出Message對(duì)象之后差导,調(diào)用了dispatchMessage()方法,然后判斷Message的callback屬性是否為空猪勇,此時(shí)的callback屬性是有值的设褐,所以執(zhí)行了handleCallback(Message message),在該方法中執(zhí)行了 message.callback.run()泣刹。根據(jù)Java的線程知識(shí)助析,我們可以知道,如果直接調(diào)用Thread對(duì)象或者Runnable對(duì)象的run()方法椅您,是不會(huì)開辟新線程的外冀,而是在原有的線程中執(zhí)行。

因?yàn)長(zhǎng)ooper是在主線程當(dāng)中的掀泳,所以dispatchMessage()方法和handleMessage()方法也都是在主線程當(dāng)中運(yùn)行锥惋。所以post()里面的run方法也自然是在主線程當(dāng)中運(yùn)行的。 使用Post()方法的好處在于:避免了在主線程和子線程中將數(shù)據(jù)傳來傳去的麻煩开伏。

四、Message:

Handler如果使用sendMessage的方式把消息入隊(duì)到消息隊(duì)列中遭商,需要傳遞一個(gè)Message對(duì)象固灵,而在Handler中,需要重寫handleMessage()方法劫流,用于獲取工作線程傳遞過來的消息巫玻,此方法運(yùn)行在UI線程上。

對(duì)于Message對(duì)象祠汇,一般并不推薦直接使用它的構(gòu)造方法得到仍秤,而是建議通過使用Message.obtain()這個(gè)靜態(tài)的方法或者Handler.obtainMessage()獲取。Message.obtain()會(huì)從消息池中獲取一個(gè)Message對(duì)象可很,如果消息池中是空的诗力,才會(huì)使用構(gòu)造方法實(shí)例化一個(gè)新Message,這樣有利于消息資源的利用我抠。并不需要擔(dān)心消息池中的消息過多苇本,它是有上限的袜茧,上限為10個(gè)。Handler.obtainMessage()具有多個(gè)重載方法瓣窄,如果查看源碼笛厦,會(huì)發(fā)現(xiàn)其實(shí)Handler.obtainMessage()在內(nèi)部也是調(diào)用的Message.obtain()。

Handler中裳凸,與Message發(fā)送消息相關(guān)的方法有:

Message obtainMessage():獲取一個(gè)Message對(duì)象菠秒。

boolean sendMessage():發(fā)送一個(gè)Message對(duì)象到消息隊(duì)列中,并在UI線程取到消息后禁灼,立即執(zhí)行弄捕。

boolean sendMessageDelayed():發(fā)送一個(gè)Message對(duì)象到消息隊(duì)列中斋荞,在UI線程取到消息后,延遲執(zhí)行悦陋。

boolean sendEmptyMessage(int what):發(fā)送一個(gè)空的Message對(duì)象到隊(duì)列中痢毒,并在UI線程取到消息后菇怀,立即執(zhí)行。

boolean sendEmptyMessageDelayed(int what,long delayMillis):發(fā)送一個(gè)空Message對(duì)象到消息隊(duì)列中搂根,在UI線程取到消息后锦积,延遲執(zhí)行。

void removeMessage():從消息隊(duì)列中移除一個(gè)未響應(yīng)的消息淆储。

五冠场、通過Handler實(shí)現(xiàn)線程間通信:

1、在Worker Thread發(fā)送消息本砰,在MainThread中接收消息:

【實(shí)例】點(diǎn)擊按扭碴裙,將下方的TextView的內(nèi)容修改為“從網(wǎng)絡(luò)中獲取的數(shù)據(jù)”

image

need-to-insert-img

【實(shí)際意義】點(diǎn)擊按鈕時(shí),程序訪問服務(wù)器,服務(wù)器接到請(qǐng)求之后舔株,會(huì)返回字符串結(jié)果莺琳,然后更新到程序。

完整版代碼如下:

XML布局文件代碼如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"  

  android:layout_width="match_parent"  

  android:layout_height="match_parent"  

  android:paddingBottom="@dimen/activity_vertical_margin"

   android:paddingLeft="@dimen/activity_horizontal_margin"  

  android:paddingRight="@dimen/activity_horizontal_margin"  

  android:paddingTop="@dimen/activity_vertical_margin"   

 tools:context=".MainActivity">

<TextView

      android:id="@+id/TextViewId"    

  android:layout_width="match_parent"   

   android:layout_height="wrap_content"    

  android:text="數(shù)據(jù)"/>

<Button

        android:id="@+id/ButtonId"   

     android:layout_width="match_parent"  

      android:layout_height="wrap_content" 

       android:text="發(fā)送消息"

        android:layout_below="@id/TextViewId"/>

</RelativeLayout> 

MainActivity.java代碼如下:

package com.example.test0207_handler; 

import android.app.Activity; 

import android.os.Bundle; 

import android.os.Handler; 

import android.os.Message; 

import android.view.Menu; 

import android.view.View; 

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.TextView;

public class MainActivity extends Activity { 

private TextView textView ; private Button button ;

private Handler handler ;

    @Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

       setContentView(R.layout.activity_main)

textView = (TextView)findViewById(R.id.TextViewId) ;

button = (Button)findViewById(R.id.ButtonId) ;    

handler =new MyHandler() ;

button.setOnClickListener(new ButtonListener()) ;

    }

//在MainAthread線程中接收數(shù)據(jù)载慈,從而修改TextView的值

class MyHandler extends Handler {

       @Override

public void handleMessage(Message msg) {

System.out.println("handleMessage--->"+Thread.currentThread().getName()) ;//得到當(dāng)前線程的名字

**String s = (String)msg.obj ;**

**            textView.setText(s) ;**

        }

    }

//生成線程對(duì)象惭等,讓NetworkThread線程啟動(dòng)

class ButtonListener implements OnClickListener {

        @Override        

public void onClick(View arg0) {

Thread t =new NetworkThread() ;

            t.start();

        }

    }

//在Worker Thread線程中發(fā)送數(shù)據(jù)

class NetworkThread extends Thread {

        @Override 

public void run(){

System.out.println("network--->"+Thread.currentThread().getName()) ;//得到當(dāng)前線程的名字

//模擬訪問網(wǎng)絡(luò):當(dāng)線程運(yùn)行時(shí),首先休眠2秒鐘

try {

Thread.sleep(2*1000) ;

}catch (InterruptedException e) {

                e.printStackTrace();

            }//變量s的值办铡,模擬從網(wǎng)絡(luò)當(dāng)中獲取的數(shù)據(jù)

String s = "從網(wǎng)絡(luò)中獲取的數(shù)據(jù)" ;

//textView.setText(s) ; //這種做法是錯(cuò)誤的辞做,只有在Mainthread中才能操作UI            

**//開始發(fā)送消息**

**Message msg = handler.obtainMessage() ;    **

**msg.obj = s ;**

**handler.sendMessage(msg) ;//sendMessage()方法,在主線程或者Worker Thread線程中發(fā)送寡具,都是可以的秤茅,都可以被取到**

       }

    }    

    @Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if it is present.

       getMenuInflater().inflate(R.menu.main, menu);

return true;

    }

}

這段代碼的結(jié)構(gòu),和最上面的第一章節(jié)是一樣的童叠。

上方代碼中框喳,我們?cè)谧泳€程中休眠2秒來模擬訪問網(wǎng)絡(luò)的操作。

65行:用字符串s表示從網(wǎng)絡(luò)中獲取的數(shù)據(jù)厦坛;70行:然后我們把這個(gè)字符串放在Message的obj屬性當(dāng)中發(fā)送出去五垮,并在主線程中接收(36行)。

運(yùn)行后結(jié)果如下:

image

點(diǎn)擊按鈕后結(jié)果如下:

image

need-to-insert-img

點(diǎn)擊按鈕后粪般,后臺(tái)輸出結(jié)果如下:

need-to-insert-img

image

可以看到拼余,子線程的名字是:Thread-1118,主線程的名字是:main亩歹。

2匙监、在MainThread中發(fā)送消息,在Worker Thread中接收消息:

【實(shí)例】點(diǎn)擊按鈕小作,在在MainThread中發(fā)送消息亭姥,在Worker Thread中接收消息,并在后臺(tái)打印輸出顾稀。

【代碼】完整版代碼如下:

XML布局文件代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    android:paddingBottom="@dimen/activity_vertical_margin"  
    android:paddingLeft="@dimen/activity_horizontal_margin"  
    android:paddingRight="@dimen/activity_horizontal_margin"  
    android:paddingTop="@dimen/activity_vertical_margin"  
    tools:context=".MainActivity">
    <Button
        android:id="@+id/ButtonId"    
        android:layout_width="match_parent"    
        android:layout_height="wrap_content"    
        android:text="在主線程中發(fā)送消息"/>
</RelativeLayout>

MainActivity.java代碼如下:

package com.example.m03_handle01; 
import android.app.Activity;
import android.os.Bundle; 
import android.os.Handler; 
import android.os.Looper; 
import android.os.Message; 
import android.util.Log; 
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity 
{
    private Button button;
    private Handler handler ;
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button)findViewById(R.id.ButtonId);
        //當(dāng)用戶點(diǎn)擊按鈕時(shí)达罗,發(fā)送Message的對(duì)象msg
        button.setOnClickListener(new OnClickListener() 
        {//使用匿名內(nèi)部類為button綁定監(jiān)聽器
            @Override public void onClick(View v) 
            { 
                Log.i("onClick:", Thread.currentThread().getName());
                Message msg = handler.obtainMessage() ;
                handler.sendMessage(msg) ;
            }
        });
        WorkerThread wt =new WorkerThread() ;
        wt.start();
    }
    //在WorkerThread生成handler
    class WorkerThread extends Thread { 
        @Override 
        public void run() {
            //準(zhǔn)備Looper對(duì)象
            Looper.prepare() ;
            //在WorkerThread當(dāng)中生成一個(gè)Handler對(duì)象
             handler =new Handler() {
                 @Override 
                 public void handleMessage(Message msg) {
                     Log.i("handleMessage:", Thread.currentThread().getName());
                     Log.i("后臺(tái)輸出", "收到了消息對(duì)象");
                 }
             };
             //調(diào)用Looper的loop()方法之后,Looper對(duì)象將不斷地從消息隊(duì)列當(dāng)中取出對(duì)象静秆,然后調(diào)用handler的handleMessage()方法粮揉,處理該消息對(duì)象
            //如果消息隊(duì)列中沒有對(duì)象,則該線程阻塞
            Looper.loop() ;//通過Looper對(duì)象將消息取出來
        }
    }
    @Override 
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
}

上方的第42行至54行代碼:這是MainThread中發(fā)送消息抚笔,在Worker Thread中接收消息的固定寫法扶认。上面的三個(gè)步驟再重復(fù)一下:

準(zhǔn)備Looper對(duì)象

在WorkerThread當(dāng)中生成一個(gè)Handler對(duì)象

調(diào)用Looper的loop()方法之后,Looper對(duì)象將不斷地從消息隊(duì)列當(dāng)中取出對(duì)象殊橙,然后調(diào)用handler的handleMessage()方法辐宾,處理該消息對(duì)象狱从;如果消息隊(duì)列中沒有對(duì)象,則該線程阻塞

注意叠纹,此時(shí)handleMessage()方法是在Worker Thread中運(yùn)行的季研。

運(yùn)行程序后,當(dāng)我們點(diǎn)擊按鈕誉察,就會(huì)在后臺(tái)輸出“收到了消息對(duì)象”這句話:

image

小小地總結(jié)一下:

首先執(zhí)行Looper的prepare()方法与涡,這個(gè)方法有兩個(gè)作用:一是生成Looper對(duì)象,二是把Looper對(duì)象和當(dāng)前線程對(duì)象形成鍵值對(duì)(線程為鍵)冒窍,存放在ThreadLocal當(dāng)中递沪,然后生成handler對(duì)象,調(diào)用Looper的myLooper()方法综液,得到與Handler所對(duì)應(yīng)的Looper對(duì)象款慨,這樣的話,handler谬莹、looper 檩奠、消息隊(duì)列就形成了一一對(duì)應(yīng)的關(guān)系,然后執(zhí)行上面的第三個(gè)步驟附帽,即Looper在消息隊(duì)列當(dāng)中循環(huán)的取數(shù)據(jù)埠戳。

Handler是如何實(shí)現(xiàn)延遲消息的,這是個(gè)老生常談的問題了蕉扮。

這里我就帶大家從源碼的角度看看整胃,并把handler各方面實(shí)現(xiàn)查漏補(bǔ)缺一下。

handler核心的發(fā)送消息的方法是sendMessage喳钟,有的朋友會(huì)說那post呢屁使?

post的話其實(shí)算是一個(gè)handler的語法糖,傳入runnable后幫助我們構(gòu)建一個(gè)message奔则。

/**

    * Causes the Runnable r to be added to the message queue.

    * The runnable will be run on the thread to which this handler is

    * attached.

    * 

    * @param r The Runnable that will be executed.

    *

    * @return Returns true if the Runnable was successfully placed in to the

    *        message queue.  Returns false on failure, usually because the

    *        looper processing the message queue is exiting.

    */
public final boolean post(Runnable r)
{
    return sendMessageDelayed(getPostMessage(r),0);
}
private static Message getPostMessage(Runnabler){
    Message m = Message.obtain();
    m.callback=r;
    return m;
}

可以看到getPostMessage里幫我們構(gòu)建出一個(gè)message然后再調(diào)用sendMessageDelayed蛮寂。

接下來看sendMessage,類似于startActivity最終都會(huì)走到startActivityForResult一樣易茬,handler所有發(fā)送消息的方法最終都會(huì)走到sendMessageDelayed酬蹋,只是delayMillis不同而已,這個(gè)delayMillis就是延時(shí)的時(shí)間抽莱。

/**

    * Pushes a message onto the end of the message queue after all pending messages

    * before the current time. It will be received in {@link #handleMessage},

    * in the thread attached to this handler.

    * 

    * @return Returns true if the message was successfully placed in to the

    *        message queue.  Returns false on failure, usually because the

    *        looper processing the message queue is exiting.

    */
public final boolean sendMessage(Messagemsg){
    return sendMessageDelayed(msg,0);
}
public final boolean sendMessageDelayed(Message msg,long delayMillis){
    if(delayMillis<0){
        delayMillis=0;
    }
    return sendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis);
}

然后這里會(huì)將DelayMillis加上當(dāng)前開機(jī)的時(shí)間(這里可以理解就是這個(gè)time就是范抓,現(xiàn)在的時(shí)間+需要延遲的時(shí)間=實(shí)際執(zhí)行的時(shí)間),接下來進(jìn)到sendMessageAtTime方法里面

/**

    * Enqueue a message into the message queue after all pending messages

    * before the absolute time (in milliseconds) <var>uptimeMillis</var>.

    * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>

    * Time spent in deep sleep will add an additional delay to execution.

    * You will receive it in {@link #handleMessage}, in the thread attached

    * to this handler.

    *

    * @param uptimeMillis The absolute time at which the message should be

    *        delivered, using the

    *        {@link android.os.SystemClock#uptimeMillis} time-base.

    *       

    * @return Returns true if the message was successfully placed in to the

    *        message queue.  Returns false on failure, usually because the

    *        looper processing the message queue is exiting.  Note that a

    *        result of true does not mean the message will be processed -- if

    *        the looper is quit before the delivery time of the message

    *        occurs then the message will be dropped.

    */
public boolean sendMessageAtTime(Message msg,long uptimeMillis){
    MessageQueue queue=mQueue;
    if(queue==null){
        RuntimeExceptione = new RuntimeException(this+" sendMessageAtTime() called with no mQueue");
        Log.w("Looper",e.getMessage(),e);
        return false;
    }
    return enqueueMessage(queue,msg,uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue,Messagemsg,longuptime Millis){
    msg.target=this;
    if(mAsynchronous){
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg,uptimeMillis);
}

其實(shí)到這里handler的任務(wù)就完成了食铐,把message發(fā)送到messageQueue里面匕垫,每個(gè)消息都會(huì)帶有一個(gè)uptimeMillis參數(shù),這就是延時(shí)的時(shí)間璃岳。

接下來我們看messageQueue里面queue.enqueueMessage這個(gè)方法年缎。

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        synchronized (this) {
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }

            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

首先是進(jìn)行msg的一些屬性判斷,handler發(fā)出的target值必須不為空铃慷,是為了通過target值來判斷是哪個(gè)handler發(fā)過來的消息的单芜。

順便說一說 并不是所有的msg,target值都必須不為空

(handler的同步屏障就是一個(gè)target為空的msg犁柜,用來優(yōu)先執(zhí)行異步方法的)

同步屏障有一個(gè)很重要的使用場(chǎng)所就是接受垂直同步Vsync信號(hào)洲鸠,用來刷新頁面view的。因?yàn)闉榱吮WCview的流暢度馋缅,所以每次刷新信號(hào)到來的時(shí)候扒腕,要把其他的任務(wù)先放一放,優(yōu)先刷新頁面萤悴。

接下來主要就是將這個(gè)msg根據(jù)實(shí)際執(zhí)行時(shí)間進(jìn)行排序插入到queue里面(看里面的for循環(huán))瘾腰。

for (;;) {
    prev = p;
    p = p.next;
    if (p == null || when < p.when) {
        break;
    }
    if (needWake && p.isAsynchronous()) {
        needWake = false;
    }
}

好了,現(xiàn)在queue也構(gòu)建完成了覆履,假設(shè)我現(xiàn)在第一條消息就是要延遲10秒蹋盆,怎么辦呢。實(shí)際走一邊咯硝全。

假設(shè)我現(xiàn)在是looper栖雾,我要遍歷這個(gè)messageQueue,那肯定要調(diào)用next方法伟众。

next()方法比較長(zhǎng)析藕,我只貼關(guān)于延時(shí)消息的核心部分。

for (;;) {
        if (nextPollTimeoutMillis != 0) {
        Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
// Try to retrieve the next message.  Return if found.
final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        do {
        prevMsg = msg;
        msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
        }
        if (msg != null) {
        if (now < msg.when) {
        // Next message is not ready.  Set a timeout to wake up when it is ready.
        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        }………………

以下省略

可以看到這里也是一個(gè)for循環(huán)遍歷隊(duì)列凳厢,核心變量就是nextPollTimeoutMillis账胧。可以看到数初,計(jì)算出nextPollTimeoutMillis后就調(diào)用nativiePollOnce這個(gè)native方法找爱。這里的話大概可以猜到他的運(yùn)行機(jī)制,因?yàn)樗歉鶕?jù)執(zhí)行時(shí)間進(jìn)行排序的泡孩,那傳入的這個(gè)nextPollTimeoutMillis應(yīng)該就是休眠時(shí)間车摄,類似于java的sleep(time)。休眠到下一次message的時(shí)候就執(zhí)行仑鸥。那如果我在這段時(shí)間又插入了一個(gè)新的message怎么辦吮播,所以handler每次插入message都會(huì)喚醒線程,重新計(jì)算插入后眼俊,再走一次這個(gè)休眠流程意狠。

nativiePollOnce這個(gè)native方法可以通過名字知道,他用的是linux中的epoll機(jī)制疮胖,具體是調(diào)用了epoll_wait這個(gè)方法环戈。

intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);

這個(gè)epoll和select一樣都是linux的一個(gè)I/O多路復(fù)用機(jī)制闷板,主要原理就不深入了,這里大概了解一下I/O多路復(fù)用機(jī)制和它與Select的區(qū)別就行院塞。

Linux里的I/O多路復(fù)用機(jī)制:舉個(gè)例子就是我們釣魚的時(shí)候遮晚,為了保證可以最短的時(shí)間釣到最多的魚,我們同一時(shí)間擺放多個(gè)魚竿拦止,同時(shí)釣魚县遣。然后哪個(gè)魚竿有魚兒咬鉤了,我們就把哪個(gè)魚竿上面的魚釣起來汹族。這里就是把這些全部message放到這個(gè)機(jī)制里面萧求,那個(gè)time到了,就執(zhí)行那個(gè)message顶瞒。

epoll與select的區(qū)別:epoll獲取事件的時(shí)候采用空間換時(shí)間的方式夸政,類似與事件驅(qū)動(dòng),有哪個(gè)事件要執(zhí)行搁拙,就通知epoll秒梳,所以獲取的時(shí)間復(fù)雜度是O(1),select的話則是只知道有事件發(fā)生了箕速,要通過O(n)的事件去輪詢找到這個(gè)事件酪碘。

參考文章: http://www.reibang.com/p/68083d432b3f

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盐茎,隨后出現(xiàn)的幾起案子兴垦,更是在濱河造成了極大的恐慌,老刑警劉巖字柠,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件探越,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡窑业,警方通過查閱死者的電腦和手機(jī)钦幔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來常柄,“玉大人鲤氢,你說我怎么就攤上這事∥髋耍” “怎么了卷玉?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)喷市。 經(jīng)常有香客問我相种,道長(zhǎng),這世上最難降的妖魔是什么品姓? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任寝并,我火速辦了婚禮箫措,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘衬潦。我一直安慰自己蒂破,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布别渔。 她就那樣靜靜地躺著,像睡著了一般惧互。 火紅的嫁衣襯著肌膚如雪哎媚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天喊儡,我揣著相機(jī)與錄音拨与,去河邊找鬼。 笑死艾猜,一個(gè)胖子當(dāng)著我的面吹牛买喧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播匆赃,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼淤毛,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了算柳?” 一聲冷哼從身側(cè)響起低淡,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞬项,沒想到半個(gè)月后蔗蹋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體囱淋,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猪杭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了妥衣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皂吮。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖称鳞,靈堂內(nèi)的尸體忽然破棺而出涮较,到底是詐尸還是另有隱情,我是刑警寧澤冈止,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布狂票,位于F島的核電站,受9級(jí)特大地震影響熙暴,放射性物質(zhì)發(fā)生泄漏闺属。R本人自食惡果不足惜慌盯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一御铃、第九天 我趴在偏房一處隱蔽的房頂上張望福扬。 院中可真熱鬧侧纯,春花似錦纺且、人聲如沸拔稳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掸刊。三九已至乃摹,卻和暖如春禁漓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背孵睬。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工播歼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掰读。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓秘狞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親蹈集。 傳聞我的和親對(duì)象是個(gè)殘疾皇子烁试,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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