安卓全局浮层方案调研
背景:
为满足开机广告丰富样式,7.76.0新闻客户端接入LongView
广告样式。目前的LongView
效果实现是复用之前一镜到底
的视频缩放动画,与集成了sax
广告SDK的倒计时相关组件。最后用单独的一个Activity层级
的浮层Window来承载后续小窗内容。
这样做的方式目前满足了第一版的需求,但是前置动画与小窗目前是侵入式埋点在主页面的onCreate
中,后续如果想扩充其他场景需要再继续埋入相应代码,不利于统一维护且容易遗漏,并且当前的浮层不能 “跨页面”。
悬浮窗基本原理
类似于动态添加View,但是由于悬浮窗是不依赖具体某个View
的所以需要WindowManager
介入。
- 获取
WindowManger
- 创建悬浮窗View [可额外处理悬浮窗View的拖拽事件等]
- 添加到
WindowManager
中
1. 应用内添加悬浮窗(页面级别)
优点:方便添加无需权限申请,快速创建。
缺点:Activity层级
无法全局,退后台后不能显示。
整体流程伪代码如下:
1 | var layoutParam = WindowManager.LayoutParams().apply { |
2. 系统类全局悬浮窗(需申请权限)
优点:可以在应用后台后依然显示,官方支持,限制较少。
缺点:需要权限申请考虑用户授权率打开率或许较低,且如果想覆盖所有场景,需要无障碍敏感权限。由于有后台操作,所以接口需要用到Service
参与。
(总结:使用普通的Service创建悬浮窗无法做到任何界面都能显示,利用无障碍服务可以做到任何界面悬浮)
1 | <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> |
相应权限判断代码省略。(6.0以下声明权限即默认拥有,但华为小米OPPO等手机有自己的畸形悬浮窗权限管理,仍旧需要考虑适配。)
注意点WindowManager.LayoutParam
中的type
需要留意区分版本:
1 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |
Android 8.0之前可以通过TYPE_PHONE
来提供用户交互窗口,但是8.0及以上会抛出异常:
1 | android.view.WindowManager$BadTokenException: Unable to add window android.view.ViewRootImpl$W@f8ec928 -- permission denied for window type 2002 |
同时以下类型也被禁止:
注意点:
综上所以我们必须添加TYPE_APPLICATION_OVERLAY
类型来达到显示目的,但是某些情况下悬浮窗效果仍旧会失效不显示。
所以此时如果仍旧想要覆盖此种场景(如小米手机查看系统信息页面),需要添加TYPE_ACCESSIBILITY_OVERLAY
类型,则必须搭配AccessibilityService
无障碍服务使用。
过程概述:
- 配置无障碍服务
- 在
AccessibilityService
中获取WindowManager
- 创建悬浮View[设置悬浮View的拖拽事件]
- 添加View到
WindowManager
3. 应用内全局悬浮窗
优点:无需权限申请,可满足大部分场景,相对较少的侵入代码。
缺点:无法后台,且本质上仍旧是Activity页面级别
在页面切换时,需要更新token
依附会有闪动。
关键点是type
类型中有一个TYPE_APPLICATION_PANEL
适合用于应用内的悬浮窗开发。所以可以在BaseActivity
基类中埋入相关代码,不断切换悬浮窗的token,也可用LifeCycle监听解耦基类代码。
过程伪代码:
1 | WindowManager windowManager = (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE); |
由于关键点仍旧依附于页面,所以切换Activity时会跟随动画一起位移且会有闪动。
之后在生命周期时做token 切换:
1 | override fun onActivityResumed(activity: Activity?) { |
总结
对比上述方案,授予权限仍旧是最佳的方案,但由于广告的特殊性用户授予权限场景低,这样会导致LongView的全局浮层几乎失效。
那么第三种方案,将会是首选,目前看到也有通过自定义toast方案来解决的,但是对于一些手机系统如小米ROM不稳定等问题需要进一步考量。