最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

Handler+Binder;看完這一篇就理解了

2022-09-02 17:23 作者:初壹十五阿  | 我要投稿

前言

在說Handlerbinder之前,我們先看看關于Framework都有什么內容


1.Framework通信(Binder,Handler,Livedata
2.Framework底層服務(AMS,PMSWMS
3.Framework系統(tǒng)資源(ServiceManager,Contxt,Resource
4.Framework事件機制
5.Framework UI機制(UI繪制原理,UI自定義)

今天先來分析一下HandlerBinder相關視頻

Framework大全V:maniu0


一丶Handler

Handler、Message、MessageQueue、Looper;

以下為零散的記錄,最后有總結;?內存泄露的本質:

長生命周期對象持有短生命周期對象,導致短生命周期對象銷毀不掉;

持有鏈:

線程>>Looper>>MessageQueue>>Message>>Handler>>Activity;

Message對象的變量target為發(fā)送消息的Handler;?MessageQueue?隊列里放Message;?Looper對象里實例化MessageQueue;?一個線程綁定一個Looper;

為什么要有handler? 主要目的是要解決線程切換問題,handler里的Message機制解決了線程間通信;

為什么有隊列MessageQueue??MessageQueue是一個單向鏈表,next()調用nativePollOnce->lunxepoll_wait()等待實現阻塞時隊列;

  • 在單線程中一次只能執(zhí)行一句代碼

  • 假如發(fā)送了一個大消息A

  • 處理這個大的消息A

  • 但是處理的太慢了

  • 從而導致其他后續(xù)要發(fā)送的消息發(fā)不出去

  • 因為單線程阻塞到了第3步處理那個消息A的地方

隊列的出現解決了"處理消息"阻塞到"發(fā)送消息"的問題;

隊列是生產者消費者模式;

而要使用隊列需要至少兩個線程、和一個死循環(huán);

  • 一個線程負責生產消息;

  • 一個線程消費消息;

  • 死循環(huán)需要取出放入隊列里的消息;

為什么有Looper?

為了循環(huán)取出隊列里的消息;

一個線程有幾個Looper,為什么不會有多個?

一個線程一個Looper,放在ThreadLocalMap中;

假如Looper對象由Handler創(chuàng)建,每創(chuàng)建一個Handler就有一個Looper,那么調用Looper.loop()時開啟死循環(huán);在外邊調用Looper的地方就會阻塞;



主線程中Looper的死循環(huán)為什么沒有導致系統(tǒng)卡死?

  • 我們的UI線程主線程其實是ActivityThread線程,而一個線程只會有一個Looper;

  • ActivityThread.javamain函數是一個APP進程的入口,如果不卡死,main函數執(zhí)行完則整個應用進程就會退出;

  • android是以事件為驅動的操作系統(tǒng),當有事件來時,就去做對應的處理,沒有時就顯示靜態(tài)界面;

獲取當前線程:Thread.currentThread();

ThreadLocalMap:類似于HashMap;

每個Thread對象都有一個對應的ThreadLocalMap;



Looper.prepare()時,存入Looper,存LooperThreadLocalMap的key為ThreadLocal,valueLooper


內存抖動根本的解決方式是復用;

handler.obtainMessage();

  • Looper的回收池中取Message;

  • Message是一個單向鏈表,Message不是一個單純的對象,而是一個鏈表集合

  • 最大長度固定50個

    Linux函數:?epoll_create:App注冊進紅黑樹中,拿到一個事件fd的值;?epoll_ctl:注冊事件類型,監(jiān)聽fd是否改變(Linux中事件都會被寫入文件中,如觸摸屏幕事件會寫入到:dev/input/event0文件中),fd有改變時喚醒epoll_wait;?epoll_wait:有事件時就分發(fā),沒事件就阻塞


總結:?handler如何做的線程切換的??首先Handler的使用步驟:

  • 調用Looper.prepare();

  • 創(chuàng)建Handler對象;

  • 調用Looper.Loop()方法。

  • 線程中發(fā)送消息。

在第一步時,創(chuàng)建一個Looper,并放到當前線程的變量threadLocals中;threadLocals是一個map,key為ThreadLocal對象本身,value為Looper;在Looper.loop()時取出;



第二步,用戶在當前線程(可能是子線程)創(chuàng)建Handler對象;

第三步,Looper.loop()一直在死循環(huán),Looper.loop()這句代碼下面的代碼是不會被調用的,調用Looper.loop()函數時,先從當前線程的map變量中取出Looper,再從Looper中拿到隊列MessageQueue,for循環(huán)中不斷從隊列中取出消息;


第四步,在其他線程調用handelr發(fā)送消息時,Message里有個target,就是發(fā)送消息的handler;

Looper.loop()時,隊列中取到消息時,調用msg.target.dispatchMessage(msg);其實就是handler對象.dispatchMessage(msg);

所以不論在哪個線程調用發(fā)送消息,都會調用到handler自己分發(fā)消息;而handler所處的線程是創(chuàng)建時的“當前線程”,所以處理時也就回到了“當前線程”;實現了線程切換,和線程通信;

Looper的死循環(huán)為什么不會讓主線程卡死(或ANR)??簡單版:

  • 我們的UI線程主線程其實是ActivityThread所在的線程,而一個線程只會有一個Looper;

  • ActivityThread.java的main函數是一個APP進程的入口,如果不一直循環(huán),則在main函數執(zhí)行完最后一行代碼后整個應用進程就會退出;

  • android是以事件為驅動的操作系統(tǒng),當有事件來時,就去做對應的處理,沒有時就顯示靜態(tài)界面;

  • ANR發(fā)生條件是:
    Activity:5 秒。應用在 5 秒內未響應用戶的輸入事件(如按鍵或者觸摸)
    BroadCastReceiver?:10 秒。BroadcastReceiver?未在 10 秒內完成相關的處理
    Service:20 秒(均為前臺)。Service?在20 秒內無法處理完成

  • 如果Handler收到以上三個相應事件在規(guī)定時間內完成了,則移除消息,不會ANR;若沒完成則會超時處理,彈出ANR對話框;

詳細:

  • App進程的入口為ActivityThread.java的main()函數,注意ActivityThread不是一個線程;

  • 應用的ui主線程實際是調用ActivityThread.java的main()函數執(zhí)行時所在的線程,而這個線程對我們不可見,但是這就是主線程;參考:

  • ActivityThread.javamain()函數中,會調用Looper.prepareMainLooper();

  • Looper.prepareMainLooper()會創(chuàng)建一個Looper并放到當前線程(主線程)的變量threadLocals中進行綁定,threadLocals是一個ThreadLocal.ThreadLocalMap;

  • ActivityThread.javamain()函數結尾,開啟Looper.loop()進行死循環(huán),不讓main函數結束,從而讓App進程不會結束;

  • Android系統(tǒng)是以事件作為驅動的操作系統(tǒng),當有事件來時,就去做對應處理,沒有事件時,就顯示當前界面,不做其他多余操作(浪費資源);

  • Looper.loop()的死循環(huán)中,不僅要取用戶發(fā)的事件,還要取系統(tǒng)內核發(fā)的事件(如屏幕亮度改變等等);

  • 在調用Looper.loop()時,從MessageQueue.next()中獲取事件,若沒有則阻塞,有則分發(fā);

  • MessageQueue其實不是一個隊列,用epoll機制實現了阻塞;

  • Looper.prepareMainLooper()時,調用c++函數epoll_create()會將App注冊進epoll機制的紅黑樹中得到fd的值,epoll_ctl()給每個App注冊事件類型并監(jiān)聽fd值是否改變,fd有改變時喚醒epoll_wait

  • epoll_wait()有事件時就分發(fā),沒事件就阻塞

子線程的Looper和子線程Looper有什么不同?

子線程Looper是可以退出的,主線程不行;

Framework大全V:maniu0


二丶Binder

進程隔離:

內核空間中存放的是內核代碼和數據,而進程的用戶空間中存放的是用戶程序的代碼和數據 為了保證系統(tǒng)的安全,用戶空間和內核空間是天然隔離的 每個進程有自己的虛擬內存空間,為了安全,每個進程只能操作自己的虛擬內存空間,只有操作系統(tǒng)才有權限操作物理內存空間

為什么要用Binder?

  • Android系統(tǒng)內核是Linux內核

  • Linux內核進程通信有:管道、內存共享、SocketFile;

  • 對比:


Binder的一次拷貝發(fā)生在用戶空間拷貝到內核空間;

用戶空間:?App進程運行的內存空間;

內核空間:?系統(tǒng)驅動、和硬件相關的代碼運行的內存空間,也就是進程ID為0的進程運行的空間;

程序局部性原則:?只加載少量代碼;應用沒有運行的代碼放在磁盤中,運行時高速緩沖區(qū)進行加載要運行的代碼;默認一次加載一個頁(4K),若不夠4K就用0補齊;

MMU:內存管理單元;

給CPU提供虛擬地址;

當對變量操作賦值時:

  • CPU拿著虛擬地址和值給到MMU

  • MMU用虛擬地址匹配到物理地址,MMU去物理內存中進行賦值;

物理地址:?物理內存的實際地址,并不是磁盤;

虛擬地址:?MMU根據物理內存的實際地址翻譯出的虛擬地址;提供給CPU使用;


頁命中:CPU讀取變量時,MMU在物理內存的頁表中找到了這個地址;

頁未命中:CPU讀取變量時,MMU在物理內存的頁表中沒有找到了這個地址,此時會觸發(fā)MMU去磁盤讀取變量并存到物理內存中;

普通的二次拷貝:

應用A拷貝到服務端:coay_from_user

從服務端拷貝到應用B:coay_to_user

mmap():

  • 在物理內存中開辟一段固定大小的內存空間

  • 將磁盤文件與物理內存進行映射(理解為綁定)

  • MMU將物理內存地址轉換為虛擬地址給到CPU(虛擬地址映射物理內存)

共享內存進程通信:

  • 進程A調用mmap()函數會在內核空間中虛擬地址和一塊同樣大小的物理內存,將兩者進行映射

  • 得到一個虛擬地址

  • 進程B調用mmap()函數,傳參和步驟1一樣的話,就會得到一個和步驟2相同的虛擬地址

  • 進程A和進程B都可以用同一虛擬地址對同一塊映射內存進行操作

  • 進程A和進程B就實現了通信

  • 沒有發(fā)生拷貝,共享一塊內存,不安全

Binder通信原理:

角色:Server端A、Client端B、Binder驅動、內核空間、物理內存

  • Binder驅動在物理內存中開辟一塊固定大小(1M-8K)的物理內存w,與內核空間的虛擬地址x進行映射得到

  • A的用戶空間的虛擬地址ax和物理內存w進行映射

  • 此時內核空間虛擬地址x和物理內存w已經進行了映射,物理內存w和Server端A的用戶空間虛擬地址ax進行了映射:也就是 內核空間的虛擬地址x = 物理內存w = Server端A的用戶空間虛擬地址ax

  • B發(fā)送請求:將數據按照binder協議進行打包給到Binder驅動,Binder驅動調用coay_from_user()將數據拷貝到內核空間的虛擬地址x

  • 因步驟3中的三塊區(qū)域進行了映射

  • Server端A就得到了Client端B發(fā)送的數據

  • 通過內存映射關系,只發(fā)生了一次拷貝


Activity跳轉時,最多攜帶1M-8k(1兆減去8K)的數據量;

真實數據大小為:1M內存-兩頁的請求頭數據=1M-8K;

應用A直接將數據拷貝到應用B的物理內存空間中,數據量不能超過1M-8K;拷貝次數少了一次,少了從服務端拷貝到用戶;

IPC通信機制:

  • 服務注冊

  • 服務發(fā)現

  • 服務調用

以下為簡單的主進程和子進程通信:

1、服務注冊: 緩存中心中有三張表(暫時理解為三個HashMap,Binder用的是native的紅黑樹):

  • 第一種:放key :String - value:類的Class;

  • 第二種:放key :Class的類名 - value:類的方法集合;

  • 第三種:放key :Class的類名 - value:類的對象;

類的方法集合:key-value;

key:方法簽名:“方法名” 有參數時用 “方法名-參數類型-參數類型-參數類型......”;

value: 方法本身;

注冊后,服務若沒被調用則一直處于沉默狀態(tài),不會占用內存,這種情況只是指用戶進程里自己創(chuàng)建的服務,不適用于AMS這種;

2、服務發(fā)現: 當被查詢到時,要被初始化;

  • 客戶端B通過發(fā)送信息到服務端A

  • 服務端解析消息,反序列化

  • 通過反射得到消息里的類名,方法,從注冊時的第一種、第二種表里找到Class,若對象沒初始化則初始化對象,并將對象添加到第三種的表里;

3、服務調用

  • 使用了動態(tài)代理

  • 客戶端在服務發(fā)現時,拿到對象(其實是代理)

  • 客戶端調用對象方法

  • 代理發(fā)送序列化數據到服務端A

  • 服務端A解析消息,反序列化,得到方法進行處理,得到序列化數據結果

  • 將序列化結果寫入到客戶端進程的容器中;

  • 回調給客戶端

AIDL:?BpBinder:數據發(fā)送角色 BbBinder:數據接收角色


編譯器生成的AIDL的java接口.Stub.proxy.transact()為數據發(fā)送處;

發(fā)送的數據包含:數據+方法code+方法參數等等;

  • 發(fā)送時調用了Linux的驅動

  • 調用copy_from_user()拷貝用戶發(fā)送的數據到內核空間

  • 拷貝成功后又進行了一次請求頭的拷貝:copy_from_user()

  • 也就是把一次的數據分為兩次拷貝

請求頭:包含了目的進程、大小等等參數,這些參數占了8K

編譯器生成的AIDL的java接口.Stub.onTransact()為數據接收處;

Binder中的IPC機制:

  • 每個App進程啟動時會在內核空間中映射一塊1M-8K的內存

  • 服務端A的服務注冊到ServiceManager中:服務注冊

  • 客戶端B想要調用服務端A的服務,就去請求ServiceManager

  • ServiceManager去讓服務端A實例化服務:服務發(fā)現

  • 返回一個用來發(fā)送數據的對象BpBinder給到客戶端B

  • 客戶端B通過BpBinder發(fā)送數據到服務端A的內核的映射區(qū)域(傳參時客戶端會傳一個reply序列化對象,在底層會將這個地址一層一層往下傳,直至傳到回調客戶端):這里發(fā)生了一次通信copy_from_user:服務調用

  • 服務端A通過BBBinder得到數據并處理數據

  • 服務端喚醒客戶端等待的線程;將返回結果寫入到客戶端發(fā)送請求時傳的一個reply容器地址中,調用onTransact返回;

  • 客戶端在onTransac中得到數據;通信結束;

ServiceManager維持了Binder這套通信框架;

Framework大全V:maniu0

Handler+Binder;看完這一篇就理解了的評論 (共 條)

分享到微博請遵守國家法律
永寿县| 宜宾县| 克拉玛依市| 钟祥市| 平顺县| 威信县| 白银市| 千阳县| 云浮市| 泉州市| 武山县| 贡嘎县| 望江县| 阜康市| 同德县| 商洛市| 天全县| 文化| 普宁市| 大石桥市| 乐昌市| 太湖县| 华亭县| 沙坪坝区| 忻州市| 金乡县| 乾安县| 静乐县| 景宁| 元朗区| 天镇县| 聊城市| 新乐市| 南投县| 盐山县| 崇阳县| 钦州市| 固安县| 敦化市| 辽阳市| 杨浦区|