自由筆記-AndroidView模塊之WindowManager相關分析

所有和Activity相關的Window初始化都在Activity的attach方法裏面,該方法會在Activity被創建的時候執行

 

Window:窗體抽象類,主要實現對象是PhoneWindow,主要成員:

// 創建窗口默認會創建對應的窗體佈局參數

private final WindowManager.LayoutParams mWindowAttributes =

new WindowManager.LayoutParams();

 

PhoneWindow:Window的主要實現類

 

ViewManager:View管理者接口,主要提供添加view,更新view,刪除view的接口

public void addView(View view, ViewGroup.LayoutParams params);

public void updateViewLayout(View view, ViewGroup.LayoutParams params);

public void removeView(View view);

 

windowManager 接口,繼承了ViewManager.

 

WindowManager.LayoutParams:Window窗體添加布局參數,父類是ViewGroup.LayoutParams

public LayoutParams() {

super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

type = TYPE_APPLICATION;//添加窗體的類型

format = PixelFormat.OPAQUE;

}

主要參數:type:窗體類型,主要有三種。應用窗體、子窗體、系統窗體;

Window 層級

應用 Window 1~99

子 Window 1000~1999

系統 Window 2000~2999

flags:這個主要是設置窗體的模式,我們看下一些常見的,具體參考

https://blog.csdn.net/zyjzyj2/article/details/53819964

 

public IBinder token = null;//Identifier for this window. 該窗口的身份驗證口令,對應非系統窗口如果token不對,WMS會拒絕添加window,這也就是爲什麼dialog必須依附在Activity上

 

 

windowManagerImpl WindowManager的實現者,主要方法:

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {

return new WindowManagerImpl(mContext, parentWindow);//創建一個本地WindowManager,就是它自己

}

 

private WindowManagerImpl(Context context, Window parentWindow) {

mContext = context;

mParentWindow = parentWindow;//這個parentWindow非常重要,後面講token的時候

}

 

所以在activity裏面通過getWindowManager獲取到的對象就是它。

 

WindowManager的創建:

我們都知道在調用activity的attach函數的時候,會創建phonewindow順便會設置WindowManager

if (wm == null) {

wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);

}

mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);

如果wm爲null,那麼通過getSystemService獲取到一個WindowManagerImpl,如果不爲空,那麼調用它的createLocalWindowManager方法創建一個

 

我們先看不爲null的情況

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {

return new WindowManagerImpl(mContext, parentWindow);

}

該方法就是想剛剛創建的phoneWindow傳入進來,新建一個WindowManagerImpl。

 

如果wm爲null,那麼會調用mContext的getSystemService方法獲取一個。這裏需要注意下這個mContext的類型

對應PhoneWindow來說,這個mContext是一個acticity,我們看下acivity裏面的getSystemService方法

@Override

public Object getSystemService(@ServiceName @NonNull String name) {

if (getBaseContext() == null) {

throw new IllegalStateException(

"System services not available to Activities before onCreate()");

}

 

if (WINDOW_SERVICE.equals(name)) {

return mWindowManager;

} else if (SEARCH_SERVICE.equals(name)) {

ensureSearchManager();

return mSearchManager;

}

return super.getSystemService(name);

}

如果是一個windowservice,那麼會直接返回創建好的windowManager,那麼對於首次初始化,這裏肯定是null。那麼是怎麼保證獲取到一個不爲null呢

我們看最開始的地方

mWindow.setWindowManager(

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),

mToken, mComponent.flattenToString(),

(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

這裏也是通過context獲取,但是這裏需要注意的是,這裏的context是一個ContextImpl,我們看他的getSystemService方法

@Override

public Object getSystemService(String name) {

return SystemServiceRegistry.getSystemService(this, name);

}

這裏通過一個叫做系統服務註冊的類獲取,這個類的作用就是註冊所有的系統服務,並將這些服務保存在一個map裏面,以便後面的獲取

 

從上面的activity的getSystemService方法可以知道,獲取其他的service會進入到super.getSystemService(name);這裏面如果是獲取

LayoutInflater,那麼會創建返回,如果是獲取其他的,那麼就會進入到ContextImpl的getSystemService的方法裏面

 

 

 

windowManagerGlobal WindowManager和WindowManagerService的中間橋樑,主要成員有:

private static WindowManagerGlobal sDefaultWindowManager;//它自己

private static IWindowManager sWindowManagerService;//WindowManagerService的代理實現類

private static IWindowSession sWindowSession;//Session的代理實現類,和WindowManagerService的溝通橋樑

private final ArrayList<View> mViews = new ArrayList<View>();Window 所對應的 View

private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();Window 所對應的 ViewRootImpl

private final ArrayList<WindowManager.LayoutParams> mParams =

new ArrayList<WindowManager.LayoutParams>();Window 所對應的佈局參數

private final ArraySet<View> mDyingViews = new ArraySet<View>();正在被刪除的 View 對象

 

viewRootImpl,每個添加的Window所對應的根View,主要成員類

final IWindowSession mWindowSession;//Session的代理實現類,和WindowManagerService的溝通橋樑,採用Binder機制

mThread = Thread.currentThread();//當前線程對象,在添加View的時候會檢查,如果創建ViewRoot的線程和後續添加View的線程不是同一個,

那麼就會報錯,這就是爲什麼非UI線程一般不給刷新UI的原因

mWindow = new W(this);//這個東西是一個Binder,是給WindowManager回調ViewRoot裏面的方法的。代表着創建的Window對象

static class W extends IWindow.Stub {

private final WeakReference<ViewRootImpl> mViewAncestor;

private final IWindowSession mWindowSession;

...

}

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);//綁定信息集合類,所有和Window相關的信息都在這裏面,後續會分發到該Window內的各個View

final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();需要添加窗體的參數

 

 

所以最終Window添加流程:WindowManager->WindowManagerImp->WindowManagerGlobal->ViewRootImpl->IWindowSession->Session->WMS

 

WindowSurfacePlacer WMS那邊的繪製者

 

window的添加過程:

源頭最開始的地方:ActivityThread的handleResumeActivity方法,當Activity創建成功,DecorView將我們的佈局文件加載完成,我們開始將根View添加到Window中

wm.addView(decor, l);//通過WindowManager添加根View

mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);//通過windowManagerGlobal添加

這裏這個mParentWindow注意下,它就是Activity創建時候的PhoneWindow,表示添加的View要依附在它上面

在global的addView裏面有這麼一段很重要的代碼

if (parentWindow != null) {

parentWindow.adjustLayoutParamsForSubWindow(wparams);

}

如果不是添加系統window,那麼parent一定是不會爲null的,我們進入adjustLayoutParamsForSubWindow這個方法裏面看看

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {

CharSequence curTitle = wp.getTitle();

if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&

wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

if (wp.token == null) {

View decor = peekDecorView();

if (decor != null) {

wp.token = decor.getWindowToken();

}

}

...

} else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&

wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {

// We don't set the app token to this system window because the life cycles should be

// independent. If an app creates a system window and then the app goes to the stopped

// state, the system window should not be affected (can still show and receive input

// events).

if (curTitle == null || curTitle.length() == 0) {

final StringBuilder title = new StringBuilder(32);

title.append("Sys").append(wp.type);

if (mAppName != null) {

title.append(":").append(mAppName);

}

wp.setTitle(title);

}

} else {

if (wp.token == null) {

wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;

}

if ((curTitle == null || curTitle.length() == 0)

&& mAppName != null) {

wp.setTitle(mAppName);

}

}

if (wp.packageName == null) {

wp.packageName = mContext.getPackageName();

}

if (mHardwareAccelerated ||

(mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {

wp.flags |= FLAG_HARDWARE_ACCELERATED;

}

}

這個方法主要做了三點,如果不是系統窗口,賦值token,這個token是後續和WMS交互的身份驗證口令,如果是則不需要token。

打開硬件加速,賦值包名

 

接下來判斷該View是否添加過,如果添加過,那麼移除後在添加。如果是添加子View,找到對應的父View

int index = findViewLocked(view, false);

if (index >= 0) {

if (mDyingViews.contains(view)) {

// Don't wait for MSG_DIE to make it's way through root's queue.

mRoots.get(index).doDie();

} else {

throw new IllegalStateException("View " + view

+ " has already been added to the window manager.");

}

// The previous removeView() had not completed executing. Now it has.

}

 

// If this is a panel window, then find the window it is being

// attached to for future reference.

if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&

wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

final int count = mViews.size();

for (int i = 0; i < count; i++) {

if (mRoots.get(i).mWindow.asBinder() == wparams.token) {

panelParentView = mViews.get(i);

}

}

}

 

一切準備就緒之後,創建對應的對象,然後通過ViewRoot添加View

root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wparams);

mViews.add(view);

mRoots.add(root);

mParams.add(wparams);

 

try {

root.setView(view, wparams, panelParentView);

}

 

進入到ViewRootImpl的setView方法

首先調用mWindowAttributes.copyFrom(attrs); 複製窗體佈局參數

requestLayout();調用該方法,準備異步繪製View到Window上

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(),

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mOutsets, mInputChannel);

通過session將window添加到WMS,爲異步添加準備,如果添加失敗,那麼取消繪製任務

if (res < WindowManagerGlobal.ADD_OKAY) {

unscheduleTraversals();

...

}

void unscheduleTraversals() {

if (mTraversalScheduled) {

mTraversalScheduled = false;

mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

mChoreographer.removeCallbacks(

Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

}

}

 

 

WindowManagerService部分:

關鍵對象:

/**

* All currently active sessions with clients.所有客戶端的session對象集合

*/

final ArraySet<Session> mSessions = new ArraySet<>();//Session:它是客戶端連接WMS的橋樑,在WindowManagerGlobal裏面創建

final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();//所有窗口的WindowState對象。

final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();//所有窗口的WindowToken對象

 

WindowToken和AppWindowToken:

WindowToken的成員變量token是IBinder對象,具有系統唯一性。因此,向WMS的mWindowMap或者mTokenMap插入對象時都是使用token值作爲索引。

APPWindowToken從WindowToken類派生,是一種比較特殊的WindowToken,代表應用窗口,主要是Activity中創建的頂層窗口.

一個WindowToken對象的成員變量APPWindowToken如果爲NULL,那麼它就不是APPWindowToken,否則就是APPWindowToken對象。

APPWindowToken中的appToken用來表示應用token,它在AMS中創建,表示一個應用。它也是一個binder

 

這兩個token中都有一個WindowList,代表保存了所有相同APPWindowToken的應用窗口及其子窗口

 

客戶端調用了addToDisplay方法之後,最終會調用WSM的addWindow方法:

1、參數檢查,進入到這個方法之後首先進行參數檢查,分爲系統窗口、應用窗口和子窗口的檢查;

如果已經添加過窗口,那麼不在添加

if (mWindowMap.containsKey(client.asBinder())) {

Slog.w(TAG_WM, "Window " + client + " is already added");

return WindowManagerGlobal.ADD_DUPLICATE_ADD;

}

如果是子窗口,那麼必須存在父窗口切父窗口不能是子窗口類型

if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {

attachedWindow = windowForClientLocked(null, attrs.token, false);

if (attachedWindow == null) {

Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "

+ attrs.token + ". Aborting.");

return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;

}

if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW

&& attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {

Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "

+ attrs.token + ". Aborting.");

return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;

}

}

WindowToken token = mTokenMap.get(attrs.token);//根據appToken獲取windowToken

如果是系統窗口,那麼token爲null,窗口類型是應用窗口,輸入法窗口,壁紙,Dream則退出,別的窗口則創建token,注意此時創建的token是沒有appWindowToken的

如果是應用窗口,那麼獲取到token,檢查其他匹配規則。如果不是應用窗口而appWindowToken又不爲null,那麼重新創建WindowToken。

 

2、創建窗口對象:

WindowState win = new WindowState(this, session, client, token,

attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);

if (addToken) {

mTokenMap.put(attrs.token, token);

}如果是新的token,加入到map中

win.attach();將窗口添加記錄。將session添加進入到sessions集合中,session中窗體數增加一

mWindowMap.put(client.asBinder(), win);將創建好的窗口對象也存放入map中,以viewRoot的IWindow爲key

Session對象中重要變量:

mSurfaceSession表示一個SurfaceSession對象,這個SurfaceSession對象是WindowManagerService服務用來與SurfaceSession服務建立連接的

SurfaceSession創建後會建立一個SurfaceComposerClient對象,可以用來和SurfaceFlinger服務通信,請求SurfaceComposerClient創建繪製表面(Surface)

mNumWindow:用來描述當前正在處理的Session對象內部包含有多少個窗口

 

 

3、將窗口添加在display上

if (type == TYPE_INPUT_METHOD) {

win.mGivenInsetsPending = true;

mInputMethodWindow = win;

addInputMethodWindowToListLocked(win);

imMayMove = false;

} else if (type == TYPE_INPUT_METHOD_DIALOG) {

mInputMethodDialogs.add(win);

addWindowToListInOrderLocked(win, true);

moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));

imMayMove = false;

} else {

addWindowToListInOrderLocked(win, true);

if (type == TYPE_WALLPAPER) {

mWallpaperControllerLocked.clearLastWallpaperTimeoutTime();

displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;

} else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {

displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;

} else if (mWallpaperControllerLocked.isBelowWallpaperTarget(win)) {

// If there is currently a wallpaper being shown, and

// the base layer of the new window is below the current

// layer of the target window, then adjust the wallpaper.

// This is to avoid a new window being placed between the

// wallpaper and its target.

displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;

}

}

 

以應用窗口爲例,具體可以查看addWindowToListInOrderLocked->addAppWindowToListLocked

 

4、接下來的代碼就是調整窗口順序,然後將添加窗口是否成功的狀態碼res返回

 

 

Window添加過程的身份檢驗:

關鍵對象: static class Token extends IApplicationToken.Stub {}

Token(ActivityRecord activity, ActivityManagerService service) {

weakActivity = new WeakReference<>(activity);

mService = service;

}

在ActivityStarter.startActivityLocked方法裏面,會新建一個ActivityRecord

ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,

intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,

requestCode, componentSpecified, voiceSession != null, mSupervisor, container,

options, sourceRecord);

在構造函數裏面,會創建這個Token

appToken = new Token(this, service);

接着會執行startActivityUnchecked->setInitialState

在setInitialState方法裏面會將新建的ActivityRecord賦值給mStartActivity變量

然後會調用mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);方法進入到ActivityStack裏面

在這裏面會執行一個addConfigOverride(r, task);方法,會將token傳遞給WMS;

mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken,

r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen,

(r.info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, r.userId, r.info.configChanges,

task.voiceSession != null, r.mLaunchTaskBehind, bounds, task.mOverrideConfig,

task.mResizeMode, r.isAlwaysFocusable(), task.isHomeTask(),

r.appInfo.targetSdkVersion, r.mRotationAnimationHint);

r.taskConfigOverride = task.mOverrideConfig;

 

在WMS的addAppToken方法裏面,會新建一個AppWindowToken,代表一個新的應用要建立窗體,然後將其存放入

HashMap<IBinder, WindowToken> mTokenMap 中。在之前說到的添加窗體的時候,會根據傳入過來的token.asBinder,去獲取對應的AppWindowToken來校驗身份

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章