对于Android的Window而言,实际上是一个相对抽象的概念。从Window的定义可以看出,Window是对窗体的一种抽象,是顶级Window的外观与行为策略。目前仅有的实现类是PhoneWindow,可以使用它来创建Window(对系统而言)。
public abstract class Window {
// Window 样式相关静态常量等
…
public interface Callback { // Window 回调接口
…
}
// Window相关抽象方法
…
}
View的事件分发机制中的事件传递:单击事件由Activity内部的Window -> Decor View -> View
WindowManager
从字面意思上理解,它是Window的管理者,更切确的说,它是Window中View的管理者。因为事实上,Window所需要展示的内容是由View来承载的(也就是DecorView),并且创建一个Window需要通过WindowManager来协助完成。它的具体实现是WindowManagerImpl,通常可以使用getSystemService(Context.WINDOW_SERVICE)来得到。
Android中基本上所有的View都是Window来呈现的,不管是Activity、Toast还是Dialog,它们的视图都是附加到Window上的,因此可以将Window理解为View的承载者与直接管理者。
WindowManager是外界访问Window的入口,通常使用定义于ViewManager的相关方法来操作的:
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
在SystemServiceRegistry类中有如下代码,可知是静态创建的WindowManagerImpl:
static {
….
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher() {
@Override
public WindowManager createService(ContextImpl ctx) {
return new WindowManagerImpl(ctx);
}});
…
}
获取WindowManager实际上获取的是WindowManagerImpl实例:
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManagerService
它是系统全局Window的管理者,负责协调Window的层级、显示及事件派发等。可以这样理解,WindowManager是本地端的管理者,负责与远程服务的WindowManagerService进行交互,从而使Window能层次分明的显示出来。WindowManager与WindowManagerService的交互是一个IPC过程。
WindowManagerService继承体系的继承体系如下,可以发现它实际上是一个Binder子类:
在SystemServer启动的时候,会注册WindowManagerService到ServiceManager中:
private void startOtherServices() {
...
wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,!mFirstBoot, mOnlyCore);
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
…}
通过 ServiceManager.getService(“window”)); 可以获得WindowManagerService,这通常在系统内部使用 。
WindowManagerGlobal
我们知道WindowManagerImpl是WindowManager的实现类,但实际上它的工作基本委托给了WindowManagerGlobal类来完成。 WindowManagerGlobal实现了WindowManagerImpl的功能,并对View、ViewRootImpl以及LayoutParams进行管理:
// view集合
private final ArrayList mViews = new ArrayList(); // ViewRootImpl集合
private final ArrayList mRoots = new ArrayList();// 参数集合
private final ArrayList mParams = new ArrayList();
// 即将移除的view集合
private final ArraySet mDyingViews = new ArraySet();
ViewRootImpl
ViewRootImpl不是View,实际上是顶级View的管理者。每一个ViewRootImpl 都对应着一个ViewTree ,通过它来完成View的绘制及显示过程。下图展示了它与WM、WMS之间的关系:
使用WindowManager添加一个view到Window
自定义浮窗 需要权限android.permission.SYSTEM_ALERT_WINDOW
/**
* 显示浮窗
* @param content 要填充的文本内容
* @param layoutId 用于创建窗体View的布局
*/
public void show(String content,int layoutId) {
wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
params = new WindowManager.LayoutParams();
screenHeight = AppInfoUtils.getScreenSize(context).height;
screenWidth = AppInfoUtils.getScreenSize(context).width;
// 加载布局
view = View.inflate(mContext, layoutId, null);
// 设置浮窗params属性
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.type = WindowManager.LayoutParams.TYPE_PHONE;
params.gravity = Gravity.TOP+Gravity.LEFT; // 将重心设置为左上方
params.format = PixelFormat.TRANSLUCENT; // 半透明
params.x = sp.getInt("startX", 0); // 设置显示位置
params.y = sp.getInt("startY", 0);
TextView tvLocation = (TextView) view.findViewById(R.id.tv_toast_location);
tvLocation.setText(content);
// 将View添加到窗体管理器
wm.addView(view, params);
}
1、LayoutParams.Flags参数表示Window的属性,通过设置它的选项可以控制Window的显示特性。如下几种常见选项:
FLAG_NOT_FOCUSABLE
不许获得焦点
FLAG_NOT_TOUCHABLE
不接受触摸屏事件
FLAG_NOT_TOUCH_MODAL
当窗口可以获得焦点(没有设置 FLAG_NOT_FOCUSALBE 选项)时,仍然将窗口范围之外的点设备事件(鼠标、触摸屏)发送给后面的窗口处理。否则它将独占所有的点设备事件,而不管它们是不是发生在窗口范围内。
FLAG_SHOW_WHEN_LOCKED
当屏幕锁定时,窗口可以被看到。这使得应用程序窗口优先于锁屏界面。可配合FLAG_KEEP_SCREEN_ON选项点亮屏幕并直接显示在锁屏界面之前。可使用FLAG_DISMISS_KEYGUARD选项直接解除非加锁的锁屏状态。此选项只用于最顶层的全屏幕窗口。
FLAG_DIM_BEHIND。
窗口之后的内容变暗
FLAG_BLUR_BEHIND
窗口之后的内容变模糊。
2、Type参数表示Window的类型,有3种主要类型:
1)Application_windows (应用Window):
值在 FIRST_APPLICATION_WINDOW 和 LAST_APPLICATION_WINDOW 之间。
是通常的、顶层的应用程序窗口。必须将 token 设置成 activity 的 token 。
2)Sub_windows (子Window):
取值在 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之间。与顶层窗口相关联,token 必须设置为它所附着的宿主窗口的 token。
3)System_windows (系统Window):
取值在 FIRST_SYSTEM_WINDOW 和 LAST_SYSTEM_WINDOW 之间。
3、Window的层次
每个Window都有对应的z-ordered,层次大的会覆盖到层次小的Window上面。在三类Window中应用Window的层级范围在1~99之间,子Window的范围在1000~1999之间,系统Window的层级范围在2000~2999之间。
要使Window位于所有Window的最顶层,采用较大的层级即可,系统Window的层级是最大的,一般选用TYPE_SYSTEM_OVERLAY或TYPE_SYSTEM_ERROR,同时要声明权限android.permission.SYSTEM_ALERT_WINDOW。如下示例
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
4、WindowManager提供的常用方法
WindowManager继承自ViewManager,提供了添加view、删除view和更新view,这三个方法都是定义在ViewManager。
public interface ViewManager
{
/**
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
Window是一个抽象的概念,每一个Window都对应着一个view和ViewRootImpl,Window与View通过ViewRootImpl建立起联系,Window是以View作为实体存在,实际使用WindowManager访问来Window,外部无法直接访问Window。WindowManager提供了三个针对View的接口方法addView、updateViewLayout和removeView,分析Window的内部机制从Window的添加、更新和删除开始。
Window的添加依赖于WindowManager,而WindowManager是一个接口,它的具体实现类是WindowManagerImpl,在WindowManagerImpl中实现了如下几个操作view的方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
由上可知,WindowManagerImpl将操作view的实现都委托给了WindowManagerGlobal(即mGlobal),下面来看一下WindowManagerGlobal的addView方法,完整代码如下
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 1、---
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
// 2、---
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
// 3、---
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.
}
// 4、---
// 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);
}
}
}
// 5、---
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// 6、---
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
上面addView方法大概做了如下几件事:
1、检查参数是否合法,并判断当前添加的是否为子Window(parentWindow是否为空),若为子Window则为其做相关调整,否则为其开启硬件加速
2、监视系统属性的变化
3、通过findViewLocked获取mViews中view的索引,看添加的view是否在mViews的集合里,如果获取的index>=0,此view存在,接着判断要删除的集合是否包含此view,若包含则直接执行doDie()删除当前view,若不包含则会抛出异常(此view正在被删除,还没有完成)
4、判断添加的是否为panel window,若是则找出以备后查
5、将Window的一系列参数添加到集合中,几种集合如下:
mViews:存储了所有Window所对应的View
mRoots:存储了所有Window所对应的ViewRootImpl
mParams:存储了所有Window所对应的布局参数
mDyingViews:存储的是即将被删除的View对象或正在被删除的View对象
6、通过ViewRootImpl的setView方法来完成界面的更新,并完成Window的添加。
在setView内部会通过requestLayout方法来完成异步刷新请求,scheduleTraversals是View的绘制入口函数。
@Override
public void requestLayout() {、
if (!mHandlingLayoutInLayoutRequest) {
checkThread(); // 检查线程,通常检查是否为主线程(禁止非主线程更新UI)
mLayoutRequested = true;
scheduleTraversals();
}
}
接着调用了scheduleTraversals方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
/// 向底层注册一个绘制事件,下次Vsync信号来时会执行相关事件
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ...
}
}
下次垂直刷新信号来时,会回调mTraversalRunnable来执行绘制操作,实际会调用doTraversal方法,进一步会调用performTraversals方法,它是真正绘制的入口:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing(“ViewAncestor”);
}
performTraversals();
…
}
}
performTraversal函数很长,下面列出了主要做的工作:
在setView方法之后会接着执行如下代码,WindowSession最终完成Window的添加,mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,因此Window的添加的过程是一个IPC调用
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
在Session内部会调用WindowManagerService的addWindow方法进行Window方法添加,具体的过程在WindowManagerService中实现了。WindowManagerService会为每个应用保留一个单独的Session。
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outInputChannel);
}
到此Window的添加就完成了。大致走了如下流程:
WindowManager -> WindowManagerImpl -> WindowManagerGlobal>addView -> ViewRootImpl>setView>requestLayout ->
(IPC)Session>addToDisplay -> WindowManagerService>addWindow
删除的过程与添加类似,通过WindowManagerGlobal来实现删除,下面看它的removeView方法
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
上述方法在要移除的view不为空的情况下,通过findViewLocked查找view在mViews(上述)中的索引,然后通过removeViewLocked进行删除。看一下这两个方法:
private int findViewLocked(View view, boolean required) {
final int index = mViews.indexOf(view);
if (required && index < 0) {
throw new IllegalArgumentException("View=" + view + " not attached to window manager");
}
return index;
}
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);
}
}
}
从removeViewLocked方法可以看出,删除操作是由ViewRootImpl来完成的,删除分为两种,分别为同步删除(removeViewImmediate)和异步删除(removeView),在ViewRootImpl的die(immediate)方法中进行判断。如果为同步则直接调用doDie方法进行删除,否则会发送一个消息进行异步处理,同时执行mDyingViews.add(view)
**
* @param immediate True, do now if not in traversal. False, put on queue and do later.
* @return True, request has been queued. False, request has been completed.
*/
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
在doDie方法内部调用dispatchDetachedFromWindow()方法删除Window,最后调用WindowManagerGlobal的doRemoveView方法进行数据刷新,包括mRoots,mViews,mParams和mDyingViews,需要将当前Window所关联的这三类对象从集合中删除
void doDie() {
checkThread();
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
...
WindowManagerGlobal.getInstance().doRemoveView(this);
}
dispatchDetachedFromWindow方法如下:
void dispatchDetachedFromWindow() {
if (mView != null && mView.mAttachInfo != null) {
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
mView.dispatchDetachedFromWindow(); // 回调
}
mAccessibilityInteractionConnectionManager.ensureNoConnection();
mAccessibilityManager.removeAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mAccessibilityManager.removeHighTextContrastStateChangeListener(
mHighContrastTextManager);
removeSendWindowContentChangedCallback(); // 清除一些监听与回调
destroyHardwareRenderer(); // 销毁硬件渲染相关资源
setAccessibilityFocus(null, null);
mView.assignParent(null);
mView = null;
mAttachInfo.mRootView = null;
mSurface.release();
...
try {
mWindowSession.remove(mWindow); //将mWindow从Window中删除
} catch (RemoteException e) {
}
...
unscheduleTraversals();
}
在dispatchDetachedFromWindow方法中真正执行删除操作,内部作了如下几件事:
1、调用Wiew的dispatchDetachedFromWindow方法,它的方法内部会调用onDetachedFromWindow()方法,当view从Window被移除,此方法就会被调用,可以在此方法中做一些资源回收工作,诸如终止动画、线程
2、垃圾回收的相关工作,如清理数据和消息、移除回调和监听。
3、通过Session的remove方法移除Window:mWindowSession.remove(mWindow),此过程是一个IPC过程,最终会调用WindowManagerService的removeWindow方法。
4、取消计划任务,主要是一些View绘制操作(测量、布局及绘制)
到此,Window的删除过程就已经完成了,大致流程
WindowManager -> (实现类)WindowManagerImpl ->(委托类) WindowManagerGlobal>removeView>removeViewLocked ->
ViewRootImpl>doDie>dispatchDetachedFromWindow -> (IPC) Session>remove -> WindowManagerService>removeWindow
同创建、删除Window类似,更新Window的实施者依然是WindowManagerGlobal,下面看它的updateViewLayout方法
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
更新过程首先要替换旧的params,接着通过ViewRootImpl的setLayoutParams方法进行更新ViewRootImpl中的params,在setLayoutParams方法内通scheduleTraversals进行Vew的重新布局(测量、布局、绘制),并会通过如下流程来更新Window的视图
scheduleTraversals-> doTraversal -> performTraversals-> relayoutWindow-> mWindowSession.relayout -> mService.relayoutWindow
到此Window的更新就完成了,大致流程如下:
WindowManager -> WindowManagerImpl -> WindowManagerGlobal>updateViewLayout -> ViewRootImpl>setLayoutParams>scheduleTraversals>doTraversal>performTraversals>relayoutWindow -> (IPC)Session>relayout -> WindowManagerService>relayoutWindow
后记:此篇参考了安卓开发艺术探索,融入个人总结所成,如有错误请不吝赐教。特此说明。更多细节可查阅android源码。