写点什么

Android 悬浮窗看这篇就够了,android 笔试面试题

作者:嘟嘟侠客
  • 2021 年 11 月 27 日
  • 本文字数:4702 字

    阅读完需:约 15 分钟

floatRootView = LayoutInflater.from(this).inflate(R.layout.activity_float_item, null)//设置拖动事件 floatRootView?.setOnTouchListener(ItemViewTouchListener(layoutParam, windowManager))// 将悬浮窗控件添加到 WindowManagerwindowManager.addView(floatRootView, layoutParam)


点击并拖拽以移动


拖拽监听 ItemViewTouchListener 代码如下:


class ItemViewTouchListener(val wl: WindowManager.LayoutParams, val windowManager: WindowManager) :View.OnTouchListener {private var x = 0private var y = 0override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {when (motionEvent.action) {MotionEvent.ACTION_DOWN -> {x = motionEvent.rawX.toInt()y = motionEvent.rawY.toInt()


}MotionEvent.ACTION_MOVE -> {val nowX = motionEvent.rawX.toInt()val nowY = motionEvent.rawY.toInt()val movedX = nowX - xval movedY = nowY - yx = nowXy = nowYwl.apply {x += movedXy += movedY}//更新悬浮球控件位置 windowManager?.updateViewLayout(view, wl)}else -> {


}}return false}}


点击并拖拽以移动

效果


?


点击并拖拽以移动

应用外悬浮窗(有局限性)

应用外悬浮窗实现流程 这里我使用了 LivaData 来进行和 Service 的通信


  • 申请悬浮窗权限

  • 创建 Service

  • 获取 WindowManager

  • 创建悬浮 View

  • 设置悬浮 View 的拖拽事件

  • 添加 View 到 WindowManager


在清单文件添加权限


<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /><uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />


点击并拖拽以移动


上代码:


打开悬浮窗


startService(Intent(this, SuspendwindowService::class.java))Utils.checkSuspendedWindowPermission(this) {isReceptionShow = falseViewModleMain.isShowSuspendWindow.postValue(true)}


点击并拖拽以移动


SuspendwindowService 代码如下


package com.lmy.suspendedwindow.service


import android.annotation.SuppressLintimport android.graphics.PixelFormatimport android.os.Buildimport android.util.DisplayMetricsimport android.view.*import android.view.ViewGroup.LayoutParams.WRAP_CONTENTimport androidx.lifecycle.LifecycleServiceimport com.lmy.suspendedwindow.Rimport com.lmy.suspendedwindow.utils.Utilsimport com.lmy.suspendedwindow.utils.ViewModleMainimport com.lmy.suspendedwindow.utils.ItemViewTouchListener


/**


  • @功能:应用外打开 Service 有局限性 特殊界面无法显示

  • @User Lmy

  • @Creat 4/15/21 5:28 PM

  • @Compony 永远相信美好的事情即将发生*/class SuspendwindowService : LifecycleService() {private lateinit var windowManager: WindowManagerprivate var floatRootView: View? = null//悬浮窗 View


override fun onCreate() {super.onCreate()initObserve()}


private fun initObserve() {ViewModleMain.apply {isVisible.observe(this@SuspendwindowService, {floatRootView?.visibility = if (it) View.VISIBLE else View.GONE})isShowSuspendWindow.observe(this@SuspendwindowService, {if (it) {showWindow()} else {if (!Utils.isNull(floatRootView)) {if (!Utils.isNull(floatRootView?.windowToken)) {if (!


《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享


Utils.isNull(windowManager)) {windowManager?.removeView(floatRootView)}}}}})}}


@SuppressLint("ClickableViewAccessibility")private fun showWindow() {windowManager = getSystemService(WINDOW_SERVICE) as WindowManagerval outMetrics = DisplayMetrics()windowManager.defaultDisplay.getMetrics(outMetrics)var layoutParam = WindowManager.LayoutParams().apply {type = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY} else {WindowManager.LayoutParams.TYPE_PHONE}format = PixelFormat.RGBA_8888flags =WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE//位置大小设置 width = WRAP_CONTENTheight = WRAP_CONTENTgravity = Gravity.LEFT or Gravity.TOP//设置剧中屏幕显示 x = outMetrics.widthPixels / 2 - width / 2y = outMetrics.heightPixels / 2 - height / 2}// 新建悬浮窗控件 floatRootView = LayoutInflater.from(this).inflate(R.layout.activity_float_item, null)floatRootView?.setOnTouchListener(ItemViewTouchListener(layoutParam, windowManager))// 将悬浮窗控件添加到 WindowManagerwindowManager.addView(floatRootView, layoutParam)}}


点击并拖拽以移动


ViewModleMain 代码如下


package com.lmy.suspendedwindow.utils


import androidx.lifecycle.AndroidViewModelimport androidx.lifecycle.MutableLiveDataimport androidx.lifecycle.ViewModel


/**


  • @功能: 用于和 Service 通信

  • @User Lmy

  • @Creat 4/16/21 8:37 AM

  • @Compony 永远相信美好的事情即将发生*/object ViewModleMain : ViewModel() {//悬浮窗口创建 移除 基于无障碍服务 var isShowWindow = MutableLiveData<Boolean>()//悬浮窗口创建 移除


var isShowSuspendWindow = MutableLiveData<Boolean>()


//悬浮窗口显示 隐藏 var isVisible = MutableLiveData<Boolean>()


}


点击并拖拽以移动


Utils 代码如下:


package com.lmy.suspendedwindow.utils


import android.app.Activityimport android.app.ActivityManagerimport android.content.Contextimport android.content.Intentimport android.net.Uriimport android.os.Buildimport android.provider.Settingsimport android.text.TextUtilsimport android.util.Logimport android.widget.Toastimport com.lmy.suspendedwindow.service.WorkAccessibilityServiceimport java.util.*


/**


  • @功能: 工具类

  • @User Lmy

  • @Creat 4/16/21 8:33 AM

  • @Compony 永远相信美好的事情即将发生/object Utils {const val REQUEST_FLOAT_CODE=1001/*

  • 跳转到设置页面申请打开无障碍辅助功能*/private fun accessibilityToSettingPage(context: Context) {//开启辅助功能页面 try {val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)intent.flags = Intent.FLAG_ACTIVITY_NEW_TASKcontext.startActivity(intent)} catch (e: Exception) {val intent = Intent(Settings.ACTION_SETTINGS)intent.flags = Intent.FLAG_ACTIVITY_NEW_TASKcontext.startActivity(intent)e.printStackTrace()}}


/**


  • 判断 Service 是否开启


*/fun isServiceRunning(context: Context, ServiceName: String): Boolean {if (TextUtils.isEmpty(ServiceName)) {return false}val myManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManagerval runningService =myManager.getRunningServices(1000) as ArrayList<ActivityManager.RunningServiceInfo>for (i in runningService.indices) {if (runningService[i].service.className == ServiceName) {return true}}return false}


/**


  • 判断悬浮窗权限权限/private fun commonROMPermissionCheck(context: Context?): Boolean {var result = trueif (Build.VERSION.SDK_INT >= 23) {try {val clazz: Class<> = Settings::class.javaval canDrawOverlays =clazz.getDeclaredMethod("canDrawOverlays", Context::class.java)result = canDrawOverlays.invoke(null, context) as Boolean} catch (e: Exception) {Log.e("ServiceUtils", Log.getStackTraceString(e))}}return result}


/**


  • 检查悬浮窗权限是否开启*/fun checkSuspendedWindowPermission(context: Activity, block: () -> Unit) {if (commonROMPermissionCheck(cont ext)) {block()} else {Toast.makeText(context, "请开启悬浮窗权限", Toast.LENGTH_SHORT).show()context.startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).apply {data = Uri.parse("package:${context.packageName}")}, REQUEST_FLOAT_CODE)}}


/**


  • 检查无障碍服务权限是否开启*/fun checkAccessibilityPermission(context: Activity, block: () -> Unit) {if (isServiceRunning(context, WorkAccessibilityService::class.java.canonicalName)) {block()} else {accessibilityToSettingPage(context)}}


fun isNull(any: Any?): Boolean = any == null


}


点击并拖拽以移动


效果:

悬浮窗权限的适配

权限配置和请求

这一块倒是没什么坑


在当 Android7.0 以上的时候,需要在 AndroidManefest.xml 文件中声明 SYSTEM_ALERT_WINDOW 权限


<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /><uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />


点击并拖拽以移动

LayoutParam 的坑!!!!

WindowManager 的 addView 方法有两个参数,一个是需要加入的控件对象,另一个参数是 WindowManager.LayoutParam 对象。


LayoutParam 里的 type 变量。有大坑!!!!!!,这个变量是用来指定窗口类型的。在设置这个变量时,需要对不同版本的 Android 系统进行适配。


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;} else {layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;}


点击并拖拽以移动


在 Android 8.0 之前,悬浮窗口设置可以为 TYPE_PHONE,这种类型是用于提供用户交互操作的非应用窗口。


但是 Android 8.0 以上版本你继续使用 TYPE_PHONE 类型的悬浮窗口,则会出现如下异常信息:


android.view.WindowManagerW@f8ec928 -- permission denied for window type 2002


点击并拖拽以移动


Android 8.0 以后不允许使用一下窗口类型来在其他应用和窗口上方显示提醒窗口,这些类型包括:


TYPE_PHONETYPE_PRIORITY_PHONETYPE_SYSTEM_ALERTTYPE_SYSTEM_OVERLAYTYPE_SYSTEM_ERROR


点击并拖拽以移动


如果需要实现在其他应用和窗口上方显示提醒窗口,那么必须该为 TYPE_APPLICATION_OVERLAY 的类型。



点击并拖拽以移动


?


但是这个 TYPE_APPLICATION_OVERLAY 类型无法在所有界面上进行显示


就像是这样



?


点击并拖拽以移动



点击并拖拽以移动


?


有的同学会问这什么鬼操作啊?这怎么解决


不要慌 只要耐心找总会找到的答案的 百度不行那就谷歌



点击并拖拽以移动


?


经过不懈的努力终于找到了解决办法


使用另外一个类型 TYPE_ACCESSIBILITY_OVERLAY 就可以解决此问题


但是当你开开心心使用的时候你会发现以下错误



点击并拖拽以移动


?



点击并拖拽以移动


?


经过查证一些资料之后发现这个类型必须和无障碍 AccessibilityService 搭配使用

无障碍悬浮窗

配置无障碍流程见我另一篇博客基于无障碍服务实现自动跳过APP启动页广告


无障碍悬浮窗实现流程


  • 配置无障碍服务

  • 在 AccessibilityService 中获取 WindowManager

  • 创建悬浮 View

  • 设置悬浮 View 的拖拽事件

  • 添加 View 到 WindowManager


启动悬浮窗:


Utils.checkAccessibilityPermission(this) {ViewModleMain.isShowWindow.postValue(true)}


点击并拖拽以移动


WorkAccessibilityService 代码如下


package com.lmy.suspendedwindow.service


import android.accessibilityservice.AccessibilityServiceimport android.annotation.SuppressLintimport android.content.Intentimport android.graphics.PixelFormatimport android.os.Buildimport android.util.DisplayMetricsimport android.view.*


《960 全网最全 Android 开发笔记》



《379 页 Android 开发面试宝典》



《507 页 Android 开发相关源码解析》



因为文件太多,全部展示会影响篇幅,暂时就先列举这些部分截图


本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

用户头像

嘟嘟侠客

关注

还未添加个人签名 2021.03.19 加入

还未添加个人简介

评论

发布
暂无评论
Android悬浮窗看这篇就够了,android笔试面试题