Ⅰ SurfaceFlinger 原理分析
SurfaceFlinger是android multimedia的一個部分,在Android 的實現中它是一個service,提供系統范圍內的surface composer功能,它能夠將各種應用程序的2D、3D surface進行組合。
每個應用程序可能對應著一個或者多個圖形界面,而每個界面我們就稱之為一個surface ,或者說是window ,在上面的圖中我們能看到4 個surface ,一個是home 界面,還有就是紅、綠、藍分別代表的3個surface ,而兩個button 實際是home surface 裡面的內容。我們需要考慮一下情況:
在實際中對這些Surface 進行merge 可以採用兩種方式,一種就是採用軟體的形式來merge ,還一種就是採用硬體的方式,軟體的方式就是我們的 SurfaceFlinger ,而硬體的方式就是 Overlay 。
因為硬體merge 內容相對簡單,我們首先來看overlay 。以IMX51 為例子,當IPU 向內核申請FB 的時候它會申請3 個FB ,一個是主屏的,還一個是副屏的,還一個就是Overlay 的。 簡單地來說,Overlay就是我們將硬體所能接受的格式數據和控制信息送到這個Overlay FrameBuffer,由硬體驅動來負責merge Overlay buffer和主屏buffer中的內容。
一般來說現在的硬體都只支持一個Overlay,主要用在視頻播放以及camera preview上,因為視頻內容的不斷變化用硬體Merge比用軟體Merge要有效率得多,下面就是使用Overlay和不使用Overlay的過程:
surfaceFlinger 只是負責 merge Surface 的控制,比如說計算出兩個 Surface 重疊的區域,至於 Surface 需要顯示的內容,則通過 skia,opengl 和 pixflinger 來計算。
創建過程
SurfaceFlinger 是一個線程類,它繼承了 Thread 類。當創建 SurfaceFlinger 這個服務的時候會啟動一個 SurfaceFlinger 監聽線程,這個線程會一直等待事件的發生,比如說需要進行 sruface flip ,或者說窗口位置大小發生了變化等,一旦產生這些事件,SurfaceComposerClient 就會通過 IBinder 發出信號,這個線程就會結束等待處理這些事件,處理完成以後會繼續等待,如此循環。
SurfaceComposerClient 和 SurfaceFlinger 是通過 SurfaceFlingerSynchro 這個類來同步信號的,其實說穿了就是一個條件變數。監聽線程等待條件的值一旦變成 OPEN 就結束等待並將條件置成 CLOSE 然後進行事件處理,處理完成以後再繼續等待條件的值變成 OPEN ,而 Client 的Surface 一旦改變就通過 IBinder 通知 SurfaceFlinger 將條件變數的值變成 OPEN ,並喚醒等待的線程,這樣就通過線程類和條件變數實現了一個動態處理機制。
窗口狀態變化的處理是一個很復雜的過程,SurfaceFlinger 只是執行 Windows Manager 的指令,由 Windows manager 來決定什麼是偶改變大小、位置、透明度、以及如何調整layer 之間的順序, SurfaceFlinger 僅僅只是執行它的指令。
普通的Android控制項,例如TextView、Button和CheckBox等,它們都是將自己的UI繪制在宿主窗口的繪圖表面之上,這意味著它們的UI是在應用程序的主線程中進行繪制的。由於應用程序的主線程除了要繪制UI之外,還需要及時地響應用戶輸入,否則系統就會認為應用程序沒有響應了。而對於一些游戲畫面,或者攝像頭預覽、視頻播放來說,它們的UI都比較復雜,而且要求能夠進行高效的繪制。這時候就必須要給那些需要復雜而高效UI的視圖生成一個獨立的繪圖表面,以及使用一個獨立的線程來繪制這些視圖的UI。
SurfaceFlinger服務運行在Android系統的System進程中,它負責管理Android系統的幀緩沖區(Frame Buffer)。Android應用程序為了能夠將自己的UI繪制在系統的幀緩沖區上,它們就必須要與SurfaceFlinger服務進行通信。
在APP端執行draw的時候,數據很明顯是要繪制到APP的進程空間,但是視圖窗口要經過SurfaceFlinger圖層混排才會生成最終的幀,而SurfaceFlinger又運行在另一個獨立的服務進程,那麼View視圖的數據是如何在兩個進程間傳遞的呢,普通的Binder通信肯定不行,因為Binder不太適合這種數據量較大的通信,那麼View數據的通信採用的是什麼IPC手段呢?答案就是共享內存,更精確的說是匿名共享內存。共享內存是linux自帶的一種IPC機制,Android直接使用了該模型,不過做出了自己的改進,進而形成了Android的匿名共享內存(Anonymous Shared Memory-Ashmem)。通過Ashmem,APP進程同SurfaceFlinger共用一塊內存,如此,就不需要進行數據拷貝,APP端繪制完畢,通知SurfaceFlinger端合成,再輸出到硬體進行顯示即可。
在每一個Android應用程序與SurfaceFlinger服務之間的連接上加上一塊用來傳遞UI元數據的匿名共享內存,這個共享內存就是 SharedClient
在每一個SharedClient裡面,有至多31個SharedBufferStack。SharedBufferStack就是Android應用程序和SurfaceFlinger 的緩沖區堆棧。用來緩沖 UI 元數據。
一般我們就繪制UI的時候,都會採用一種稱為「雙緩沖」的技術。雙緩沖意味著要使用兩個緩沖區,其中一個稱為Front Buffer,另外一個稱為Back Buffer。UI總是先在Back Buffer中繪制,然後再和Front Buffer交換,渲染到顯示設備中。這下就可以理解SharedBufferStack的含義了吧?SurfaceFlinger服務只不過是將傳統的「雙緩沖」技術升華和抽象為了一個SharedBufferStack。可別小看了這個升華和抽象,有了SharedBufferStack之後,SurfaceFlinger 服務就可以使用N個緩沖區技術來繪制UI了。N值的取值范圍為2到16。例如,在Android 2.3中,N的值等於2,而在Android 4.1中,據說就等於3了。
在SurfaceFlinger服務中,每一個SharedBufferStack都對應一個Surface,即一個窗口。這樣,我們就可以知道為什麼每一個SharedClient裡麵包含的是一系列SharedBufferStack而不是單個SharedBufferStack: 一個SharedClient對應一個Android應用程序,而一個Android應用程序可能包含有多個窗口 ,即Surface。從這里也可以看出,一個Android應用程序至多可以包含31個Surface。
SharedBufferStack中的 緩沖區只是用來描述UI元數據的 ,這意味著它們不包含真正的UI數據。 真正的UI數據保存在GraphicBuffer中 ,後面我們再描述GaphicBuffer。因此,為了完整地描述一個UI,SharedBufferStack中的每一個已經使用了的緩沖區都對應有一個GraphicBuffer,用來描述真正的UI數據。當SurfaceFlinger服務緩制Buffer-1和Buffer-2的時候,就會找到與它們所對應的GraphicBuffer,這樣就可以將對應的UI繪制出來了。
當Android應用程序需要 更新一個Surface 的時候,它就會找到與它所對應的SharedBufferStack,並且從它的空閑緩沖區列表的尾部取出一個空閑的Buffer。我們假設這個取出來的空閑Buffer的編號為index。接下來Android應用程序就請求SurfaceFlinger服務為這個編號為index的 Buffer分配一個圖形緩沖區GraphicBuffer 。
SurfaceFlinger 服務分配好圖形緩沖區 GraphicBuffer 之後,會將它的編號設置為 index,然後再將這個圖形緩沖區 GraphicBuffer 返回給 Android 應用程序訪問。Android應用程序得到了 SurfaceFlinger 服務返回的圖形緩沖區 GraphicBuffer 之後,就在裡面 寫入UI數據 。寫完之後,就將與它所對應的緩沖區,即編號為 index 的 Buffer,插入到對應的 SharedBufferStack 的已經使用了的 緩沖區列表的頭部 去。這一步完成了之後,Android 應用程序就通知 SurfaceFlinger 服務去繪制那些保存在已經使用了的緩沖區所描述的圖形緩沖區GraphicBuffer了。用上面例子來說,SurfaceFlinger服務需要繪制的是編號為1和2的Buffer所對應的圖形緩沖區GraphicBuffer。由於SurfaceFlinger服務知道編號為1和2的 Buffer 所對應的圖形緩沖區 GraphicBuffer 在哪裡,因此,Android 應用程序只需要告訴 SurfaceFlinger 服務要繪制的 Buffer 的編號就OK了。 當一個已經被使用了的Buffer被繪制了之後,它就重新變成一個空閑的 Buffer 了 。
SharedBufferStack 是在 Android 應用程序和 SurfaceFlinger 服務之間共享的,但是,Android 應用程序和 SurfaceFlinger 服務使用 SharedBufferStack 的方式是不一樣的,具體來說,就是 Android 應用程序關心的是它裡面的空閑緩沖區列表,而 SurfaceFlinger 服務關心的是它裡面的已經使用了的緩沖區列表。從SurfaceFlinger服務的角度來看,保存在 SharedBufferStack中 的已經使用了的緩沖區其實就是在排隊等待渲染。
為了方便 SharedBufferStack 在 Android 應用程序和 SurfaceFlinger 服務中的訪問,Android 系統分別使用 SharedBufferClient 和 SharedBufferServer 來描述 SharedBufferStack ,其中,SharedBufferClient 用來在Android 應用程序這一側訪問 SharedBufferStack 的空閑緩沖區列表,而 SharedBufferServer 用來在SurfaceFlinger 服務這一側訪問 SharedBufferStack 的排隊緩沖區列表。
只要 SharedBufferStack 中的 available 的 buffer 的數量大於0, SharedBufferClient 就會將指針 tail 往前移一步,並且減少 available 的值,以便可以獲得一個空閑的 Buffer。當 Android 應用程序往這個空閑的 Buffer 寫入好數據之後,它就會通過 SharedBufferClient 來將它添加到 SharedBufferStack 中的排隊緩沖區列表的尾部去,即指針 queue_head 的下一個位置上。
當 Android 應用程序通知 SurfaceFlinger 服務更新UI的時候,只要對應的 SharedBufferStack 中的 queued 的緩沖區的數量大於0,SharedBufferServer 就會將指針 head 的下一個Buffer繪制出來,並且將指針 head 向前移一步,以及將 queued 的值減1。
參考:
https://blog.csdn.net/luoshengyang/article/details/7846923
Ⅱ android 進程間的通信(IPC)方式有哪些
Android是基於linux內核的。所以linux支持的IPC,android都用到了。比如命名管道,共享內存。 除此外,android還使用了一套自己獨特的IPC方式 binder. 主要用於2個進程間的遠程調用。但是這里就牽扯遠程調用如何傳遞參數,如何回傳結果。 這需要調用者對數據進行打包和解包,是一個繁瑣的過程。為此,android引入了aidl(android interface description launguage). 開發人員定義好aidl,android會根據aidl的描述生產stub代碼,幫助調用者對數據打包,解包。開發人員所要做的事是繼承stub代碼,實現stub代碼中的函數。這些函數是你在aidl中定義的。
Ⅲ 如何偷Android的內存
MemoryFile是android在最開始就引入的一套框架,其內部實際上是封裝了android特有的內存共享機制Ashmem匿名共享內存,簡單來說,Ashmem在Android內核中是被注冊成一個特殊的字元設備,Ashmem驅動通過在內核的一個自定義slab緩沖區中初始化一段內存區域,然後通過mmap把申請的內存映射到用戶的進程空間中(通過tmpfs),這樣子就可以在用戶進程中使用這里申請的內存了,另外,Ashmem的一個特性就是可以在系統內存不足的時候,回收掉被標記為」unpin」的內存,這個後面會講到,另外,MemoryFile也可以通過Binder跨進程調用來讓兩個進程共享一段內存區域。由於整個申請內存的過程並不再Java層上,可以很明顯的看出使用MemoryFile申請的內存實際上是並不會佔用Java堆內存的。
MemoryFile暴露出來的用戶介面可以說跟他的名字一樣,基本上跟我們平時的文件的讀寫基本一致,也可以使用InputStream和OutputStream來對其進行讀寫等操作:
1
2
3
4
MemoryFile memoryFile = new MemoryFile(null, inputStream.available());
memoryFile.allowPurging(false);
OutputStream outputStream = memoryFile.getOutputStream();
outputStream.write(1024);
上面可以看到allowPurging這個調用,這個就是之前說的」pin」和」unpin」,在設置了allowPurging為false之後,這個MemoryFile對應的Ashmem就會被標記成」pin」,那麼即使在android系統內存不足的時候,也不會對這段內存進行回收。另外,由於Ashmem默認都是」unpin」的,因此申請的內存在某個時間點內都可能會被回收掉,這個時候是不可以再讀寫了
Tricks
MemoryFile是一個非常trickly的東西,由於並不佔用Java堆內存,我們可以將一些對象用MemoryFile來保存起來避免GC,另外,這里可能android上有個BUG:
在4.4及其以上的系統中,如果在應用中使用了MemoryFile,那麼在mpsys meminfo的時候,可以看到多了一項Ashmem的值:
可以看出來雖然MemoryFile申請的內存不計入Java堆也不計入Native堆中,但是佔用了Ashmem的內存,這個實際上是算入了app當前佔用的內存當中
但是在4.4以下的機器中時,使用MemoryFile申請的內存居然是不算入app的內存中的:
而且這里我也算過,也是不算入Native Heap中的,另外,這個時候去系統設置裡面看進程的內存佔用,也可以看出來其實並沒有計入Ashmem的內存的
這個應該是android的一個BUG,但是我搜了一下並沒有搜到對應的issue,搞不好這里也可能是一個feature
而在大名鼎鼎的Fresco當中,他們也有用到這個bug來避免在decode bitmap的時候,將文件的位元組讀到Java堆中,使用了MemoryFile,並利用了這個BUG然這部分內存不算入app中,這里分別對應了Fresco中的GingerbreadPurgeableDecoder和KitKatPurgeableDecoder,Fresco在decode圖片的時候會在4.4和4.4以下的系統中分別使用這兩個不同的decoder
從這個地方可以看出來,使用MemoryFile,在4.4以下的系統當中,可以幫我們的app額外」偷」一些內存,並且可以不計入app的內存當中
Summary
這里主要是簡單介紹了MemoryFile的基本原理和用法,並且闡述了一個MemoryFile中一個可以幫助開發者」偷」內存的地方,這個是一個非常trickly的方法,雖然4.4以下使用這塊的內存並不計入進程當中,但是並不推薦大量使用,因為當設置了allowPurging為false的時候,這個對應的Ashmem內存區域是被」pin」了,那麼在android系統內存不足的時候,是不能夠把這段內存區域回收的,如果長時間沒有釋放的話,這樣子相當於無端端佔用了大量手機內存而又無法回收,那對系統的穩定性肯定會造成影響
Ⅳ 為什麼Android要採用Binder作為IPC機制
1)從性能的角度
數據拷貝次數:Binder數據拷貝只需要一次,而管道、消息隊列、Socket都需要2次,但共享內存方式一次內存拷貝都不需要;從性能角度看,Binder性能僅次於共享內存。
(2)從穩定性的角度
Binder是基於C/S架構的,簡單解釋下C/S架構,是指客戶端(Client)和服務端(Server)組成的架構,Client端有什麼需求,直接發送給Server端去完成,架構清晰明朗,Server端與Client端相對獨立,穩定性較好;而共享內存實現方式復雜,沒有客戶與服務端之別, 需要充分考慮到訪問臨界資源的並發同步問題,否則可能會出現死鎖等問題;從這穩定性角度看,Binder架構優越於共享內存。
僅僅從以上兩點,各有優劣,還不足以支撐google去採用binder的IPC機制,那麼更重要的原因是:
(3)從安全的角度
傳統Linux IPC的接收方無法獲得對方進程可靠的UID/PID,從而無法鑒別對方身份;而Android作為一個開放的開源體系,擁有非常多的開發平台,App來源甚廣,因此手機的安全顯得額外重要;對於普通用戶,絕不希望從App商店下載偷窺隱射數據、後台造成手機耗電等等問題,傳統Linux IPC無任何保護措施,完全由上層協議來確保。
Android為每個安裝好的應用程序分配了自己的UID,故進程的UID是鑒別進程身份的重要標志,前面提到C/S架構,Android系統中對外只暴露Client端,Client端將任務發送給Server端,Server端會根據許可權控制策略,判斷UID/PID是否滿足訪問許可權,目前許可權控制很多時候是通過彈出許可權詢問對話框,讓用戶選擇是否運行。Android 6.0,也稱為Android M,在6.0之前的系統是在App第一次安裝時,會將整個App所涉及的所有許可權一次詢問,只要留意看會發現很多App根本用不上通信錄和簡訊,但在這一次性許可權許可權時會包含進去,讓用戶拒絕不得,因為拒絕後App無法正常使用,而一旦授權後,應用便可以胡作非為。
針對這個問題,google在Android M做了調整,不再是安裝時一並詢問所有許可權,而是在App運行過程中,需要哪個許可權再彈框詢問用戶是否給相應的許可權,對許可權做了更細地控制,讓用戶有了更多的可控性,但同時也帶來了另一個用戶詬病的地方,那也就是許可權詢問的彈框的次數大幅度增多。對於Android M平台上,有些App開發者可能會寫出讓手機異常頻繁彈框的App,企圖直到用戶授權為止,這對用戶來說是不能忍的,用戶最後吐槽的可不光是App,還有Android系統以及手機廠商,有些用戶可能就跳果粉了,這還需要廣大Android開發者以及手機廠商共同努力,共同打造安全與體驗俱佳的Android手機。
Android中許可權控制策略有SELinux等多方面手段,下面列舉從Binder的一個角度的許可權控制:
Android源碼的Binder許可權是如何控制? -Gityuan的回答
傳統IPC只能由用戶在數據包里填入UID/PID;另外,可靠的身份標記只有由IPC機制本身在內核中添加。其次傳統IPC訪問接入點是開放的,無法建立私有通道。從安全形度,Binder的安全性更高。
說到這,可能有人要反駁,Android就算用了Binder架構,而現如今Android手機的各種流氓軟體,不就是干著這種偷窺隱射,後台偷偷跑流量的事嗎?沒錯,確實存在,但這不能說Binder的安全性不好,因為Android系統仍然是掌握主控權,可以控制這類App的流氓行為,只是對於該採用何種策略來控制,在這方面android的確存在很多有待進步的空間,這也是google以及各大手機廠商一直努力改善的地方之一。在Android 6.0,google對於app的許可權問題作為較多的努力,大大收緊的應用許可權;另外,在Google舉辦的Android Bootcamp 2016大會中,google也表示在Android 7.0 (也叫Android N)的許可權隱私方面會進一步加強加固,比如SELinux,Memory safe language(還在research中)等等,在今年的5月18日至5月20日,google將推出Android N。
(4)從語言層面的角度
大家多知道Linux是基於C語言(面向過程的語言),而Android是基於Java語言(面向對象的語句),而對於Binder恰恰也符合面向對象的思想,將進程間通信轉化為通過對某個Binder對象的引用調用該對象的方法,而其獨特之處在於Binder對象是一個可以跨進程引用的對象,它的實體位於一個進程中,而它的引用卻遍布於系統的各個進程之中。可以從一個進程傳給其它進程,讓大家都能訪問同一Server,就像將一個對象或引用賦值給另一個引用一樣。Binder模糊了進程邊界,淡化了進程間通信過程,整個系統彷彿運行於同一個面向對象的程序之中。從語言層面,Binder更適合基於面向對象語言的Android系統,對於Linux系統可能會有點「水土不服」。
另外,Binder是為Android這類系統而生,而並非Linux社區沒有想到Binder IPC機制的存在,對於Linux社區的廣大開發人員,我還是表示深深佩服,讓世界有了如此精湛而美妙的開源系統。也並非Linux現有的IPC機制不夠好,相反地,經過這么多優秀工程師的不斷打磨,依然非常優秀,每種Linux的IPC機制都有存在的價值,同時在Android系統中也依然採用了大量Linux現有的IPC機制,根據每類IPC的原理特性,因時制宜,不同場景特性往往會採用其下最適宜的。比如在Android OS中的Zygote進程的IPC採用的是Socket(套接字)機制,Android中的Kill Process採用的signal(信號)機制等等。而Binder更多則用在system_server進程與上層App層的IPC交互。
(5) 從公司戰略的角度
總所周知,Linux內核是開源的系統,所開放源代碼許可協議GPL保護,該協議具有「病毒式感染」的能力,怎麼理解這句話呢?受GPL保護的Linux Kernel是運行在內核空間,對於上層的任何類庫、服務、應用等運行在用戶空間,一旦進行SysCall(系統調用),調用到底層Kernel,那麼也必須遵循GPL協議。
而Android 之父 Andy Rubin對於GPL顯然是不能接受的,為此,Google巧妙地將GPL協議控制在內核空間,將用戶空間的協議採用Apache-2.0協議(允許基於Android的開發商不向社區反饋源碼),同時在GPL協議與Apache-2.0之間的Lib庫中採用BSD證授權方法,有效隔斷了GPL的傳染性,仍有較大爭議,但至少目前緩解Android,讓GPL止步於內核空間,這是Google在GPL Linux下 開源與商業化共存的一個成功典範。
Ⅳ Android跨進程通信-mmap函數
通過mmap或者內存共享的Linux IPC機制
直接世猛將同一段內存映射到數據發送進程和數據接收進程的用戶空間,這樣數據發送進程只需要將數據拷貝到共享的內存區域,數據接收進程就可以直接使用數據了。
mmap是一個很重要的函數,它可以實現共享內存,但並不像SystemV和Posix的共享內存存粹的只用於共享內存,橋返飢mmap()的設計,主要是用來做文件的映射的,它提供了我們一種新的訪問文件的方案。
mmap函數的使用非常簡單,我們來看一下
常規文件操作為了提高讀寫效率和保護磁碟,使用了 頁緩存機制 ,這種機制會造成讀文件時需要先將文件頁從磁碟拷貝到頁緩存中,由於 頁緩存處在內核空間 ,不能被用戶進程直接定址,所以還需要 將頁緩存中數據頁再次拷貝到內存 對應的用戶空間中。
常規文件操作為了提高讀寫效率和保護磁碟,使用了頁緩存機制,這種機制會造成讀文件時需要先將文件頁從磁碟拷貝到頁緩存中,由於頁緩存處在內核空間,不能被用戶進程直接定址,所以還需要將頁緩存中數據頁再次拷貝到內存對應的用戶空間中。
而 使用mmap操作文件中,由於不需要經過內核空間的數據緩存,只使用一次數據拷貝,就從磁碟中將數據傳入內存的用戶空間中,供進程使用 。
mmap的關鍵點是實現了用戶空間和內核空間的數據直接交互而省去了空間不同數據不通的繁瑣過程,因此mmap效率很高。
mmap()使用非常頻繁,看過Android系統源碼的人,肯定看到過大量的地方使用mmap()函數,比如上面提到的 匿名共享內存的使用就使用到了mmap來映射/dev/ashmem里的文件 。
這里我再介紹一種mmap()在Android系統上的使用場景, mmap的設計目的就是為了讓文件的訪問更有效率 ,所以當APK進行安裝時,為了更高效的讀取APK包裡面的文件,同樣也用到了mmap函數。
Dalvik在安裝應敏返用時,需要載入dex文件,然後進行odex優化處理,優化函數為dvmContinueOptimization,我們看一下他的大致實現。
可以看到,dvmContinueOptimization函數中對dex文件的載入便用了mmap內存映射函數。