1.Window是什么
Window在Android开发中是一个窗口的概念,它是一个抽象类,具体的实现类是PhoneWindow,在PhoneWindow中有一个顶级View—DecorView,继承自FrameLayout,我们可以通过getDecorView()获得它,当我们调用Activity的setContentView时,其实最终会调用Window的setContentView,当我们调用Activity的findViewById时,其实最终调用的是Window的findViewById,这也间接的说明了Window是View的直接管理者。但是Window并不是真实存在的,它更多的表示一种抽象的功能集合,View才是Android中的视图呈现形式,绘制到屏幕上的是View不是Window,但是View不能单独存在,它必需依附在Window这个抽象的概念上面,Android中需要依赖Window提供视图的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系统状态栏),输入法窗口等,因此Activity,Dialog等视图都对应着一个Window。
2.WindowManager
WindowManager 本身是一个 interface ,先看一下它的父类
代码语言:javascript复制public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
很明显,有这三大操作.但是 WindowManager 本身不实现这些功能,它们在 WindowManagerImpl 实现 在看 WindowManagerImpl 之前,先看一下 WindowManager 的内部类 LayoutParams
第一个 SoftInputModeFlags
代码语言:javascript复制 SOFT_INPUT_STATE_UNSPECIFIED,
SOFT_INPUT_STATE_UNCHANGED,
SOFT_INPUT_STATE_HIDDEN,
SOFT_INPUT_STATE_ALWAYS_HIDDEN,
SOFT_INPUT_STATE_VISIBLE,
SOFT_INPUT_STATE_ALWAYS_VISIBLE,
SOFT_INPUT_ADJUST_UNSPECIFIED,
SOFT_INPUT_ADJUST_RESIZE,
SOFT_INPUT_ADJUST_PAN,
SOFT_INPUT_ADJUST_NOTHING,
SOFT_INPUT_IS_FORWARD_NAVIGATION,
是不是有几个很眼熟,就是控制当然页面软键盘的行为的
第二个 type
这个分类的注视有很多,不过大致把type分为3类
- 应用程序窗口:type值范围是1~99,Activity就是一个典型的应用程序窗口,type值是TYPE_BASE_APPLICATION,WindowManager的LayoutParams默认type值是TYPE_APPLICATION。
- 子窗口:type值范围是1000~1999,PupupWindow就是一个典型的子窗口,type值是TYPE_APPLICATION_PANEL,子窗口不能独立存在,必须依附于父窗口
- 系统窗口:type值范围是2000~2999,系统窗口的类型很多,上面并没有全部列举出来,系统状态栏就是一个典型的系统窗口,type值是TYPE_STATUS_BAR,与应用程序窗口不同的是,系统窗口的创建是需要声明权限的。
第三个 flags
这个分类也很多,不过常用的就是 FLAG_KEEP_SCREEN_ON
剩余还有好几个,不过基本不怎么用,就不解释了 好了,接下来看 WindowManagerImpl 的三个方法的具体实现 (WindowManagerGlobal)
3.WindowManagerGlobal
先介绍几个重要的数据结构
代码语言:javascript复制 private static IWindowManager sWindowManagerService;---后面讲
private static IWindowSession sWindowSession;---后面讲
private final ArrayList<View> mViews = new ArrayList<View>();----所有的view
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();---实现了View与WindowManager之间所需要的协议,作为WindowManagerGlobal中大部分的内部实现
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();----所有的view对应的布局参数
private final ArraySet<View> mDyingViews = new ArraySet<View>();---需要删除view就放到队列里,等到真正需要删除的时候再删除
(1)addView
代码语言:javascript复制 int index = findViewLocked(view, false);
if (index >= 0) {
// 已经存在,并且在 mDyingViews 里面,就让它真正消失
// 已经存在,但是不在 mDyingViews 里面,提示重复添加
if (mDyingViews.contains(view)) {
//里面会把它从 mDyingViews 中移除
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " view
" has already been added to the window manager.");
}
}
......
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// 在 ViewRootImpl 里面
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
ViewRootImpl.addView
代码语言:javascript复制 try {
.....
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
.....
} finally {
.....
}
if (res < WindowManagerGlobal.ADD_OKAY) {
......
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " attrs.token
" is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " attrs.token
" is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " attrs.token
" is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " mWindow
" has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException("Unable to add window "
mWindow " -- another window of type "
mWindowAttributes.type " already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException("Unable to add window "
mWindow " -- permission denied for window type "
mWindowAttributes.type);
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException("Unable to add window "
mWindow " -- the specified display can not be found");
case WindowManagerGlobal.ADD_INVALID_TYPE:
throw new WindowManager.InvalidDisplayException("Unable to add window "
mWindow " -- the specified window type "
mWindowAttributes.type " is not valid");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " res);
}
最重要的就是这个. mWindowSession.addToDisplay 如果失败会返回一大堆Exception.如果 WindowManager.addView 失败,大多都会在这里看到exception mWindowSession 在创建的时候就会被初始化.首先看一下 WindowManagerGlobal.getWindowSession()
代码语言:javascript复制 public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager imm = InputMethodManager.getInstance();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
},
imm.getClient(), imm.getInputContext());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
其实 sWindowManagerService 和 sWindowSession 都是AIDL.他们说的实现分别在 WindowManagerService 和 Session 那么 mWindowSession.addToDisplay 就是 Session.addToDisplay.最终还是转到 WindowManagerService.addWindow (其实看代码就知道 Session 类似于 WindowManagerService 的代理)
代码语言:javascript复制 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) {
// 先检查 DisplayContent 有没有问题
final DisplayContent displayContent = mRoot.getDisplayContentOrCreate(displayId);
.....
// 检查 window 类型
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
parentWindow = windowForClientLocked(null, attrs.token, false);
.....
}
// 检查 token
.....
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
.....
if (token == null) {
.....
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
token = new WindowToken(this, binder, type, false, displayContent,
session.mCanAddInternalSystemWindow);
} else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
atoken = token.asAppWindowToken();
.....
} else if (rootType == TYPE_INPUT_METHOD) {
if (token.windowType != TYPE_INPUT_METHOD) {
Slog.w(TAG_WM, "Attempted to add input method window with bad token "
attrs.token ". Aborting.");
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
}
// 这里省略一大堆的 token 的判断
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
if (win.mDeathRecipient == null) {
// Client has apparently died, so there is no reason to
// continue.
Slog.w(TAG_WM, "Adding window client " client.asBinder()
" that is dead, aborting.");
return WindowManagerGlobal.ADD_APP_EXITING;
}
if (win.getDisplayContent() == null) {
Slog.w(TAG_WM, "Adding window to Display that has been removed.");
return WindowManagerGlobal.ADD_INVALID_DISPLAY;
}
mPolicy.adjustWindowParamsLw(win.mAttrs);
win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
res = mPolicy.prepareAddWindowLw(win, attrs);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
final boolean openInputChannels = (outInputChannel != null
&& (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
if (openInputChannels) {
win.openInputChannel(outInputChannel);
}
// From now on, no exceptions or errors allowed! 从这里开始没有任何问题 ADD_OKAY
res = WindowManagerGlobal.ADD_OKAY;
......
}
return res;
}
(2)updateViewLayout
这两个相对比较简单,就是mParams对应的值换一下
(3)removeView
代码语言:javascript复制 private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
很明显,remove的时候并没有马上删除.那什么时候真正删除view呢?
代码语言:javascript复制 void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
}
if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
doTrimForeground();
}
}
在 doRemoveView 里面,什么时候调用呢?在 ViewRootImpl.doDie 里面.一共三种情况
- 发送 MSG_DIE 消息
- 就是上面的 removeViewLocked 里面.稍微看一下 root.die 逻辑就知道.如果 immediate=true 就立马删除,此时 deferred=false.如果 immediate=false,会发送 MSG_DIE 消息,然后添加到 mDyingViews
- 就是最开始分析 addView 之前检测 mDyingViews 是否包含该view,包含了就删除