2024年Android最新Android悬浮窗看这篇就够了,阿里巴巴国际站运营面试题及答案
总结
写到这里也结束了,在文章最后放上一个小小的福利,以下为小编自己在学习过程中整理出的一个关于Flutter的学习思路及方向,从事互联网开发,最主要的是要学好技术,而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果。
由于内容较多就只放上一个大概的大纲,需要更及详细的学习思维导图的
还有高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术资料,并且还有技术大牛一起讨论交流解决问题。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
SuspendwindowService代码如下
package com.lmy.suspendedwindow.service
import android.annotation.SuppressLint
import android.graphics.PixelFormat
import android.os.Build
import android.util.DisplayMetrics
import android.view.*
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.lifecycle.LifecycleService
import com.lmy.suspendedwindow.R
import com.lmy.suspendedwindow.utils.Utils
import com.lmy.suspendedwindow.utils.ViewModleMain
import com.lmy.suspendedwindow.utils.ItemViewTouchListener
/**
- @功能:应用外打开Service 有局限性 特殊界面无法显示
- @User Lmy
- @Creat 4/15/21 5:28 PM
- @Compony 永远相信美好的事情即将发生
*/
class SuspendwindowService : LifecycleService() {
private lateinit var windowManager: WindowManager
private 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 (!Utils.isNull(windowManager)) {
windowManager?.removeView(floatRootView)
}
}
}
}
})
}
}
@SuppressLint(“ClickableViewAccessibility”)
private fun showWindow() {
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
val 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_8888
flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
//位置大小设置
width = WRAP_CONTENT
height = WRAP_CONTENT
gravity = Gravity.LEFT or Gravity.TOP
//设置剧中屏幕显示
x = outMetrics.widthPixels / 2 - width / 2
y = outMetrics.heightPixels / 2 - height / 2
}
// 新建悬浮窗控件
floatRootView = LayoutInflater.from(this).inflate(R.layout.activity_float_item, null)
floatRootView?.setOnTouchListener(ItemViewTouchListener(layoutParam, windowManager))
// 将悬浮窗控件添加到WindowManager
windowManager.addView(floatRootView, layoutParam)
}
}

ViewModleMain代码如下
package com.lmy.suspendedwindow.utils
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
/**
- @功能: 用于和Service通信
- @User Lmy
- @Creat 4/16/21 8:37 AM
- @Compony 永远相信美好的事情即将发生
*/
object ViewModleMain : ViewModel() {
//悬浮窗口创建 移除 基于无障碍服务
var isShowWindow = MutableLiveData()
//悬浮窗口创建 移除
var isShowSuspendWindow = MutableLiveData()
//悬浮窗口显示 隐藏
var isVisible = MutableLiveData()
}

Utils代码如下:
package com.lmy.suspendedwindow.utils
import android.app.Activity
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import com.lmy.suspendedwindow.service.WorkAccessibilityService
import 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_TASK
context.startActivity(intent)
} catch (e: Exception) {
val intent = Intent(Settings.ACTION_SETTINGS)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
context.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 ActivityManager
val 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 = true
if (Build.VERSION.SDK_INT >= 23) {
try {
val clazz: Class<> = Settings::class.java
val 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权限

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.WindowManager B a d T o k e n E x c e p t i o n : U n a b l e t o a d d w i n d o w a n d r o i d . v i e w . V i e w R o o t I m p l BadTokenException: Unable to add window android.view.ViewRootImpl BadTokenException:Unabletoaddwindowandroid.view.ViewRootImplW@f8ec928 – permission denied for window type 2002

Android 8.0以后不允许使用一下窗口类型来在其他应用和窗口上方显示提醒窗口,这些类型包括:
TYPE_PHONE
TYPE_PRIORITY_PHONE
TYPE_SYSTEM_ALERT
TYPE_SYSTEM_OVERLAY
TYPE_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.AccessibilityService
import android.annotation.SuppressLint
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.util.DisplayMetrics
import android.view.*
import android.view.accessibility.AccessibilityEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.lmy.suspendedwindow.R
import com.lmy.suspendedwindow.utils.ItemViewTouchListener
import com.lmy.suspendedwindow.utils.Utils.isNull
import com.lmy.suspendedwindow.utils.ViewModleMain
/**
- @功能:利用无障碍打开悬浮窗口 无局限性 任何界面可以显示
- @User Lmy
- @Creat 4/15/21 5:57 PM
- @Compony 永远相信美好的事情即将发生
*/
class WorkAccessibilityService : AccessibilityService(), LifecycleOwner {
private lateinit var windowManager: WindowManager
private var floatRootView: View? = null//悬浮窗View
private val mLifecycleRegistry = LifecycleRegistry(this)
override fun onCreate() {
super.onCreate()
mLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
initObserve()
}
/**
- 打开关闭的订阅
*/
private fun initObserve() {
ViewModleMain.isShowWindow.observe(this, {
if (it) {
showWindow()
} else {
建议
当我们出去找工作,或者准备找工作的时候,我们一定要想,我面试的目标是什么,我自己的技术栈有哪些,近期能掌握的有哪些,我的哪些短板 ,列出来,有计划的去完成,别看前两天掘金一些大佬在驳来驳去 ,他们的观点是他们的,不要因为他们的观点,膨胀了自己,影响自己的学习节奏。基础很大程度决定你自己技术层次的厚度,你再熟练框架也好,也会比你便宜的,性价比高的替代,很现实的问题但也要有危机意识,当我们年级大了,有哪些亮点,与比我们经历更旺盛的年轻小工程师,竞争。
-
无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!!!!!!!
-
准备想说怎么样写简历,想象算了,我觉得,技术就是你最好的简历
-
我希望每一个努力生活的it工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。
-
有什么问题想交流,欢迎给我私信,欢迎评论
【附】相关架构及资料


内含往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
存中…(img-eXG6nHwc-1715660452701)]
[外链图片转存中…(img-HceXcqwS-1715660452701)]
内含往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
更多推荐

所有评论(0)