无障碍权限是高危权限,前提是能很好的引导用户进行授权。此方案即使用户手动杀死App,该App的无障碍服务依旧幸存
随着目前Android 生态的逐渐完善,以前的保活方案例如 后台无声音乐、双守护进程、1像素Activity、leoric(https://github.com/tiann/Leoric,当然还有就是ioctl方式(https://github.com/Pangu-Immortal/KeepAlivePerfect)也可以实现(经验证大部分Android 14+的设备并不能有效保活)都逐步失效,毕竟这些方案终究是偏门,考虑到长久可靠的方案;这里给大家介绍下用Android 原生无障碍服务维持应用长久保活的方式。
1、授权无障碍权限
找到对应的App进行授权
2、启动一个前台服务,业务逻辑可以在该前台服务内进行,授权无障碍权限后,即使应用没有任何Activity了,该服务也能正常运行,当然也可以直接在无障碍服务里边处理业务逻辑,但是用户难以感知该应用还活着(前台服务可以设置通知栏显示应用状态)
import android.accessibilityservice.AccessibilityService
import android.graphics.Bitmap
import android.os.Build
import android.view.Display
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RequiresApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor
class SmartAccessibilityService:AccessibilityService() {
companion object {
private var instance: SmartAccessibilityService? = null
fun acquire(): SmartAccessibilityService? = instance
}
override fun onAccessibilityEvent(p0: AccessibilityEvent?) {
}
override fun onInterrupt() {
}
override fun onServiceConnected() {
super.onServiceConnected()
instance = this
}
@RequiresApi(Build.VERSION_CODES.R)
fun takeScreenshot(result:(bitmap:Bitmap?)->Unit){
takeScreenshot(
Display.DEFAULT_DISPLAY, Dispatchers.Main.asExecutor(), object : TakeScreenshotCallback {
override fun onSuccess(p0: ScreenshotResult) {
try {
p0.hardwareBuffer.use { buffer ->
val bitmap = Bitmap.wrapHardwareBuffer(buffer, p0.colorSpace)
result(bitmap)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun onFailure(p0: Int) {
}
}
)
}
}
import android.accessibilityservice.AccessibilityServiceInfo
import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.view.accessibility.AccessibilityManager
object AccessibilityHelper{
//无障碍服务开关状态判断
fun isServiceToggled(context: Context, accessibilityService: String = context.packageName): Boolean {
val enabledServices = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
)
return enabledServices?.contains(accessibilityService) == true
}
//无障碍服务是否可用
fun isServiceEnabled(context: Context, packageName: String = context.packageName): Boolean {
val accessibilityManager = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
val installedServices = accessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_GENERIC)
for (service in installedServices) {
val enabledServiceInfo = service.resolveInfo.serviceInfo
if (enabledServiceInfo.packageName == packageName) {
return true
}
}
return false
}
fun permissionCheck(
context: Context , packageName: String = context.packageName,
yes: (() -> Unit), no:(()->Unit)? = null) {
val valid = isServiceEnabled(context,packageName) and isServiceToggled(context,packageName)
if (valid){
yes.invoke()
}else{
if (no != null){
no.invoke()
}else{
requestPermission(context)
}
}
}
fun requestPermission(context: Context){
context.startActivity(
Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
.apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK })
}
}
大家要理解保活仅仅是让应用活着,各大厂商平台的Doze处理并不能保证应用能一直和服务器或者用户交互。所以要想实现应用长时间存活以及和用户、服务器交互还需要授权 "忽略电池优化" 和 "允许所有后台行为"
首先在AndroidManifest.xml中声明以下权限:
然后跳转到该授权界面
import android.content.Context
import android.content.Intent
import android.os.PowerManager
import android.provider.Settings
import android.widget.Toast
object BatteryOptimizationUtils {
/**
* 检查应用是否已经被忽略电池优化
* @param context 上下文对象
* @return 如果已经被忽略返回 true,否则返回 false
*/
fun isIgnoringBatteryOptimizations(context: Context): Boolean {
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
val packageName = context.packageName
return powerManager.isIgnoringBatteryOptimizations(packageName)
}
/**
* 请求用户开启忽略电池优化
* @param context 上下文对象
*/
fun requestIgnoreBatteryOptimizations(context: Context) {
try {
val intent = Intent()
val packageName = context.packageName
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
intent.data = android.net.Uri.parse("package:$packageName")
context.startActivity(intent)
}
} catch (e: Exception) {
e.printStackTrace()
Toast.makeText(context, "请求忽略电池优化失败", Toast.LENGTH_SHORT).show()
}
}
}
如果不想让用户从菜单卡片手动杀死APP可如此设置
private fun hideFromBackground(isChecked:Boolean){
//App 后台隐藏
(getSystemService(Activity.ACTIVITY_SERVICE) as ActivityManager).let { manager ->
manager.appTasks.forEach { task ->
task?.setExcludeFromRecents(isChecked)
}
}
}
想在App界面实现应用屏幕常亮可如此设置 FLAG_KEEP_SCREEN_ON
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
enableEdgeToEdge()
ViewCompat.setOnApplyWindowInsetsListener(mBinding.toolbar) { v: View, insets: WindowInsetsCompat ->
v.setPadding(
v.paddingLeft,
insets.getInsets(WindowInsetsCompat.Type.statusBars()).top,
v.paddingRight,
v.paddingBottom
)
WindowInsetsCompat.CONSUMED
}
}
想在App存活期间一直屏幕常亮可如此设置
object ScreenUtils {
private const val WAKE_LOCK_TAG = "myapp:mywakelocktag" // 请将 "myapp" 替换为你的应用包名
private var wakeLock: PowerManager.WakeLock? = null
fun wakeUpAndUnlockScreen(){
if (!isScreenOn(app)) {
wakeUpScreen(app)
}
}
fun lockScreen(context: Context = app){
val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
// 以下代码尝试禁用锁屏(在部分设备和场景下可能有限制)
val keyguardLock = keyguardManager.newKeyguardLock("MyAppLock")
keyguardLock.disableKeyguard()
}
@SuppressLint("WakelockTimeout")
fun acquireWakeLockNoTimeout(context: Context) {
val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, WAKE_LOCK_TAG)
wakeLock?.acquire() // 10 seconds is the maximum time to hold a wake lock.
Log.d("ScreenUtils", "wakeUpScreen not timeout")
}
/**
* 点亮屏幕
*/
fun wakeUpScreen(context: Context) {
// 确保WakeLock的唯一性
val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, WAKE_LOCK_TAG)
wakeLock?.acquire(10000) // 10 seconds is the maximum time to hold a wake lock.
Log.d("ScreenUtils", "wakeUpScreen")
}
/**
* 判断屏幕是否亮着
*/
fun isScreenOn(context: Context): Boolean {
val powerManager = context.applicationContext.getSystemService(Context.POWER_SERVICE) as PowerManager
Log.d("ScreenUtils", "isScreenOn: ${powerManager.isInteractive}")
return powerManager.isInteractive
}
/**
* 释放WakeLock
*/
fun releaseWakeLock() {
if (wakeLock?.isHeld == true) {
wakeLock?.release()
}
}
}
只需要在这里设置
override fun onPause() {
super.onPause()
val alwaysLight = MMKV.defaultMMKV().getBoolean(Constant.AlwaysLight.key,false)
if (alwaysLight){
ScreenUtils.acquireWakeLockNoTimeout(this)
}else{
ScreenUtils.releaseWakeLock()
}
}
部分设备要想后台也能常亮可能还需要显示一个悬浮窗(否则设备一直常亮失效);可以以下示例可跳过授权悬浮窗权限
private val layoutParams
get() = WindowManager.LayoutParams().apply {
type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
format = PixelFormat.TRANSLUCENT
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
gravity = Gravity.CENTER
}
private fun showFloat(){
//windowManager 添加View ,自己实现
}