之前分析說過,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,不同視窗之間的關係後面會更加詳細的分析,這裡有一個概念即可。
視窗的新增
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)的抽象
那麼接著關鍵點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比較方便吧,這四個類的模型如下圖:
至於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;
}複製程式碼
剛才說完成了前半部分,主要針對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
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的流程算走完了
總結
視窗的新增流程簡化如下,這裡暫且忽略視窗的分組管理。
- APP首先去WMS登記視窗
- WMS端登記視窗
- APP新建Surface殼子,請求WMS填充Surface
- WMS請求SurfaceFlinger分配視窗圖層
- SurfaceFlinger分配Layer,將結果回傳給WMS
- WMS將視窗資訊填充到Surface傳輸到APP
- APP端獲得填充資訊,獲取與SurfaceFlinger通訊的能力
作者:看書的小蝸牛
原文連結: WindowManagerService視窗管理之Window新增流程
僅供參考,歡迎指正