Android Handler詳解

本期主要內(nèi)容

  • 1: Handler是什么穗酥?
  • 2:為什么要使用Handler?
  • 3: Handler /Looper/ MessageQueue/Message究竟是做什么的?
  • 4:Handler如何去實(shí)現(xiàn)發(fā)送和處理消息

1、Handler是什么?

  • Handler 是一個(gè)消息分發(fā)對(duì)象后专。handler是Android給我們提供用來更新UI的一套機(jī)制准颓,也是一套消息處理機(jī)制,我們可以發(fā)消息圃郊,也可以通過它處理消息。
  • 1:To schedule messages and runnables to be executed as some point in the future 定時(shí)任務(wù)
  • 2: To enqueue an action to be performed on a different thread than your own 在不同線程中執(zhí)行任務(wù)

2女蜈、為什么要使用Handler?

  • 最根本的目的就是為了解決多線程并發(fā)的問題持舆!打個(gè)比方色瘩,如果在一個(gè)activity中有多個(gè)線程,并且沒有加鎖逸寓,就會(huì)出現(xiàn)界面錯(cuò)亂的問題居兆。但是如果對(duì)這些更新UI的操作都加鎖處理,又會(huì)導(dǎo)致性能下降竹伸。處于對(duì)性能的問題考慮泥栖,Android給我們提供這一套更新UI的機(jī)制我們只需要遵循這種機(jī)制就行了。不用再去關(guān)系多線程的問題勋篓,所有的更新UI的操作吧享,都是在主線程的消息隊(duì)列中去輪訓(xùn)的。
  • 它把消息發(fā)送給Looper管理的MessageQueue譬嚣,并負(fù)責(zé)處理Looper分發(fā)給他的消息钢颂。

3、Handler 拜银、 Looper 殊鞭、Message 這三者都與Android異步消息處理線程相關(guān)的概念。那么什么叫異步消息處理線程呢尼桶?

Handler 操灿、 Looper 、Message.png
  • 異步消息處理線程啟動(dòng)后會(huì)進(jìn)入一個(gè)無限的循環(huán)體之中疯汁,每循環(huán)一次牲尺,從其內(nèi)部的消息隊(duì)列中取出一個(gè)消息,然后回調(diào)相應(yīng)的消息處理函數(shù)幌蚊,執(zhí)行完成一個(gè)消息后則繼續(xù)循環(huán)谤碳。若消息隊(duì)列為空,線程則會(huì)阻塞等待溢豆。

  • 那么Android消息機(jī)制主要是指Handler的運(yùn)行機(jī)制蜒简,Handler運(yùn)行需要底層的MessageQueue和Looper支撐。其中MessageQueue采用的是單鏈表的結(jié)構(gòu)漩仙,Looper可以叫做消息循環(huán)搓茬。由于MessageQueue只是一個(gè)消息存儲(chǔ)單元,不能去處理消息队他,而Looper就是專門來處理消息的卷仑,Looper會(huì)以無限循環(huán)的形式去查找是否有新消息,如果有的話麸折,就處理锡凝,否則就一直等待著。

  • 我們知道垢啼,Handler創(chuàng)建的時(shí)候會(huì)采用當(dāng)前線程的Looper來構(gòu)造消息循環(huán)系統(tǒng)窜锯,需要注意的是张肾,線程默認(rèn)是沒有Looper的,如果需要使用Handler就必須為線程創(chuàng)建Looper锚扎,因?yàn)槟J(rèn)的UI主線程吞瞪,也就是ActivityThread,ActivityThread被創(chuàng)建的時(shí)候就會(huì)初始化Looper驾孔,這也是在主線程中默認(rèn)可以使用Handler的原因芍秆。

Handler類包含如下方法用于發(fā)送、處理消息:

  • void handleMessage(Message msg):處理消息的方法助币。該方法通常用于被重寫浪听。

  • final boolean hasMessages(int what):檢查消息隊(duì)列中是否包含what屬性為指定值的消息螟碎。

  • final boolean hasMessages(int what, Object object):檢查消息隊(duì)列中是否包含what屬性為指定值且object屬性為指定對(duì)象的消息眉菱。

  • 多個(gè)重載的Message obtainMessage():獲取消息。

  • sendEmptyMessage(int what):發(fā)送空消息掉分。

  • final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒后發(fā)送空消息俭缓。

  • final boolean sendMessage(Message msg):立即發(fā)送消息。

  • final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒后發(fā)送消息酥郭。

  • Message:Handler接收和處理的消息對(duì)象华坦。

    • 2個(gè)整型數(shù)值:輕量級(jí)存儲(chǔ)int類型的數(shù)據(jù)。
    • 1個(gè)Object:任意對(duì)象不从。
    • replyTo:線程通信時(shí)使用惜姐。
    • what:用戶自定義的消息碼,讓接收者識(shí)別消息椿息。
  • MessageQueue:Message的隊(duì)列歹袁。

    • 采用先進(jìn)先出的方式管理Message。
    • 每一個(gè)線程最多可以擁有一個(gè)寝优。
  • Looper:消息泵条舔,是MessageQueue的管理者,會(huì)不斷從MessageQueue中取出消息乏矾,并將消息分給對(duì)應(yīng)的Handler處理孟抗。

    • 每個(gè)線程只有一個(gè)Looper。
    • Looper.prepare():為當(dāng)前線程創(chuàng)建Looper對(duì)象钻心。
    • Looper.myLooper():可以獲得當(dāng)前線程的Looper對(duì)象凄硼。
    • Handler:能把消息發(fā)送給MessageQueue,并負(fù)責(zé)處理Looper分給它的消息捷沸。

4:Handler如何去實(shí)現(xiàn)發(fā)送和處理消息

1: 使用Thread發(fā)送消息

Thread

2: 使用Runnable發(fā)送消息

Runnable.png

3: 使用Handler下載文件并更新進(jìn)度條
1: 首先配置權(quán)限信息 讀寫權(quán)限問題

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" />

2: 在Android 6.0之后必須動(dòng)態(tài)檢測(cè)權(quán)限摊沉。所以在下載操作開始之前需要進(jìn)行權(quán)限判斷

Android6.0.png
發(fā)送消息,準(zhǔn)備更新UI操作.png
handleMessage.png

權(quán)限問題處理方法

權(quán)限問題處理方法.png

4: 使用 handler實(shí)現(xiàn)倒計(jì)時(shí)功能
首先強(qiáng)調(diào)一下需要注意的問題吧
Handler在進(jìn)行異步操作并處理返回結(jié)果時(shí)經(jīng)常被使用亿胸。通常我們的代碼會(huì)這樣實(shí)現(xiàn)坯钦。

Handler

但是预皇,其實(shí)上面的代碼可能導(dǎo)致內(nèi)存泄露,當(dāng)你使用Android lint工具的話婉刀,會(huì)得到這樣的警告:

warning.png

以下幾種情況都可能存在內(nèi)存泄漏的情況

1.當(dāng)一個(gè)Android應(yīng)用啟動(dòng)的時(shí)候吟温,會(huì)自動(dòng)創(chuàng)建一個(gè)供應(yīng)用主線程使用的Looper實(shí)例。Looper的主要工作就是一個(gè)一個(gè)處理消息隊(duì)列中的消息對(duì)象突颊。在Android中鲁豪,所有Android框架的事件(比如Activity的生命周期方法調(diào)用和按鈕點(diǎn)擊等)都是放入到消息中,然后加入到Looper要處理的消息隊(duì)列中律秃,由Looper負(fù)責(zé)一條一條地進(jìn)行處理爬橡。主線程中的Looper生命周期和當(dāng)前應(yīng)用一樣長(zhǎng)。

2.當(dāng)一個(gè)Handler在主線程進(jìn)行了初始化之后棒动,我們發(fā)送一個(gè)target為這個(gè)Handler的消息到Looper處理的消息隊(duì)列時(shí)糙申,實(shí)際上已經(jīng)發(fā)送的消息已經(jīng)包含了一個(gè)Handler實(shí)例的引用,只有這樣Looper在處理到這條消息時(shí)才可以調(diào)用Handler#handleMessage(Message)完成消息的正確處理船惨。

3.在Java中柜裸,非靜態(tài)的內(nèi)部類和匿名內(nèi)部類都會(huì)隱式地持有其外部類的引用。靜態(tài)的內(nèi)部類不會(huì)持有外部類的引用粱锐。

解決方式

  • 要解決這種問題疙挺,思路就是不適用非靜態(tài)內(nèi)部類,繼承Handler時(shí)怜浅,要么是放在單獨(dú)的類文件中铐然,要么就是使用靜態(tài)內(nèi)部類。因?yàn)殪o態(tài)的內(nèi)部類不會(huì)持有外部類的引用恶座,所以不會(huì)導(dǎo)致外部類實(shí)例的內(nèi)存泄露搀暑。當(dāng)你需要在靜態(tài)內(nèi)部類中調(diào)用外部的Activity時(shí),我們可以使用弱引用來處理奥裸。另外關(guān)于同樣也需要將Runnable設(shè)置為靜態(tài)的成員屬性险掀。注意:一個(gè)靜態(tài)的匿名內(nèi)部類實(shí)例不會(huì)持有外部類的引用。
??.png

面試要點(diǎn)

  • 這里 我們可以逐步分析:
public Handler(Callback callback, boolean async) {
        
       //得到主線程(已經(jīng)被ActivityThread初始化好)的looper
       mLooper = Looper.myLooper();
       if (mLooper == null) {
           throw new RuntimeException(
               "Can't create handler inside thread that has not called Looper.prepare()");
       }
       
       //得到主線程(已經(jīng)被ActivityThread初始化好)的looper的MessageQueue湾宙,注釋:①
       mQueue = mLooper.mQueue;
       mCallback = callback;
       mAsynchronous = async;
}

注意樟氢,看到了嗎? 這里是核心:mLooper = Looper.myLooper();侠鳄,

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

而這個(gè)Looper對(duì)象埠啃,是在我們啟動(dòng)程序的時(shí)候,也就是ActivityThread 中的main方法幫我們初始化的伟恶,也就是我們主線程的Looper碴开。

public static void prepareMainLooper() {
     ···
     prepare(false);
     ···
}

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

我們?cè)谧泳€程 使用 handler的實(shí)例進(jìn)行 sendMessage(Message msg) 的時(shí)候:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //這里的代碼和上面的注釋①對(duì)應(yīng)。
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

最終走向enqueueMessage:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

最終把我們的Message,放進(jìn)了主線程的MessageQueue里面進(jìn)行循環(huán)潦牛!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末眶掌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子巴碗,更是在濱河造成了極大的恐慌朴爬,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橡淆,死亡現(xiàn)場(chǎng)離奇詭異召噩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)逸爵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門具滴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人师倔,你說我怎么就攤上這事构韵。” “怎么了溯革?”我有些...
    開封第一講書人閱讀 158,084評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵贞绳,是天一觀的道長(zhǎng)谷醉。 經(jīng)常有香客問我致稀,道長(zhǎng),這世上最難降的妖魔是什么俱尼? 我笑而不...
    開封第一講書人閱讀 56,708評(píng)論 1 284
  • 正文 為了忘掉前任抖单,我火速辦了婚禮,結(jié)果婚禮上遇八,老公的妹妹穿的比我還像新娘矛绘。我一直安慰自己,他們只是感情好刃永,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評(píng)論 6 386
  • 文/花漫 我一把揭開白布货矮。 她就那樣靜靜地躺著,像睡著了一般斯够。 火紅的嫁衣襯著肌膚如雪囚玫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,021評(píng)論 1 291
  • 那天读规,我揣著相機(jī)與錄音抓督,去河邊找鬼。 笑死束亏,一個(gè)胖子當(dāng)著我的面吹牛铃在,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼定铜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼阳液!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起揣炕,我...
    開封第一講書人閱讀 37,866評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤趁舀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后祝沸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體矮烹,經(jīng)...
    沈念sama閱讀 44,308評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評(píng)論 2 327
  • 正文 我和宋清朗相戀三年罩锐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奉狈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,768評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涩惑,死狀恐怖仁期,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竭恬,我是刑警寧澤跛蛋,帶...
    沈念sama閱讀 34,461評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站痊硕,受9級(jí)特大地震影響赊级,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜岔绸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評(píng)論 3 317
  • 文/蒙蒙 一理逊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧盒揉,春花似錦晋被、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至藕漱,卻和暖如春欲侮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谴分。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工锈麸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牺蹄。 一個(gè)月前我還...
    沈念sama閱讀 46,571評(píng)論 2 362
  • 正文 我出身青樓忘伞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子氓奈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評(píng)論 2 350

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