背景
在某些场景下,应用退到后台其实还需要保留与用户一定程度的交互,这时候就衍生出今天的主题,App桌面浮球的实现。具体内部实现其实也不难,下面我会带大家一步步分析整个流程。
基础知识
杂七杂八
-
有了解过App启动流程的同学肯定都或多或少接触过LaunchActivity的源码,LaunchActivity是继承与Activity的,手机桌面图标就是通过LaunchActivity来启动以安装的应用的。下面介绍个可以在线查看源码的网站👉在线查看源码
-
看过activity的setContent的源码都知道,内部主要是phoneWindow里面DecorView添加setContent里指定的布局,而布局类型是PhoneWindow的generateLayout方法里会根据当前用户设置的主题去设置对应的Feature,接着,根据对应的Feature来选择加载对应的布局文件。然后将刚刚inflate出来的View往DecorView里添加。详细的可以看这篇文章介绍👉setContent内部密码
-
Window都是通过WindowManager创建、管理的,然而实现WindowManager唯一类是WindowManagerImpl,但实际做工作的是WindowManagerGlobal。可以看这篇文章介绍Window和WindowManager的创建与Activity关系👉Window和WindowManager的创建与Activity关系。
提示:里面会涉及到AIDL进程通信获取WindowServiceManager(行业话:Binder驱动获取client),可以先了解Linux的mmap和android binder驱动如何实现的。
浮球实现
实现原理分析
-
上面分析了我们桌面应用是一个继承Activity的类,那么如果我们调用WindowManager创建一个Window,然后塞入一个我们想要的展示在手机桌面的View;或者是说WindowManager能直接给个api让我们把想要展示的View塞入,就更完美了。
-
大胆假设后就要小心求证了,下面回到我们的代码中进行分析下。
WindowManager搜addView方法
上面通过WindowManager的接口搜全部function,我们搜到一个符合我们预想的function。
-
ViewManager接口,ViewManager的注解是说,给一个Activity添加or移除一个View;可以通过Context.getSystemService(java.lang.String)获取到相应的对象。
核心代码
- 首先要获取到ViewManager的对象。刚刚基础知识补充道了LaunchActivity是一个Activity,所以这里通过WINDOW_SERVICE获取到桌面的LaunchActivity。WINDOW_SERVICE的注解:通过该字符串访问系统的窗口管理器。
private var mWindowManager: WindowManager =
context.applicationContext.getSystemService(Context.WINDOW_SERVICE) as WindowManager
- 获取到系统管理器后,我们就要准备好我们addView的过程。了解过自定义View的童鞋都知道addView要准备的包括:View本身和LayoutParams。那我们要实现桌面浮窗也必须要准备好这两样。View的话没什么好详细讲解的,这个根据具体业务决定的。那么我们下面讲解下LayoutParams这部分。
private fun generateLayoutParams(): WindowManager.LayoutParams {
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
getWindowType(),
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or // Keeps the button presses from going to the background window
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or // Enables the notification to recieve touch events
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,// Draws over status bar
PixelFormat.TRANSLUCENT
)
params.gravity = Gravity.LEFT or Gravity.TOP
params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
return params
}
/**
* http://liaohuqiu.net/cn/posts/android-windows-manager/
* @return Window的type类型
*/
fun getWindowType(): Int {
return if (isFloatWindowOpAllowed(mApplicationContext)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else if (Build.VERSION.SDK_INT >= 25) {
//7.1系统修复toast问题,导致悬浮球不能常驻
WindowManager.LayoutParams.TYPE_PHONE
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WindowManager.LayoutParams.TYPE_TOAST
} else {
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
}
}
上面getWindowType方法是设置window的type,那是因为window的type能决定在UI展示上层级问题。还有上面涉及到的gravity和softInputMode用处可以看这篇博客介绍👉Window详解
- 当View和LayoutParams都准备好以后,我们就可以进行我们的addView的操作了。
//显示桌面浮窗
mWindowManager.addView(view, layoutParams)
//移除桌面浮窗,注意上下两个view必须是同个对象,要不然你应该懂会出现什么问题的😼
mWindowManager.removeView(view)
大家想看详细的project可以自行clone来看:demo的github地址: feature_1.0.0分支
总结
- 当基础知识不断巩固后,你可以用一个更好理解的角度去想明白这段代码的含义。例如上面的浮窗实现,ViewManager接口注解提及到的给activity添加/移除View,当你没理解桌面app启动如何进行,没了解到LaunchActivity是集成Activity的时候,可能你就很难将ViewManager的注解意思理解通透。
- 学习是一个慢慢积累的过程,一起加油吧💪💪💪💪
网友评论