Window 基本概念:
什么是Window,顾名思义 Window 表示一个窗口的概念。在日常开发中我们直接接触 Window 的机会并不多,但是在某些特殊情况下我们需要使用Window 来实现。
Android中所有视图都是由 Window 展示的,无论是Activity、Dialog、Toast 它们的视图实际上都是附属在Window上的 ,因此Window实际是View 的直接管理者。
源码中Window是一个抽象类;
public abstract class Window{}
而它的具体实现是通过PhoneWindow 来实现的;
public class PhoneWindow extends Window implements MenuBuilder.Callback {}
如何创建一个Window 窗口
创建一个Window窗口是很简单的,只需要通过WindowManager 即可完成创建,WindowManager是外部访问Window 的入口,Window创建的具体实现是在WindowManagerService中, 而 WindowManager 与WindowManagerService 的交互是一个IPC过程。
以下是 通过WindowManager 添加Window的示例代码:
WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
Button button = new Button(this);
button.setText("Hello Window");
//参数说明: LayoutParams(int w, int h, int _type, int _flags, int _format);
//1:layoutParams w
//2:layoutParams h
//3:窗口类型
//4:行为选项、旗标
//5:设置Window的背景支持半透明
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,0,0, PixelFormat.TRANSPARENT);
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL|WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
layoutParams.gravity = Gravity.TOP| Gravity.LEFT;
layoutParams.x = 100;
layoutParams.y = 300;
//添加
windowManager.addView(button,layoutParams);
//更新
windowManager.updateViewLayout(button,layoutParams);
//删除
windowManager.removeView(button);
WindowManager.LayoutParams 中flags 与type 这两个参数是比较重要的,
下面对其常用的选项进行简单的介绍,如果读者想了解更所,可以参考官网api
Flags参数 表示Window的属性,通过这些属性 可以控制Window的显示特性。
FLAG_NOT_TOUCH_MODAL:表示Window 不需要获取焦点,也不需要接受各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层具有焦点的Window
FLAG_NOT_FOCUSABLE: 此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理,这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法接受到单击事件。
FLAG_SHOW_WHEN_LOCKED:开启该模式 表示可以让Window显示在锁屏界面上。
Type :
在Android中大致可以将Window 分为三层,每个Window都也有自己的z-ordered ,层次大的会覆盖在层次小的Window 上面。在这三层中:
应用Window的层级范围 0~99,
子Window的层级范围 1000~1999;
系统Window的层级范围 2000~ 2999;
很显然系统Window的层级是最大的,如果我们选用系统Window ,一般情况下可以选用TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERROR 如果采用 TYPE_SYSTEM_ERROR 我们只需要 :
layoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR
并同时 添加权限
注:
android版本大于6.0之后 针对 SYSTEM_ALERT_WINDOW 该权限需要通过代码去打开启动授权界面来玩成
if (Settings.canDrawOverlays(this)) {
....
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(intent);
}
WindowManager 内部方法解析
WindowManager中常用的只有三个方法:
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
而在源码中我们可以看到这三个方法主要是定义在 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 同样也是一个接口,只不过是继承了ViewManager 该接口;
public interface WindowManager extends ViewManager {}
WindowManager的具体实现类是;
public final class WindowManagerImpl implements WindowManager{}
由此可见当调用WindowManager中的添加,删除,修改等方法时,其实是调用了 WindowManagerImpl
中的 各个方法。
下面我们针对WindowManagerImpl中的 addView、updateViewLayout、removeView
这三个方法来进一步分析 Window 的添加,更新,删除。
1:WindowManager.addView(….)
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
通过addView 源码我们可以看出 Window的添加实际是通过 WindowManagerGlobal. addVeiw方法完成的。
以下我们只需要看 WindowManagerGlobal. addVeiw 方法即可
private final ArrayList mViews = new ArrayList();
private final ArrayList mRoots = new ArrayList();
private final ArrayList mParams =
new ArrayList();
private final ArraySet mDyingViews = new ArraySet();
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
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;
.....
ViewRootImpl root;
View panelParentView = null;
.....
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
....
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;
}
}
该方法比较长,我们只需要关注我们想要关注的内容即可,
注:通过源码可以看到,内部会将View、roots、wparams 存储到不同的 ArrayList 内, 可以发现mView 存储的是所有Window所对应的View,mRoots 存储的是所有Window 所对应的ViewRootImpl,mParams存储的是所有Window所对应的布局参数,而mDyingViews 则存储了那些正在被删除的view 对象,或者说是那些已经调用removeView方法 但是删除操作还没有完成的Window对象。
在addView 中我们可以看出 最终 是调用了 ViewRootImpl.setView(….)方法来实现Window的添加
final IWindowSession mWindowSession;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
.....
// 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();
.....
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
}
改方法中我们只需要关注 reqestLayout() 该方法即可,该方法主要是来完成异步刷新请求。
接下来我们看一下 requestLayout()方法内部是如何实现的。
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
内部调用了scheduleTraversals 方法,根据View的绘制原理,我们知道scheduleTraversals就是View的绘制入口。
当View绘制完成之后,接着通过WindowSession 来完成Window的添加 ,在setView中 我们可以看到内部调用了 mWindowSession.addToDisplay(…..)
而mWIndowSession的类型是IWindowSession ,真正的实现类是Session 而通过Session的源码
final class Session extends IWindowSession.Stub
implements IBinder.DeathRecipient
我们可以看到Window的添加过程其实就是一个IPC过程。
而当mWindowSession.addToDisplay(….)方法我们可以看出
final WindowManagerService mService;
@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);
}
在session内部我们看到内部调用了mService.addWindow,而mService对象 是 WindowManagerService 如此一来,Window的添加请求,就交给了WindowManagerService 去处理了,到现在我们对Window的添加流程大致了解清除了,而对于实际通过WindowManagerService 添加一个WIndow的具体实现,读者可以 通过查看源码进行进一步的分析,
内部源码 有兴趣的读者 可以自行查看。
2:WindowManager.removeView(….)
接下来我们看以下Window的删除,在WindowManagerImpl中我们可以看到
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
内部删除时调用了 WindowManagerGlobal.removeView(….) 方法,
而在WindowManagerGlobal.removeView(….) 方法内部,我们可以看到
private final ArrayList mRoots = new ArrayList();
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);
}
}
内部先找到带删除的索引 index 然后通过该索引得到当前要删除的View,之后调用 removeViewLocked(…)来进一步删除,
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 来实现删除操作的。在代码内部我们可以看到具体的删除操作时通过 root.die(immediate) 来完成的。
该 root.die(immediate)的源码 我们可以看出
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;
}
*****************************
case MSG_DIE:
doDie();
break;
die内部有两种形式的删除,一种是同步删除 则直接调用了doDie方法,另外一种是异步删除 首先发送了一条Message 接着Handler在接受到该Message之后,再调用doDie方法。因此无论是同步还说异步都会调用到doDie方法,我们只需看该方法内是如何实现Window是如何删除的。
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
mRemoved = true;
if (mAdded) {
dispatchDetachedFromWindow();
}
.....
}
WindowManagerGlobal.getInstance().doRemoveView(this);
}
可以看出该方法内部会调用dispatchDetachedFromWindow方法,而真正删除Window的逻辑是在dispatchDetachedFromWindow方法内实现的。
final IWindowSession mWindowSession
void dispatchDetachedFromWindow() {
.....
try {
mWindowSession.remove(mWindow);
} catch (RemoteException e) {
}
.....
}
而在改方法内部我们可以看出 内部实际上是调用了mWindowSession.remove(….)方法,根Window的添加一样,remove方法最终会交给 WindowManagerService 中去实现。
以下是WindowManagerService 中removeWindowToken的实现细节;
public void removeWindow(Session session, IWindow client) {
synchronized(mWindowMap) {
WindowState win = windowForClientLocked(session, client, false);
if (win == null) {
return;
}
removeWindowLocked(win);
}
}
内部的详细代码 感兴趣的童鞋可以仔细研读。
之后紧接着调用 WindowManagerGlobal.getInstance().doRemoveView(this) 该方法 刷新数据。
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 (HardwareRenderer.sTrimForeground && HardwareRenderer.isAvailable()) {
doTrimForeground();
}
}
该方法内部我们可以看出(包括mRoots、mParams、以及mDYingView) 从当前Window所关联的这三类对象从列表中删除。
至此 Window的删除流程我们大致已经了解了。
3:WindowManager.updateViewLayout(….)
接下来我们看WIndow的更新过程,我们还是从WindowManagerImpl方法开始
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
内部调用到 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);
}
}
而在 WindowManagerGlobal.updateViewLayout 内部我们可以看出哪部更新实现是 首先更新View的LayoutParams并替换掉老掉LayoutParams 接着再更新ViewRootImpl中LayoutParams。
在ViewRootImpl 中会通过 scheduleTraversals方法来对View 重新布局,同时还会通过WindowSession来更新Window视图。
void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
.......
if (newView) {
mSoftInputMode = attrs.softInputMode;
requestLayout();
}
.....
scheduleTraversals();
}
}
这个过程最终会调用到WindowManagerService.relayoutWindow 来更新窗口,这同样是一个IPC过程;
总结: 针对Window的添加、删除、更新,我们可以看出其实都是一个IPC过程,最终无论是添加、删除、更新 都是在WindowManagerService中具体实现的。