第十章 Android的訊息機制

2021-08-19 09:26:58 字數 3055 閱讀 1094

有時候需要在子執行緒中進行耗時的i/o操作,可能是讀取檔案或者是訪問網路等,當耗時操作完成以後可能需要在ui上做一些改變,這時候就需要用handler切換到主線程執行。

messagequeue 只是乙個訊息儲存單元,不能處理訊息,內部的儲存結構是單鏈表。

looper 是訊息迴圈,以無限迴圈的形式去查詢是否有新訊息,如果有的話就處理訊息,否則就一直等著。

threadlocal 可以在每個執行緒中儲存資料,handler建立的時候會採用當前的looper來構造訊息迴圈系統,那麼handler是怎麼獲取到當前執行緒的looper呢?

使用threadlocal,threadlocal在不同的執行緒中互不干擾地儲存並提供資料,可以輕鬆獲取每個執行緒的looper。執行緒預設是沒有looper的,如果使用handler就必須建立looper。ui執行緒中在activitythread被建立時就會初始化looper,這也是在主線程中預設可以使用handler的原因。

android 規定訪問ui只能在主線程中進行,如果在子執行緒中訪問ui,那麼程式就會丟擲異常。viewrootimpl 對ui操作做了驗證:

/**

* viewrootimpl對ui操作做了驗證,驗證工作由該方法進行

*/void checkthread()

}

提供handler,主要是為了解決在子執行緒中無法訪問ui的矛盾。

為什麼不允許在子執行緒中訪問ui呢?

因為 android 的ui控制項不是執行緒安全的,如果在多執行緒中併發訪問可能會導致ui控制項處於不可預估的狀態。

為什麼系統不對ui控制項的訪問加上鎖機制呢?

缺點有兩個:1:加上鎖機制會讓ui訪問的邏輯變得複雜;2:鎖機制會降低ui訪問的效率,因為鎖機制會阻塞某些執行緒的執行。

handler 建立時會採用當前執行緒的 looper 來構建內部的訊息迴圈系統,如果當前執行緒沒有looper,會報錯。looper是執行在床架handler所在的執行緒中的。

threadlocal 是乙個執行緒內部的資料儲存類,通過它可以在指定的執行緒中儲存資料,資料儲存以後,只有在指定的執行緒中可以獲取到儲存的資料,對於其他執行緒來說無法獲取到資料。

當某些資料時以執行緒為作用域並且不同的執行緒具有不同的資料副本的時候,就可以考慮使用threadlocal。

為什麼通過threadlocal可以在不同的執行緒中維護一套資料的副本並且彼此互不干擾?

因為不同執行緒訪問threadlocal的get(),threadlocal內部會從各自的執行緒中取出乙個陣列,然後再從陣列中根據當前threadlocal的索引去查詢對應的value值。不同執行緒的陣列是不一樣的。

messagequeue:通過單鏈表的資料結構來維護訊息列表

enqueuemessage():往訊息佇列中插入一條訊息

next():從訊息佇列中取出一條訊息並將其從訊息佇列中移除,next() 是乙個無線迴圈的方法,如果訊息佇列中沒有訊息,那麼 next() 方法會一直阻塞在這裡,當有新訊息到來,next會返回這條訊息並將其從單鏈表中刪除。

looper:訊息迴圈

構造方法:建立messagequeue物件,並把當前執行緒的物件儲存起來。

prepare():為當前執行緒建立乙個looper,並存到threadlocal中。

quit():直接退出looper

quitsafely():設定乙個退出標記,訊息佇列中的訊息處理完畢後才安全退出。

loop():是乙個死迴圈,唯一跳出迴圈的方式就是messagequeue.next()返回了null。

(1). 當looper.quit或者quitsafely被呼叫的時候,messagequeue的quit方法會被呼叫來通知訊息佇列退出,當訊息佇列被標記為退出狀態時,它的next()就會返回null。:looper必須退出,否則loop方法就會無限迴圈下去。

(2). looper 會呼叫messagequeue.next()獲取新訊息,而next是乙個阻塞操作,當沒有訊息的時候,next方法就會一直阻塞在**,導致looper也會一直阻塞在**。

(3). 如果messagequeue.next()返回了訊息,looper就會處理這條訊息:msg.target.dispatchmessage(msg)(msg.target 就是傳送訊息的 handler 物件)。然後會根據情況呼叫handler d的run()/handmessage()。成功將**邏輯切換到指定的執行緒中執行。

注意:

looper退出後,通過handler傳送的訊息會失敗,這個時候handler的send方法會返回false。在子執行緒中如果手動為其建立了looper,那麼在所有事情完成以後應該呼叫quit方法來終止訊息迴圈,否則這個子執行緒會一直處於等待的狀態。。而退出looper以後,該執行緒就會立刻終止,因此建議不需要的時候終止looper。

post/send:handler 傳送訊息的過程就是向 messagequeue 中插入一條訊息 —> messagequeue 的 next 方法就會返回這條訊息給 looper,looper 收到訊息後就呼叫 handler 的dispatchmessage(msg)分發訊息,這時候訊息就交給了 handler 處理。

構造方法:如果當前執行緒沒有looper的話,就會丟擲異常。這也解釋了在沒有looper的子執行緒中建立handler會引發程式異常的原因了。

mlooper = looper.mylooper();

/*** 在建立handler的時候,沒有looper會引發程式異常

10.3 主線程的訊息迴圈

第十章 函式

使用def關鍵字 定義個數可變的位置形參 定義個數可變的關鍵字形參 定義預設值引數 定義個數可變的位置形參 deffun1 args 結果為乙個元組 print args fun1 10,20,30 10,20,30 定義個數可變的關鍵字形參 deffun2 args 結果為乙個字典 print a...

第十章 屬性

z屬性 本章也比較簡單稍作介紹 無參屬性就是我們常見的屬性 可以封裝屬性 以執行緒安全方式訪問 有參屬性就是c 中的所引器 匿名型別 如圖遇到如下 具體點就是 上面的注釋也已經講清楚了,定義的型別之後,構造例項,然後初始化屬性。現在詳細說下 編譯器接收到 上圖 先用var 定義乙個型別,但是不具體指...

第十章 屬性

目錄 10.1 無參屬性 10.2 有參屬性 10.3 呼叫屬性訪問器方法時的效能 10.4 屬性訪問器的可訪問性 10.5 泛型屬性訪問器方法 物件導向設計和程式設計的重要原則之一就是資料封裝,意味著型別的字段不應該公開,否則很容易因為不恰單使用欄位而破壞物件的狀態。可將屬性想象成智慧型字段,即背...