无障碍权限另外的作用之-Android应用保活

无障碍权限是高危权限,前提是能很好的引导用户进行授权。此方案即使用户手动杀死App,该App的无障碍服务依旧幸存


前言

随着目前Android 生态的逐渐完善,以前的保活方案例如 后台无声音乐、双守护进程、1像素Activity、leoric(https://github.com/tiann/Leoric,当然还有就是ioctl方式(https://github.com/Pangu-Immortal/KeepAlivePerfect)也可以实现(经验证大部分Android 14+的设备并不能有效保活)都逐步失效,毕竟这些方案终究是偏门,考虑到长久可靠的方案;这里给大家介绍下用Android 原生无障碍服务维持应用长久保活的方式。

一、方案必要条件

1、授权无障碍权限

无障碍权限另外的作用之-Android应用保活_第1张图片

  找到对应的App进行授权

无障碍权限另外的作用之-Android应用保活_第2张图片

2、启动一个前台服务,业务逻辑可以在该前台服务内进行,授权无障碍权限后,即使应用没有任何Activity了,该服务也能正常运行,当然也可以直接在无障碍服务里边处理业务逻辑,但是用户难以感知该应用还活着(前台服务可以设置通知栏显示应用状态)

二、具体步骤

1.配置无障碍服务 

         
            
                
            

            
        

2.编写无障碍服务

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) {
                }
            }
        )
    }
}

3.获取无障碍服务状态

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 ,自己实现
}
 
  

你可能感兴趣的:(android)