Android視窗管理分析(2):WindowManagerService視窗管理之Window新增流程

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

之前分析說過,WindowManagerService只負責視窗管理,並不負責View的繪製跟圖層混合,本文就來分析WMS到底是怎麼管理視窗的。初接觸Android時感覺:Activity似乎就是Google封裝好的視窗,APP只要合理的啟動新的Activity就開啟了新視窗,這樣理解沒什麼不對,Activity確實可以看做一種視窗及View的封裝,不過從原始碼來看,Activity跟Window還是存在不同。本文主要從視窗的新增流程來將APP端、WMS端、SurfaceFlinger端三塊串聯起來,主要說一下幾個方面

  • 視窗的分類:Activity、Dialog、PopupWindow、Toast等對應視窗的區別
  • Window、IWindow 、WindowState、WindowToken、AppToken等之間的關係
  • 視窗的新增及Surface申請與Binder傳遞

視窗的分類簡述

在Android系統中,PopupWindow、Dialog、Activity、Toast等都有視窗的概念,但又各有不同,Android將視窗大致分為三類:應用視窗、子視窗、系統視窗。其中,Activity與Dialog屬於應用視窗、PopupWindow屬於子視窗,必須依附到其他非子視窗才能存在,而Toast屬於系統視窗,Dialog可能比較特殊,從表現上來說偏向於子視窗,必須依附Activity才能存在,但是從性質上來說,仍然是應用視窗,有自己的WindowToken,不同視窗之間的關係後面會更加詳細的分析,這裡有一個概念即可。

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

視窗的新增

Activity並不是View展示的唯一方式,分析視窗新增流程的話,Activity也並不是最好的例子,因為Activity還會牽扯到AMS的知識,這裡我們不用Activity,而是用一個懸浮View的展示來分析視窗的新增,程式碼入下:

private void addTextViewWindow(Context context){

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

這有三點比較關鍵,關鍵點1:獲取WindowManagerService服務的代理物件,不過對於Application而言,獲取到的其實是一個封裝過的代理物件,一個WindowManagerImpl例項,Application 的getSystemService()原始碼其實是在ContextImpl中:有興趣的可以看看APP啟動時Context的建立:

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }複製程式碼

SystemServiceRegistry類用靜態欄位及方法中封裝了一些服務的代理,其中就包括WindowManagerService

    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

    static {
             ...
             registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx.getDisplay());
            }});
            ...
    }複製程式碼

因此context.getApplicationContext().getSystemService()最終可以簡化為new WindowManagerImpl(ctx.getDisplay()),下面看下WindowManagerImpl的構造方法,它有兩個實現方法,對於Activity跟Application其實是有區別的,這點後面分析:

public WindowManagerImpl(Display display) {
    this(display, null);
}

private WindowManagerImpl(Display display, Window parentWindow) {
    mDisplay = display;
    mParentWindow = parentWindow;
}複製程式碼

對於Application採用的是一參構造方法,所以其mParentWindow=null,這點後面會有用,到這裡,通過getService獲取WMS代理的封裝類,接著看第二點,WindowManager.LayoutParams,主要看一個type引數,這個引數決定了視窗的型別,這裡我們定義成一個Toast視窗,屬於系統視窗,不需要處理父視窗、子視窗之類的事,更容易分析,最後看關鍵點3,利用WindowManagerImpl的addView方法新增View到WMS,

 @Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}複製程式碼

不過很明顯WindowManagerImpl最後是委託mGlobal來進行這項操作,WindowManagerGlobal是一個單利,一個程式只有一個:

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();複製程式碼

接著看WindowManagerGlobal的addView,對於新增系統視窗,這裡將將程式碼精簡一下,不關係子視窗等之類的邏輯

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        <!--關鍵點1-->
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
    }
   <!--關鍵點2-->
    try {
        root.setView(view, wparams, panelParentView);
    }           
     ...  }複製程式碼

先看關鍵點1,在向WMS新增View的時候,WindowManagerGlobal首先為View新建了一個ViewRootImpl,ViewRootImpl可以看做也是Window和View之間的通訊的紐帶,比如將View新增到WMS、處理WMS傳入的觸控事件、通知WMS更新視窗大小等、同時ViewRootImpl也封裝了View的繪製與更新方法等。看一下ViewRootImpl如何通過setView將檢視新增到WMS的:

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                  ...
                    <!--關鍵點1 -->
                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                try {
                    <!--關鍵點2 -->
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
               ...複製程式碼

先看關鍵點1,這裡是先為relayout佔一個位置,其實是依靠Handler先傳送一個Message,排在所有WMS傳送過來的訊息之前,先佈局繪製一次,之後才會處理WMS傳來的各種事件,比如觸控事件等,畢竟要首先將各個View的佈局、位置處理好,才能準確的處理WMS傳來的事件。接著看做關鍵點2,這裡才是真正新增視窗的地方,雖然關鍵點1執行在前,但是用的是Handler發訊息的方式來處理,其Runable一定是在關鍵點2之後執行,接著看關鍵點2,這裡有個比較重要的物件mWindowSession與mWindow,兩者都是在ViewRootImpl在例項化的時候建立的:

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();
    mWindow = new W(this);複製程式碼

mWindowSession它是通過WindowManagerGlobal.getWindowSession獲得的一個Binder服務代理,是App端向WMS傳送訊息的通道。相對的,mWindow是一個W extends IWindow.Stub Binder服務物件,其實可以看做是App端的視窗物件,主要作用是傳遞給WMS,並作為WMS向APP端傳送訊息的通道,在Android系統中存在大量的這種互為C\S的場景。接著看mWindowSession獲取的具體操作是:首先通過getWindowManagerService 獲取WMS的代理,之後通過WMS的代理在服務端open一個Session,並在APP端獲取該Session的代理:

 public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                InputMethodManager imm = InputMethodManager.getInstance();
                <!--關鍵點1-->
                IWindowManager windowManager = getWindowManagerService();
                <!--關鍵點2-->
                sWindowSession = windowManager.openSession(***)
                ...
        return sWindowSession;
    }
}複製程式碼

看關鍵點1 :首先要記住sWindowSession是一個單例的物件,之後就可以將getWindowManagerService函式其實可以簡化成下面一句程式碼,其實就是獲得WindowManagerService的代理,之前的WindowManagerImpl都是一個殼子,或者說介面封裝,並未真正的獲得WMS的代理:

    IWindowManager.Stub.asInterface(ServiceManager.getService("window"))複製程式碼

再看關鍵點2:sWindowSession = windowManager.openSession,它通過binder驅動後,會通知WMS回撥openSession,開啟一個Session返回給APP端,而Session extends IWindowSession.Stub ,很明顯也是一個Binder通訊的Stub端,封裝了每一個Session會話的操作。

@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    Session session = new Session(this, callback, client, inputContext);
    return session;
}複製程式碼

到這裡看到如何獲取Session,下面就是利用Session來add一個視窗:其實是呼叫Session.java的addToDisplayWithoutInputChannel函式

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        Rect outOutsets, InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}複製程式碼

不過它又反過來去呼叫WMS的addWindow,繞這麼大一圈,並且APP端IWindowSession還是單例的,為什麼不直接用WMS來處理呢?疑惑,在WMS中addWindow又做了什麼呢,就像名字寫的,負責新增一個視窗,程式碼精簡後如下:

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        ...
        synchronized(mWindowMap) {
        ...
        <!--關鍵點1 不能重複新增-->
            if (mWindowMap.containsKey(client.asBinder())) {
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
        <!--關鍵點2 對於子視窗型別的處理 1、必須有父視窗 2,父視窗不能是子視窗型別-->
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }}
           ...
           boolean addToken = false;
            <!--關鍵點3 根據IWindow 獲取WindowToken WindowToken是視窗分組的基礎,每個視窗必定有一個分組-->
            WindowToken token = mTokenMap.get(attrs.token);
          <!--關鍵點4對於Toast類系統視窗,其attrs.token可以看做是null, 如果目前沒有其他的類似系統視窗展示,token仍然獲取不到,仍然要走新建流程-->
            if (token == null) {
            ...       
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } 
            ...
             <!--關鍵點5 新建WindowState,WindowState與視窗是一對一的關係,可以看做是WMS中與視窗的抽象實體-->
            WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
            ...
            if (addToken) {
                mTokenMap.put(attrs.token, token);
            }
            win.attach();
            mWindowMap.put(client.asBinder(), win);
            ...    
           <!--關鍵點6-->
          addWindowToListInOrderLocked(win, true);
        return res;
    }複製程式碼

這裡有幾個概念需要先了解下:

  • IWindow:APP端視窗暴露給WMS的抽象例項,在ViewRootImpl中例項化,與ViewRootImpl一一對應,同時也是WMS向APP端傳送訊息的Binder通道。
  • WindowState:WMS端視窗的令牌,與IWindow,或者說與視窗一一對應,是WMS管理視窗的重要依據。
  • WindowToken:視窗的令牌,其實也可以看做視窗分組的依據,在WMS端,與分組對應的資料結構是WindowToken(視窗令牌),而與組內每個視窗對應的是WindowState物件,每塊令牌(AppWindowToken、WindowToken)都對應一組視窗(WindowState),Activity與Dialog對應的是AppWindowToken,PopupWindow對應的是普通的WindowToken。
  • AppToken:其實是ActivityRecord裡面的IApplicationToken.Stub appToken 代理,也是ActivityClientRecord裡面的token,可以看做Activity在其他服務(非AMS)的抽象

WindowToken與WindowState關係.jpg
WindowToken與WindowState關係.jpg

那麼接著關鍵點1:一個視窗不能被新增兩次,IWindow是一個Binder代理,在WMS端,一個視窗只會有一個IWindow代理,這是由Binder通訊機制保證的,這個物件不能被新增兩次,否則會報錯。關鍵點2,如果是子視窗的話,父視窗必須已被新增,由於我們分析的是系統Toast視窗,可以先不用關心;關鍵點3,WindowManager.LayoutParams中有一個token欄位,該欄位標誌著視窗的分組屬性,比如Activity及其中的Dialog是複用用一個AppToken,Activity裡的PopupWindow複用一個IWindow型別Token,其實就是Activity的ViewRootImpl裡面建立的IWindow,而對於我們現在新增的Toast類系統視窗,並未設定其attrs.token,那即是null,其實所有的Toast類系統視窗的attrs.token都可以看做null,就算不是null,也會在WMS被強制設定為null。所以Toast類系統視窗必定複用一個WindowToken,也可以說所有的Toast類系統視窗都是位於同一分組,這也是因為該型別系統視窗太常用,而且為所有程式服務,直接用一個WindowToken管理更加快捷,畢竟快速新建與釋放WindowToken也算是一種開銷。假設到我們新增系統視窗的時候,沒有任何系統視窗展示,是獲取不到key=null的WindowToken的,要新建WindowToken,並且新增到全域性的TokenMap中,而關鍵點5,其實就是新建視窗在WMS端的抽象例項:WindowState,它同視窗一一對應,詳細記錄了視窗的引數、Z順序、狀態等各種資訊,新建只有會被放入全域性的Map中,同時也會被附加到相應的WindowToken分組中去,到這裡APP端向WMS註冊視窗的流程就算走完了,不過只算完成了前半部分,WMS還需要向SurfaceFlinger申請Surface,才算完成真正的分配了視窗。在向SurfaceFlinger申請Surface之前,WMS端需要獲得SF的代理,在WindowState物件建立後會利用 win.attach()函式為當前APP申請建立SurfaceFlinger的連結:

void attach() {
    if (WindowManagerService.localLOGV) Slog.v(
    mSession.windowAddedLocked();
}

void windowAddedLocked() {
    if (mSurfaceSession == null) {
       // SurfaceSession新建
        mSurfaceSession = new SurfaceSession();
        mService.mSessions.add(this);
       ...
    }
    mNumWindow++;
}複製程式碼

可以看到SurfaceSession對於Session來說是單利的,也就是與APP的Seesion一一對應,SurfaceSession所握著的SurfaceFlinger的代理其實就是SurfaceComposerClient,其實現如下:

    public SurfaceSession() {
        mNativeClient = nativeCreate();
    }

    static jlong nativeCreate(JNIEnv* env, jclass clazz) {
        SurfaceComposerClient* client = new SurfaceComposerClient();
        client->incStrong((void*)nativeCreate);
        return reinterpret_cast<jlong>(client);
    }複製程式碼

Session與APP程式是一一對應的,它會進一步為當前程式建立SurfaceSession會話,可以這麼理解:Session是APP同WMS通訊的通道,SurfaceSession是WMS為APP向SurfaceFlinger申請的通訊通道,同樣 SurfaceSession與APP也是一一對應的,既然是同SurfaceFlinger通訊的信使,那麼SurfaceSession就應該握著SurfaceFlinger的代理,其實就是SurfaceComposerClient裡的ISurfaceComposerClient mClient物件,它是SurfaceFlinger為每個APP封裝一個代理,也就是 程式 <-> Session <-> SurfaceSession <-> SurfaceComposerClient <-> ISurfaceComposerClient(BpSurfaceComposerClient) 五者是一條線, 為什麼不直接與SurfaceFlinger通訊呢?大概為了SurfaceFlinger管理每個APP的Surface比較方便吧,這四個類的模型如下圖:

Session及SurfaceComposerClient類圖
Session及SurfaceComposerClient類圖

至於ISurfaceComposerClient(BpSurfaceComposerClient) 究竟是怎麼樣一步步建立的,其實它是利用ComposerService這樣一個單利物件為為每個APP在WMS端申請一個ISurfaceComposerClient物件,在WMS端表現為BpSurfaceComposerClient,在SurfaceFlinger端表現為BnSurfaceComposerClient,具體程式碼如下:

SurfaceComposerClient::SurfaceComposerClient()
    : mStatus(NO_INIT), mComposer(Composer::getInstance())
{
}
// 單利的,所以只有第一次的時候採用
void SurfaceComposerClient::onFirstRef() {
    sp<ISurfaceComposer> sm(ComposerService::getComposerService());
    if (sm != 0) {
        sp<ISurfaceComposerClient> conn = sm->createConnection();
        if (conn != 0) {
            mClient = conn;
            mStatus = NO_ERROR;
        }
    }
}

sp<ISurfaceComposerClient> SurfaceFlinger::createConnection()
{
    sp<ISurfaceComposerClient> bclient;
    sp<Client> client(new Client(this));
    status_t err = client->initCheck();
    if (err == NO_ERROR) {
        bclient = client;
    }
    return bclient;
}複製程式碼

SurfaceComposer類圖
SurfaceComposer類圖

剛才說完成了前半部分,主要針對WMS的視窗管理,後半部分則是圍繞Surface的分配來進行的,還記得之前ViewRootImpl在setView時候分了兩步嗎?雖然先呼叫requestLayout先執行,但是由於其內部利用Handler傳送訊息延遲執行的,所以可以看做requestLayout是在addWindow之後執行的,那麼這裡就看新增視窗之後,如何分配Surface的,requestLayout函式呼叫裡面使用了Hanlder的一個小手段,那就是利用postSyncBarrier新增了一個Barrier(擋板),這個擋板的作用是阻塞普通的同步訊息的執行,在擋板被撤銷之前,只會執行非同步訊息,而requestLayout先新增了一個擋板Barrier,之後自己插入了一個非同步任務mTraversalRunnable,其主要作用就是保證mTraversalRunnable在所有同步Message之前被執行,保證View繪製的最高優先順序。具體實現如下:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;

            <!--關鍵點1 新增塞子-->
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        <!--關鍵點2 新增非同步訊息任務-->
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ...複製程式碼

mTraversalRunnable任務的主要作用是:如果Surface未分配,則請求分配Surface,並測量、佈局、繪圖,其執行主體其實是performTraversals()函式,該函式包含了APP端View繪製大部分的邏輯, performTraversals函式很長,這裡只簡要看幾個點,其實主要是關鍵點1:relayoutWindow:

private void performTraversals() {
            final View host = mView;
               ...
    if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null) {
            <!--關鍵點1 申請Surface或者重新設定引數-->
            relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
          <!--關鍵點2 測量-->
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            }        
          <!--關鍵點3 佈局-->
                performLayout(lp, desiredWindowWidth, desiredWindowHeight);
           <!--關鍵點4 更新window-->
              try {
                mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
                        contentInsets, visibleInsets, touchableRegion);
            ...
          <!--關鍵點5 繪製-->
           performDraw();
           ...  
       }複製程式碼

relayoutWindow主要是通過mWindowSession.relayout向WMS申請或者更新Surface如下,這裡只關心一個重要的引數mSurface,在Binder通訊中mSurface是一個out型別的引數,也就是Surface內部的內容需要WMS端負責填充,並回傳給APP端:

   private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
       ...
        int relayoutResult = mWindowSession.relayout(
                mWindow, mSeq, params, ...  mSurface);
        ...
        return relayoutResult;
    }複製程式碼

看下到底relayout是如何想SurfaceFlinger申請Surface的。我們知道每個視窗都有一個WindowState與其對應,另外每個視窗也有自己的動畫,比如入場/出廠動畫,而WindowStateAnimator就是與WindowState的動畫,為什麼要提WindowStateAnimator,因為WindowStateAnimator是

 public int relayoutWindow(Session session, IWindow client, int seq,... Surface outSurface) {
         WindowState win = windowForClientLocked(session, client, false);
        WindowStateAnimator winAnimator = win.mWinAnimator;
         <!--關鍵點1 -->
           SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
           if (surfaceControl != null) {
         <!--關鍵點2 -->
             outSurface.copyFrom(surfaceControl);
                } else {
                    outSurface.release();
                }複製程式碼

這裡只看Surface建立程式碼,首先通過windowForClientLocked找到WindowState,利用WindowState的WindowStateAnimator成員建立一個SurfaceControl,SurfaceControl會呼叫native函式nativeCreate(session, name, w, h, format, flags)建立Surface,

static jlong nativeCreate(JNIEnv* env, jclass clazz, jobject sessionObj,
        jstring nameStr, jint w, jint h, jint format, jint flags) {
    ScopedUtfChars name(env, nameStr);
    <!--關鍵點1-->
    sp<SurfaceComposerClient> client(android_view_SurfaceSession_getClient(env, sessionObj));
    <!--關鍵點2-->
    sp<SurfaceControl> surface = client->createSurface(
            String8(name.c_str()), w, h, format, flags);
    surface->incStrong((void *)nativeCreate);
    return reinterpret_cast<jlong>(surface.get());
}複製程式碼

關鍵點1是取到SurfaceSession物件中SurfaceComposerClient物件,之後呼叫SurfaceComposerClient的createSurface方法進一步建立SurfaceControl,

sp<SurfaceControl> SurfaceComposerClient::createSurface(
        const String8& name,
        uint32_t w,
        uint32_t h,
        PixelFormat format,
        uint32_t flags)
{
    sp<SurfaceControl> sur;
    if (mStatus == NO_ERROR) {
        sp<IBinder> handle;
        sp<IGraphicBufferProducer> gbp;
        <!--關鍵點1 獲取圖層的關鍵資訊handle, gbp-->
        status_t err = mClient->createSurface(name, w, h, format, flags,
                &handle, &gbp);
         <!--關鍵點2 根據返回的圖層關鍵資訊 建立SurfaceControl物件-->
        if (err == NO_ERROR) {
            sur = new SurfaceControl(this, handle, gbp);
        }
    }
    return sur;
}複製程式碼

這裡先看mClient->createSurface,SurfaceComposerClient的mClient其實是一個BpSurfaceComposerClient物件,它SurfaceFlinger端Client在WMS端的代理,因此建立Surface的程式碼還是在SurfaceFlinger服務端的Client物件中,這裡有兩個關鍵的變數sp handle與 sp gbp,前者標誌在SurfaceFlinger端的圖層,後者用來建立GraphicBuffer,兩者型別都是IBinder型別,同時也是需要SurfaceFlinger填充的物件,這兩者是一個圖層對應的最關鍵的資訊:

status_t Client::createSurface(
        const String8& name,
        uint32_t w, uint32_t h, PixelFormat format, uint32_t flags,
        sp<IBinder>* handle,
        sp<IGraphicBufferProducer>* gbp){
    ...
    <!--關鍵點2 這裡並未直接建立 ,而是通過傳送了一個MessageCreateLayer訊息-->
    sp<MessageBase> msg = new MessageCreateLayer(mFlinger.get(),
            name, this, w, h, format, flags, handle, gbp);
    mFlinger->postMessageSync(msg);
    return static_cast<MessageCreateLayer*>( msg.get() )->getResult();
}複製程式碼

Client 並不會直接新建圖層,而是向SurfaceFlinger傳送一個MessageCreateLayer訊息,通知SurfaceFlinger服務去執行,其handler程式碼如下:

 class MessageCreateLayer : public MessageBase {
        SurfaceFlinger* flinger;
        Client* client;
              virtual bool handler() {
            result = flinger->createLayer(name, client, w, h, format, flags,
                    handle, gbp);
            return true;
        }
    };複製程式碼

其實就是呼叫SurfaceFlinger的createLayer,建立一個圖層,到這裡才是真正的建立圖層:

status_t SurfaceFlinger::createLayer(
        const String8& name,
        const sp<Client>& client,
        uint32_t w, uint32_t h, PixelFormat format, uint32_t flags,
        sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp)
{
    if (int32_t(w|h) < 0) {
        return BAD_VALUE;
    }

    status_t result = NO_ERROR;

    sp<Layer> layer;
  <!--關鍵點1 新建不同圖層-->
    switch (flags & ISurfaceComposerClient::eFXSurfaceMask) {
        case ISurfaceComposerClient::eFXSurfaceNormal:
            result = createNormalLayer(client,
                    name, w, h, flags, format,
                    handle, gbp, &layer);
            break;
        case ISurfaceComposerClient::eFXSurfaceDim:
            result = createDimLayer(client,
                    name, w, h, flags,
                    handle, gbp, &layer);
            break;
        default:
            result = BAD_VALUE;
            break;
    }

    if (result != NO_ERROR) {
        return result;
    }
   ...
}複製程式碼

SurfaceFlinger會根據不同的視窗引數,建立不同型別的圖層,這裡只看一下createNormalLayer普通樣式的圖層,

status_t SurfaceFlinger::createNormalLayer(const sp<Client>& client,
        const String8& name, uint32_t w, uint32_t h, uint32_t flags, PixelFormat& format,
        sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp, sp<Layer>* outLayer)
{
    // initialize the surfaces
    switch (format) {
    case PIXEL_FORMAT_TRANSPARENT:
    case PIXEL_FORMAT_TRANSLUCENT:
        format = PIXEL_FORMAT_RGBA_8888;
        break;
    case PIXEL_FORMAT_OPAQUE:
        format = PIXEL_FORMAT_RGBX_8888;
        break;
    }
    <!--關鍵點 1 -->
    *outLayer = new Layer(this, client, name, w, h, flags);
    status_t err = (*outLayer)->setBuffers(w, h, format, flags);
    <!--關鍵點 2-->
    if (err == NO_ERROR) {
        *handle = (*outLayer)->getHandle();
        *gbp = (*outLayer)->getProducer();
    }
  return err;
}複製程式碼

可以看到 圖層最終對應的是Layer,這裡會新建一個Layer物件,Layer中包含著與這個圖層對應的Handle及Producer物件,Handle可以看做是Surface的唯一性標識,不過好像沒太大的作用,最多是一個標識,將來清理的時候有用。相比之下gbp = (*outLayer)->getProducer()比較重要,它實際是一個BufferQueueProducer物件,關係到共享記憶體的分配問題,後面會專門分析,這裡到此打住,我們終於得到了一個圖層物件,到這裡之後,我們梳理一下,圖層如何建立的:

  • 首先APP端新建一個Surface圖層的容器殼子,
  • APP通過Binder通訊將這個Surface的殼子傳遞給WMS,
  • WMS為了填充Surface去向SurfaceFlinger申請真正的圖層,
  • SurfaceFlinger收到WMS請求為APP端的Surface分配真正圖層
  • 將圖層相關的關鍵資訊Handle及Producer傳遞給WMS

Layer建立之後,SurfaceFlinger會將圖層標識資訊Handle及Producer傳遞給WMS,WMS利用這兩者建立一個SurfaceControl物件,之後再利用該物件建立Surface,具體程式碼如下:

void getSurface(Surface outSurface) {
    outSurface.copyFrom(mSurfaceControl);
}

public void copyFrom(SurfaceControl other) {
long surfaceControlPtr = other.mNativeObject;
long newNativeObject = nativeCreateFromSurfaceControl(surfaceControlPtr);
synchronized (mLock) {
    setNativeObjectLocked(newNativeObject);
}
}複製程式碼

可以看到Surface的拷貝函式其實就是直接修改Surface native物件指標值,native的Surface物件中包含mGraphicBufferProducer物件,很重要,會被傳遞給APP端。

static jlong nativeCreateFromSurfaceControl(JNIEnv* env, jclass clazz,
        jlong surfaceControlNativeObj) {

    sp<SurfaceControl> ctrl(reinterpret_cast<SurfaceControl *>(surfaceControlNativeObj));
    sp<Surface> surface(ctrl->getSurface());
    if (surface != NULL) {
        surface->incStrong(&sRefBaseOwner);
    }
    return reinterpret_cast<jlong>(surface.get());
}

sp<Surface> SurfaceControl::getSurface() const
{
    Mutex::Autolock _l(mLock);
    if (mSurfaceData == 0) {
        mSurfaceData = new Surface(mGraphicBufferProducer, false);
    }
    return mSurfaceData;
}複製程式碼

到這裡WMS端Surface建立及填充完畢,並且Surface其實與WMS的SurfaceControl一一對應,當APP端需要在圖層級別進行操控的時候,其實還是要依靠SurfaceControl的,WMS的Surface建立完畢後,需要傳遞給APP端,之後APP端就獲得直接同SurfaceFlinger通訊的能力,比如繪圖與UI更新,怎傳遞的呢?我們知道Surface實現了Parcel介面,因此可以傳遞序列化的資料,其實看一下Surface nativeReadFromParcel就知道到底是怎麼傳遞的了,利用readStrongBinder獲取IGraphicBufferProducer物件的控制程式碼,之後轉化為IGraphicBufferProducer代理其實就是BpGraphicBufferProducer,之後利用BpGraphicBufferProducer構建Surface,這樣APP端Surface就被填充完畢,可以同SurfaceFlinger通訊了:

static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject parcelObj) {
    Parcel* parcel = parcelForJavaObject(env, parcelObj);
    if (parcel == NULL) {
        doThrowNPE(env);
        return 0;
    }
     sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
    sp<IBinder> binder(parcel->readStrongBinder());
    if (self != NULL
            && (IInterface::asBinder(self->getIGraphicBufferProducer()) == binder)) {
        return jlong(self.get());
    }
    sp<Surface> sur;
    sp<IGraphicBufferProducer> gbp(interface_cast<IGraphicBufferProducer>(binder));
    if (gbp != NULL) {
        sur = new Surface(gbp, true);
        sur->incStrong(&sRefBaseOwner);
    }

    if (self != NULL) {
        self->decStrong(&sRefBaseOwner);
    }

    return jlong(sur.get());
}複製程式碼

到這裡為止,APP<->WMS <->WMS 通訊申請Surface的流程算走完了

Surface對應關係.jpg
Surface對應關係.jpg

總結

視窗的新增流程簡化如下,這裡暫且忽略視窗的分組管理。

  • APP首先去WMS登記視窗
  • WMS端登記視窗
  • APP新建Surface殼子,請求WMS填充Surface
  • WMS請求SurfaceFlinger分配視窗圖層
  • SurfaceFlinger分配Layer,將結果回傳給WMS
  • WMS將視窗資訊填充到Surface傳輸到APP
  • APP端獲得填充資訊,獲取與SurfaceFlinger通訊的能力

作者:看書的小蝸牛
原文連結: WindowManagerService視窗管理之Window新增流程

僅供參考,歡迎指正

相關文章