所有和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來校驗身份