使用全局悬浮窗的应用,在Android 7.1.1 (Android N MR1)上会crash,crash 堆栈类似下面:
E/AndroidRuntime: FATAL EXCEPTION: main
android.view.WindowManager$BadTokenException: Unable to add window -- window android.view.ViewRootImpl$W@32622a4 has already been added
at android.view.ViewRootImpl.setView(ViewRootImpl.java:691)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
at com.tencent.ysdk.module.icon.impl.a.g(Unknown Source)
at com.tencent.ysdk.module.icon.impl.floatingviews.q.onAnimationEnd(Unknown Source)
at android.view.animation.Animation$3.run(Animation.java:381)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6119)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
同时在堆栈附近有下面这样一句话:
Adding more than one toast window for UID at a time
最近在开发一个悬浮窗功能,为了方便应用开发者的调用,个人采用了使用全局悬浮窗的方案;同时为了突破厂商的限制,同时又使用了将悬浮窗的type设置为Toast类型。下面是事例代码:
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.format = PixelFormat.RGBA_8888;
params.gravity = Gravity.LEFT | Gravity.TOP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
params.type = WindowManager.LayoutParams.TYPE_TOAST;
} else {
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}
今天上午Google提示Android 7.1.1可以升级更新,然后就愉快的更新了系统,然后悲催的发现开发中的版本忽然crash了,其中上面的现象中已经说了问题现象。
根据crash的现象,感觉确定是在addView的时候出了问题。然后最近有调整的只有两个方面:
原则上不是这个问题。为了缩小范围排除问题范围,因此先使用代码调整前的版本验证。结果发现问题依然存在,因此确定不是因为代码引起的。
使用另外一台同机型的 Android 7.0的设备验证,相同的包也不会出问题,因此基本上确认就是更新系统引起的。但是google应该不会把这么大的bug遗留下来,那就有可能是有一些变化和调整。
结合上面的crash堆栈和问题提示,个人感觉更有可能和Adding more than one toast window for UID at a time
这句话有关,去google搜索一下,排在第一位的就是关于Android源码的说明:
Android源码关于Toast修改的说明:https://android.googlesource.com/platform/frameworks/base/+/dc24f93
核心内容:
Prevent apps to overlay other apps via toast windows
It was possible for apps to put toast type windows
that overlay other apps which toast winodws aren't
removed after a timeout.
Now for apps targeting SDK greater than N MR1 to add a
toast window one needs to have a special token. The token
is added by the notificatoion manager service only for
the lifetime of the shown toast and is then removed
including all windows associated with this token. This
prevents apps to add arbitrary toast windows.
Since legacy apps may rely on the ability to directly
add toasts we mitigate by allowing these apps to still
add such windows for unlimited duration if this app is
the currently focused one, i.e. the user interacts with
it then it can overlay itself, otherwise we make sure
these toast windows are removed after a timeout like
a toast would be.
We don't allow more that one toast window per UID being
added at a time which prevents 1) legacy apps to put the
same toast after a timeout to go around our new policy
of hiding toasts after a while; 2) modern apps to reuse
the passed token to add more than one window; Note that
the notification manager shows toasts one at a time.
bug:30150688
Change-Id: Icc8f8dbd060762ae1a7b1720e96c5afdb8aff3fd
恩,看来滥用悬浮窗的问题已经很严重了,基本确认这次遇到的异常就是这个问题引起的。
根据上面官方的说明,这个变更应该只涉及到Toast,因此只需要保证在7.1.1以上不滥用Toast即可。
在代码中添加对于7.1.1以上版本的判断,7.1.1以上版本用回phone。代码如下:
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.format = PixelFormat.RGBA_8888;
params.gravity = Gravity.LEFT | Gravity.TOP;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if(Build.VERSION.SDK_INT > 24){
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}else{
params.type = WindowManager.LayoutParams.TYPE_TOAST;
}
} else {
params.type = WindowManager.LayoutParams.TYPE_PHONE;
}
然后测试问题确实已经解决~
通过使用phone类型确实解决了crash的问题,但是没有解决厂商的限制,等厂商都升级到7.1.1以后,我们的悬浮窗弹出成功率又会大幅下降。由于我们的实际诉求本来就不是要添加全局的悬浮窗,之所以选择悬浮窗是为了降低第三方的接入成本。在目前这种前提下我们只好添加与第三方的交互,在新的版本调整实现方式,弃用全局悬浮窗,改为在应用内,通过应用添加悬浮窗实现。
目前对于全局的悬浮窗,不管是厂商还是官方,都已经出手开始限制,可见悬浮窗已经彻底被玩坏了~