Window就是窗口,是一个抽象类,它的具体实现类是PhoneWindow。它的作用是
显示和管理view,不管是activity,toast,dialog他们的view都是依附在Window上的,都是通过Window呈现的。DecorView的事件也是从Window传递过去的。平时如果想要显示一个悬浮窗,就可以使用到Window。
创建一个Window的步骤如下:
1.首先要确定是要显示系统窗口还是应用窗口,如果是要显示系统窗口则要获取权限,如果用户未授权,则会报错
a.在manifest文件中声明需要使用到的权限
//弹出系统类型的Window悬浮窗需要的权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
b.在6.0系统上要进行动态授权
private fun checkWindowPermission(context:Activity): Boolean {
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
if(!Settings.canDrawOverlays(context)){
//还未授权,需要跳转到权限设置页面
ToastUtils.longToast(context,"当前无权限,请授权")
context.startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:"+context.packageName)),0)
return false
}
return true
}
return true
}
2.创建window
//6.0以上系统
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
btn = new Button(context);
btn.setText("悬浮窗");
btn.setGravity(Gravity.CENTER);
btn.setBackground(getResources().getDrawable(R.color.red));
final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
if(Build.VERSION.SDK_INT>Build.VERSION_CODES.O){
//8.0以上的系统
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
//这里如果指定window级别是应用级的,
// 那么就会报Unable to add window -- token null is not valid; is your activity running? 这个异常
// layoutParams.type =1; running? 这个异常
}else {
layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
//这里如果指定window级别是应用级的,
// 那么就会报Unable to add window -- token null is not valid; is your activity running? 这个异常
// layoutParams.type =1;running? 这个异常
}
//这里falgs 如果是仅仅设置成FLAG_NOT_TOUCH_MODAL,那么按返回键,onDestory方法不会回调
layoutParams.flags = FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL;
layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
layoutParams.height = 300;
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
layoutParams.x = 300;
layoutParams.y = 300;
windowManager.addView(btn,layoutParams);
//获取当前窗口可视区域大小
Rect rect = new Rect();
btn.getWindowVisibleDisplayFrame(rect);
LogUtil.i("rect.width()=${rect.width()} rect.height()= ${rect.height()}");
btn.post(new Runnable() {
@Override
public void run() {
WindowId windowId = btn.getWindowId();
LogUtil.e("windowId================ "+windowId);
ViewParent parent = btn.getParent();
LogUtil.e("parent================ "+parent);
}
});
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
downX = (int) event.getRawX();
downY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int moveX = (int) event.getRawX();
int moveY = (int) event.getRawY();
layoutParams.x += moveX - downX;
layoutParams.y += moveY - downY;
windowManager.updateViewLayout(btn,layoutParams);
downX = moveX;
downY = moveY;
break;
case MotionEvent.ACTION_UP:
if(event.getRawX() == downX && event.getRawY() == downY){
LogUtil.e("单击");
makeActivityFromBackgroundToForeground();
}
break;
}
return false;
}
});
}
上面的代码中,设置了Window的flags是用于控制window的显示特性的,这里我们详细的介绍下,Window常用的几种flags
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
这种标记表示window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,
最终事件会传递到下层的具有焦点的window。
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
此模式下,系统将当前window区域以外的单击事件传递个底层的window,当前window区域的事件自己处理,一般需要开启这个标记
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
这个标记表示window可以显示在锁屏的界面上.
WindowManger.LayoutParams.type是用于区分window的类型的,window的类型分为三类,
应用Window: type的取值范围是1~99 比如 dialog
子Window : type的取值范围是1000~1999 不能单独存在,需要依附在父window上,比如 popwindow
系统Window: type的取值范围是2000~2999 需要权限才能创建
type的取值越大,则现在的层次就更在上面。上面的window会覆盖下面的window。所以想要弹出的window不被应用的window遮盖,就将type的值设置到2000~2999之间。这时的window就是系统级别的,需要在manifest文件中使用权限,并且在6.0的版本中,动态获取权限。
上面的代码中,如果将WindowManger.LayoutParams.type的值设置为1,也就是将Window的类型设置为应用Window,那么就会报错,报错的原因可以查看这篇文章的分析。
介绍完基本使用后,我们来看看window的创建过程,
Window的添加过程:首先要看windowManager.addView()方法,这个方法是用于添加view的。
下面我们看看WindowManger的addView方法,点击这个方法,结果跳转到了ViewManger接口中了,
下面看看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);
}
可以看到这个接口定义了三个方法,分别是添加、更新、删除view的方法,所以,WindowManger实际操的对象都是view。WindowManager其实也是一个接口,它继承了ViewManager接口,所以WindowManager才拥有ViewManager的三个方法。但是WindowManager毕竟只是一个接口,具体的addView的操作肯定是在其实现类中完成的,WindowManager的实现类就是WindowMangerImpl,下面我们看看WindowManagerImpl类的addView方法
/frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
// ... 省略部分代码
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// ... 省略部分代码
}
从这个方法中可以看到,它的addView的操作是交给了WindowManagerGlobal来完成的,这里使用了桥接设计模式。
下面看看WindowManagerGlobal类的addView方法:
/frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {
private static WindowManagerGlobal sDefaultWindowManager;
private static IWindowManager sWindowManagerService;
private static IWindowSession sWindowSession;
private final Object mLock = new Object();
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
// ... 省略部分代码
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;
synchronized (mLock) {
// ... 省略部分代码
int index = findViewLocked(view, false); // 1
if (index >= 0) {
if (mDyingViews.contains(view)) {
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
// ... 省略部分代码
root = new ViewRootImpl(view.getContext(), display); //2
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView); // 3
} catch (RuntimeException e) {
// ...省略部分代码
}
}
}
}
这个方法中,前面个if判断,是做参数检查。
注释1 处,会调用findViewLocked方法,查找view是否被添加,如果已经添加,继续判断这个view是否是在即将从Window移除的view,如果是,则将其移除,如果这个view不是即将被从Widow中移除的那个对象,则会报view+ " has already been added to the window manager.异常。在注释2处,会创建ViewRootImpl对象,接着设置view的params,在将view添加到mViews这个集合中,这个mViews集合是专门存放所有Window对应的view的,接着将前面创建的ViewRootImpl对象root添加到mRoots集合中,这个mRoots集合是用于存放所有Window对应的ViewRootImpl对象的。接着将wparams添加到mParams集合中,这个mParams集合中,存储了所有Window对应的params。完成了这些操作后, 在注释3 处,调用ViewRootImpl的setView方法,下面看看ViewRootImpl的setView方法的源码:
ViewRootImpl类中
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// ...省略部分代码
int res; /* = WindowManagerImpl.ADD_OKAY; */
// 1 通过handler发送消息完成view的measure,layout,draw
requestLayout();
try {
// ...省略部分代码
// 2 通过mWindowSession.addToDisplay方法内部通过WindowManagerService完成了window的添加
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
}
// ... 省略部分代码
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
// ...省略部分代码
// 关键代码
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// ...省略部分代码
}
}
注释1出,通过调用调用requestLayout方法,接着调用了scheduleTraversals()方法,这个方法中
通过mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);这个方法最终会回调到mTraversalRunnable这个Runnable的run方法。下面接着看mTraversalRunnable的run方法:
ViewRootImpl类中
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
// ...省略部分代码
// 这个方法最终会执行performMeasure,performLayout,performDraw三个方法,完成view的测量、布局、绘制
performTraversals();
// ...省略部分代码
}
}
private void performTraversals() {
// ... 省略部分代码
try {
// ... 省略部分代码
// 关键点 relayoutWindow方法内部通过WindowManagerService完成Window的更新
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
} catch (RemoteException e) {
}
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// ... 省略部分代码
if (!mStopped || mReportNextDraw) {
// ...省略部分代码
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
// ...省略部分代码
// 关键点
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// ...省略部分代码
if (measureAgain) {
// 关键点
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// ...省略部分代码
}
}
}
// ...省略部分代码
if (didLayout) {
// 关键代码
performLayout(lp, mWidth, mHeight);
// ... 省略部分代码
}
// ...省略部分代码
if (!cancelDraw && !newSurface) {
// ...省略部分代码
// 关键代码
performDraw();
}
// ...省略部分代码
}
通过这一系列的方法的调用,ViewRootImpl的requestLayout方法方法其实就是完成了view的测量,布局和绘制过程。
接着继续看ViewRootImpl的setView方法中的注释2处
ViewRootImpl类中
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// ...省略部分代码
int res; /* = WindowManagerImpl.ADD_OKAY; */
// 1 通过handler发送消息完成view的measure,layout,draw
requestLayout();
try {
// ...省略部分代码
// 2 通过mWindowSession.addToDisplay方法了添加window
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
}
// ... 省略部分代码
}
}
}
mWindowSession是WindowManagerSerivice的Session对象在客户端的一个代理对象,它是IWindowSession的类型变量,它是IWindowSession也是一个Binder,所以mWindowSession也是一个binder,mWindowSession最终通过IPC和WindowManagerService进行通信,在SystemServer进程中,调用Session的addToDisplay方法,这个方法会调用到WindowManagerService的addWindow方法,完成对window的添加。
总结:window的添加过程,首先是通过WindowManager.addView方法发起的,由于WindowManager是个接口,实际
的addView方法是它的实现类WindowManagerImpl完成的,WindowManagerImpl的addView方法内部又通过WindowManagerGlobal类的addView方法来完成view的添加,WindowManagerGlobal内部又通过ViewRootImpl的setView方法来对view进行添加。setView方法内部,调用mWindowSession的relayoutWindow对view进行跟新,在接着完成view的测量,布局,和绘制。完成这个步骤后,在通过mWindowSession.addToDisplay()方法发起远程调用,最终调用SystemServer进程的Session类的addToDisplay()方法,这个方法内部,有调用WindowManagerService的addWindow()方法来完成window的添加。这就是Window的整个添加过程。WindowManagerService内部会为每个应用保留一个Session。