关于WindowManager在Android N和Android N以下表现差异的分析总结
Posted this.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于WindowManager在Android N和Android N以下表现差异的分析总结相关的知识,希望对你有一定的参考价值。
1. 问题描述
通过WindowManager往窗口里添加浮动按钮,在android7.0时该按钮可以全局保留,直至进程被杀掉。而Android7.0以下(以Android4.4为例)浮动按钮随Activity的onStop()方法被覆盖。
以下为浮动按钮的实现代码:
WindowManager mWm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type= LayoutParams.TYPE_PHONE; // 系统提示类型,重要
params.format=1;
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.flags = params.flags | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
params.flags = params.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
params.width = dipToPixel(60);
params.height = dipToPixel(60);
params.x = dipToPixel(140);
params.y = dipToPixel(230);
View view = LayoutInflater.from(mContext).inflate(R.layout.float_view, null);
mWm.addView(view, params);
FloatView floatView = new FloatView(this); //传入Activity Context
2.解决方案
考虑到可能由于传入的Context为Activity导致的问题,进行修改后发现浮动按钮在Android4.4下正常运行。
FloatView floatView = new FloatView(getApplicationContext());
以下则以两方面分析差异原因:
* 使用Activity表现不同的原因。
* 使用Application表现相同的原因。
3.原因分析
##### * 使用Activity表现不同的原因。
首先,可以了解到Activity对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);
WindowManager
在Activity初始化的时候就已经被创建。mWindowManager
变量在ActivityThread的handleLaunchedActivity
的activity.attach()
方法中赋值。最终该变量指向了WindowManagerImpl
对象。
public WindowManagerImpl createLocalWindowManager(Window parentWindow)
return new WindowManagerImpl(mContext, parentWindow);
此处的parentWindow
为当前Activity的Window
。
之后使用WindowManager
的addView
方法添加按钮,最终会调用WindowManagerGlobal
的addView
方法。其中该方法有如下语句:
if (parentWindow != null)
parentWindow.adjustLayoutParamsForSubWindow(wparams);
从该该方法开始,表现出现差异:
//Android 7.0
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)
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);
//...
//Android 4.4
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.token == null)
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
if ((curTitle == null || curTitle.length() == 0)
&& mAppName != null)
wp.setTitle(mAppName);
//...
代码大概的逻辑是判断该LayoutParams
的type
是属于应用窗口级别,还是系统窗口级别。从而为LayoutParams
添加对应的token
。
此处token
可以用来绑定Activity
和WindowManager
。当视图发生变更需要重新遍历或者调用onStop覆盖掉当前Activity时,就可以通过该token找到对应的ViewRootImpl
,从而进行View树的相关操作。
在Android7.0时,系统窗口没有设置token,4.4时,token设置为默认的mAppToken
,即Activity启动时由AMS
创建并传递过来的IBinder
对象。
在Activity进入后台后,会调用Activity中的performStop方法,方法中执行以下语句:
WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
从而调用了setStoppedState方法:
public void setStoppedState(IBinder token, boolean stopped)
synchronized (mLock)
int count = mViews.size();
for (int i = 0; i < count; i++)
//token是Activity的mToken 不为空
if (token == null || mParams.get(i).token == token)
ViewRootImpl root = mRoots.get(i);
root.setWindowStopped(stopped);
当LayoutParams的token
与Activity的token
匹配时,则取出ViewRootImpl
调用setWindowStopped
方法,从而执行onStop过程的后续操作。
Android7.0 token为null,不匹配,无法进入判断,则不进行对浮动按钮的后续操作,所以会常驻留窗口。
Android4.4 token为Activity持有的mToken
,匹配,会执行后续操作。
##### * 使用Application表现相同的原因。
Application没有对getSystemService
进行重写,全部实现交由ContextImpl
实现。
@Override
public Object getSystemService(String name)
return SystemServiceRegistry.getSystemService(this, name);
上述从SystemServiceRegistry
取出相应服务。SystemServiceRegistry
在APP创建时会初始化各种服务。其中包括WindowManager
。
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>()
@Override
public WindowManager createService(ContextImpl ctx)
return new WindowManagerImpl(ctx);
);
通过Application的getSystemService
获得的WindowManager
仍然是WindowManagerImpl
对象。Application和Activity创建的区别也在此处。
//Application调用
public WindowManagerImpl(Context context)
this(context, null);
//Activity调用
private WindowManagerImpl(Context context, Window parentWindow)
mContext = context;
mParentWindow = parentWindow;
Application获得的WindowManagerImpl
的mParentWindow
属性为空。这样会在addView
时跳过token的赋值。
if (parentWindow != null)
parentWindow.adjustLayoutParamsForSubWindow(wparams);
从而在setStopState
方法判断mParams.get(i).token == token
中不论是7.0还是4.4中token都为null,不匹配,无法进入后续的stop操作,浮动按钮就常驻窗口。
4. 个人理解
Android系统为每个Activity初始化时添加一个本地的WindowManager
引用。由于Activity涉及频繁的View操作,对用WindowManager
的使用频率较高,提前初始化对于效率会是一种提升。
以上是关于关于WindowManager在Android N和Android N以下表现差异的分析总结的主要内容,如果未能解决你的问题,请参考以下文章
关于ProgressDialog.show抛出android.view.WindowManager$BadTokenException: Unable to add window
[Android FrameWork 6.0源码学习] View的重绘过程之WindowManager的addView方法
Android WindowManager悬浮窗:不需要申请权限实现悬浮
Android.view.WindowManager $ BadTokenException: