Android視窗管理分析(1):View如何繪製到螢幕上的主觀理解

看書的小蝸牛發表於2017-08-23

視窗管理可以說是Android系統中最複雜的一部分,主要是它涉及的模組比較多,雖然說是視窗管理,但除了WindowManagerService還包括SurfaceFlinger服務、Linux的共享記憶體及tmpfs檔案系統、Binder通訊、InputManagerService、動畫、VSYNC同步技術等,一篇文章不可能分析完全,但是可以首先對於視窗的顯示與管理有一個大概的輪廓,再分塊分解,涉及的知識點大概如下:

視窗管理知識圖譜.png
視窗管理知識圖譜.png

WMS的作用是視窗管理 不負責View繪製

既然是概述,我們不妨直觀的思考一個問題,Activity是如何呈現到螢幕上的,或者說View是如何被繪製到螢幕上來的?或多或少,開發者都知道WindowManagerService是負責Android的視窗管理,但是它其實只負責管理,比如視窗的新增、移除、調整順序等,至於影象的繪製與合成之類的都不是WMS管理的範疇,WMS更像在更高的層面對於Android視窗的一個抽象,真正完成影象繪製的是APP端,而完成圖層合成的是SurfaceFlinger服務。這裡通過一個簡單的懸浮視窗來探索一下大概流程:

    TextView mview=new TextView(context);
    ...<!--設定顏色 樣式-->
    WindowManager mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    WindowManager.LayoutParams wmParams = new WindowManager.LayoutParams();
    wmParams.type = WindowManager.LayoutParams.TYPE_TOAST;
    wmParams.format = PixelFormat.RGBA_8888;
    wmParams.width = 800;
    wmParams.height = 800;
    mWindowManager.addView(mview, wmParams);複製程式碼

以上程式碼可以在主螢幕上新增一個TextView並展示,並且這個TextView獨佔一個視窗。在利用WindowManager.addView新增視窗之前,TextView的onDraw不會被呼叫,也就說View必須被新增到視窗中,才會被繪製,或者可以這樣理解,只有申請了依附視窗,View才會有可以繪製的目標記憶體。當APP通過WindowManagerService的代理向其新增視窗的時候,WindowManagerService除了自己進行登記整理,還需要向SurfaceFlinger服務申請一塊Surface畫布,其實主要是畫布背後所對應的一塊記憶體,只有這一塊記憶體申請成功之後,APP端才有繪圖的目標,並且這塊記憶體是APP端同SurfaceFlinger服務端共享的,這就省去了繪圖資源的拷貝,示意圖如下:

繪圖原理.jpg
繪圖原理.jpg

以上是抽象的圖層對應關係,可以看到,APP端是可以通過unLockCanvasAndPost直接同SurfaceFlinger通訊進行重繪的,就是說圖形的繪製同WMS沒有關係,WMS只是負責視窗的管理,並不負責視窗的繪製,這一點其實也可以從IWindowSession的binder通訊介面看出來:

interface IWindowSession {

    int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, out Rect outContentInsets,
            out InputChannel outInputChannel);

    int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            in int viewVisibility, in int layerStackId, out Rect outContentInsets,
            out InputChannel outInputChannel);

    int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewVisibility,
            int flags, out Rect outFrame, out Rect outOverscanInsets,
            out Rect outContentInsets, out Rect outVisibleInsets,
            out Configuration outConfig, out Surface outSurface);

    void remove(IWindow window);
...
}複製程式碼

從引數就可以看出,APP與WindowManagerService通訊的時候沒有任何View相關的資訊,更不會說將檢視的資料傳遞給WMS,基本都是以IWindow為基本單位進行通訊的,所以涉及的操作也都是針對視窗的,比如整個視窗的新增、移除、大小調整、分組等,單單從視窗顯示來看,WMS的作用確實很明確,就是在服務端登記當前存活視窗,後面還會看到,這會影響SurfaceFlinger的圖層混合,可以說是為SurfaceFlinger服務的。

在對於日常開發來說,WMS的視窗分組有時候會對開發帶來影響,如果不知道視窗分組管理,可能有點忙迷惑,比如Dialog必須使用Activity的Context,PopupWindow不能作為父視窗,尤其要避免作為Webview的容器等,這些都跟WMS視窗的組織有關係。PopupWindow、Dialog、Activity三者都有視窗的概念,但又各有不同,Activity屬於應用視窗、PopupWindow屬於子視窗,而Dialog位於兩者之間,從性質上說屬於應用視窗,但是從直觀理解上,比較像子視窗(其實不是)。Android中的視窗主要分為三種:系統視窗、應用視窗、子視窗,Toast就屬於系統視窗,而Dialog、Activity屬於應用視窗,不過Dialog必須依附Activity才能存在。PopupWindow算是子視窗,必須依附到其他視窗,依附的視窗可以使應用視窗也可以是系統視窗,但是不能是子視窗。

視窗組織形式.jpg
視窗組織形式.jpg

當然,WMS的作用不僅只是管理視窗,它還負責視窗動畫、Touch事件等,後面會逐個模組分析。

View繪製與資料傳遞

既然WMS的作用只是視窗管理,那麼圖形是怎麼繪製的呢?並且這些繪製資訊是如何傳遞給SurfaceFlinger服務的呢?每個View都有自己的onDraw回撥,開發者可以在onDraw裡繪製自己想要繪製的影象,很明顯View的繪製是在APP端,直觀上理解,View的繪製也不會交給服務端,不然也太不獨立了,可是View繪製的記憶體是什麼時候分配的呢?是誰分配的呢?我們知道每個Activity可以看做是一個圖層,其對應一塊繪圖表面其實就是Surface,Surface繪圖表面對應的記憶體其實是由SurfaceFlinger申請的,並且,記憶體是APP與SurfaceFlinger間程式共享的。實現機制是基於Linux的共享記憶體,其實就是MAP+tmpfs檔案系統,你可以理解成SF為APP申請一塊記憶體,然後通過binder將這塊記憶體相關的資訊傳遞APP端,APP端往這塊記憶體中繪製內容,繪製完畢,通知SF圖層混排,之後,SF再將資料渲染到螢幕。其實這樣做也很合理,因為影象記憶體比較大,普通的binder與socket都無法滿足需求,記憶體共享的示意圖如下:

View繪製與共享記憶體.jpg
View繪製與共享記憶體.jpg

總結

其實整個Android視窗管理簡化的話可以分為以下三部分

  • WindowManagerService:WMS控制著Surface畫布的新增與次序,動畫還有觸控事件
  • SurfaceFlinger:SF負責圖層的混合,並且將結果傳輸給硬體顯示
  • APP端:每個APP負責相應圖層的繪製,
  • APP與SurfaceFlinger通訊:APP與SF圖層之間資料的共享是通過匿名記憶體來實現的。

作者:看書的小蝸牛
原文連結: Android視窗管理分析(1):視窗管理及主觀理解

僅供參考,歡迎指正

相關文章