Android正确的保活方案,不要掉进保活需求死循环陷进

22,176 阅读7分钟

在开始前,还是给大家简单介绍一下,以前出现过的一些黑科技:

大概在6年前Github中出现过一个叫MarsDaemon,这个库通过双进程守护的方式实现保活,一时间风头无两。好景不长,进入 Android 8.0时代之后,这个库就废掉了。

最近2年Github上面出来一个Leoric 感兴趣的可以去看一下源码,谁敢用在生产环境呢,也就自己玩玩的才会用吧(不能因为保活而导致手机卡巴斯基),我没有试过这个,我想说的是:黑科技能黑的了一时,能黑的了一世吗?

没有规矩,不成方圆,要提升产品的存活率,最终还是要落到产品本身上面来,尊重用户,提升用户体验才是正道。

以前我也是深受保活需求的压迫,最近发现QQ群里有人又提到了如何保活,那么我们就来说一说,如何来正确保活App?


Android 8.0之后: 加强了应用后台限制,当时测试过一组数据:

应用处于前台,启动一个前台Service,里面使用JobScheduler启动定时任务(30秒触发一次), 此时手机锁屏,前10分钟内,定时任务都是正常执行;
大概在12分钟左右,发现应用进程就被kill掉了,解锁屏幕,app也不在前台了;

各大国产手机厂商底层都经过自己魔改,自家都有自己的一套自启动管理,小米手机更乱(当时有个神隐模式的概念,那也是杀后台高手),只能说当时Android手机各种性能方面都不足,各家都会有自己的一套省电模式,以此来达到省电和提高手机性能,Android 系统变得越来越完善,但是厂商定制的自启动、省电模式还在,所以我们要做保活。

1.Android 8.0之前-常用的保活方案

1.开启一个前台Service
2.Android 6.0+ 忽略电池优化开关(稍后会有代码)
3.无障碍服务(只针对有用这个功能的app,如支付宝语音增强提醒用了它)


2.Android 8.0之后-常用的保活方案

1.开启一个前台Service(可以加上,单独启用的话无法满足保活需求)
2.Android 6.0+ 忽略电池优化开关(稍后会有代码)
3.无障碍服务(只针对有用这个功能的app,如支付宝语音增强提醒用了它)
4.应用自启动权限(最简单的方案是针对不同系统提供教程图片-让用户自己去打开)
5.多任务列表窗口加锁(提供GIF教程图片-让用户自己去打开)
6.多任务列表窗口隐藏App(仅针对有这方面需求的App)
7.应用后台高耗电(仅针对Vivo手机)

3.保活方案实现步骤

(1). 前台Service

针对target=34+的app,适配Android14+前台服务类型,我们需要增加如下权限配置:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

<service android:name=".service.ForegroundCoreService" android:foregroundServiceType="specialUse"/>
//前台服务
class ForegroundCoreService : Service() {
    override fun onBind(intent: Intent?): IBinder? = null
    private var mForegroundNF:ForegroundNF by lazy {
        ForegroundNF(this)
    }
     override fun onCreate() {
        super.onCreate()
        mForegroundNF.startForegroundNotification()
    }
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if(null == intent){
            //服务被系统kill掉之后重启进来的
            return START_NOT_STICKY
        }
        mForegroundNF.startForegroundNotification()
        return super.onStartCommand(intent, flags, startId)
    }
    override fun onDestroy() {
        mForegroundNF.stopForegroundNotification()
        super.onDestroy()
    }
}
//初始化前台通知,停止前台通知
class ForegroundNF(private val service: ForegroundCoreService) : ContextWrapper(service) {
    companion object {
        private const val START_ID = 101
        private const val CHANNEL_ID = "app_foreground_service"
        private const val CHANNEL_NAME = "前台保活服务"
    }
    private var mNotificationManager: NotificationManager? = null
    
    private var mCompatBuilder:NotificationCompat.Builder?=null
    
    private val compatBuilder: NotificationCompat.Builder?
            get() {
                if (mCompatBuilder == null) {
                    val notificationIntent = Intent(this, MainActivity::class.java)
                    notificationIntent.action = Intent.ACTION_MAIN
                    notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER)
                    notificationIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                    //动作意图
                    val pendingIntent = PendingIntent.getActivity(
                        this, (Math.random() * 10 + 10).toInt(),
                        notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT
                    )
                    val notificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder(this,CHANNEL_ID)
                    //标题
                    notificationBuilder.setContentTitle(getString(R.string.notification_content))
                    //通知内容
                    notificationBuilder.setContentText(getString(R.string.notification_sub_content))
                    //状态栏显示的小图标
                    notificationBuilder.setSmallIcon(R.mipmap.ic_coolback_launcher)
                    //通知内容打开的意图
                    notificationBuilder.setContentIntent(pendingIntent)
                    mCompatBuilder = notificationBuilder
                }
                return mCompatBuilder
            }
   
    init {
        createNotificationChannel()
    }
    
    //创建通知渠道
    private fun createNotificationChannel() {
        mNotificationManager =
            getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        //针对8.0+系统
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                CHANNEL_ID,
                CHANNEL_NAME,
                NotificationManager.IMPORTANCE_LOW
            )
            channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
            channel.setShowBadge(false)
            mNotificationManager?.createNotificationChannel(channel)
        }
    }
    
    //开启前台通知
    fun startForegroundNotification() {
        service.startForeground(START_ID, compatBuilder?.build())
    }
    
    //停止前台服务并清除通知
    fun stopForegroundNotification() {
        mNotificationManager?.cancelAll()
        service.stopForeground(true)
    }
}

(2).忽略电池优化(Android 6.0+)

1.我们需要在AndroidManifest.xml中声明一下权限

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

2.通过Intent来请求忽略电池优化的权限(需要引导用户点击)

//在Activity的onCreate中注册ActivityResult,一定要在onCreate中注册
//监听onActivityForResult回调
mIgnoreBatteryResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
            //查询是否开启成功
            if(queryBatteryOptimizeStatus()){
               //忽略电池优化开启成功
            }else{
               //开启失败
            }
        }

通过Intent打开忽略电池优化弹框:

val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
intent.data = Uri.parse("package:$packageName")
//启动忽略电池优化,会弹出一个系统的弹框,我们在上面的
launchActivityResult(intent)

查询是否成功开启忽略电池优化开关:

fun Context.queryBatteryOptimizeStatus():Boolean{
    val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager?
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        powerManager?.isIgnoringBatteryOptimizations(packageName)?:false
    } else {
        true
    }
}

(3).无障碍服务

看官方文档:创建自己的无障碍服务
它也是一个Service,它的优先级比较高,提供界面增强功能,初衷是帮助视觉障碍的用户或者是可能暂时无法与设备进行全面互动的用户完成操作。
可以做很多事情,使用了此Service,在6.0+不需要申请悬浮窗权限,直接使用WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY 挺方便的
(仅针对有需要此服务的app,可以开启增强后台保活)

(4).自启动权限(即:白名单管理列表页面)

是系统给用户自己去打开“自启动权限”开关的入口,我们需要针对不同的手机厂商和系统版本,弹出提示引导用户是否前去打开“自启动权限”
有的手机厂商叫:白名单管理,有的叫:自启动权限,两个是一个概念;
点击查看跳转到『手机自启动设置页面』完整代码

(需要注意:如果是代码控制跳转,无法保证永远可以调整,系统升级可能就给你屏蔽了, 最简单的方法是:显示一个如何找到自启动页面的引导图,下面以华为手机为例:)


华为手机-自启动管理

(5).多任务列表窗口加锁

可以针对不同手机厂商,显示引导用户,开启App窗口加锁之后,点击清理加速不会导致应用被kill


华为手机窗口加锁-教程图

(6).多任务列表窗口隐藏App窗口

刚刚上面多任务窗口加锁完,再提示用户去App里面把隐藏App窗口开关打开,这样用户就不会多任务列表里面把App窗口给手抖划掉

多任务窗口中『隐藏App窗口』,可以用如下代码控制:
(这个也只是针对有这方面需求App提供的一种增强方案罢了:因为隐藏了窗口,用户就不会去想他,不会去手痒去划掉它)

//在多任务列表页面隐藏App窗口
fun hideAppWindow(context: Context,isHide:Boolean){
        try {
            val activityManager: ActivityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
            //控制App的窗口是否在多任务列表显示
            activityManager.appTasks[0].setExcludeFromRecents(isHide)
        }catch (e:Exception){
            .....
        }
    }

(7).应用后台高耗电(Vivo手机独有)

开启的入口:“设置”>“电池”>“后台高耗电”>“找到xxxApp打开开关”


vivo允许后台高耗电

最后还是奉劝那些,仍然执着于找寻黑科技的开发者,醒醒吧,太阳晒屁股了。

如果说你的App用户群体不是普通用户,是专门给一些玩机大神们用的,都可以root手机的话,那么直接 move 到系统目录 priv/system/app 即可, 即使被用户强杀也会自动重新拉起。


往期文章推荐:
1.Jetpack Compose UI创建布局绘制流程+原理 —— 内含概念详解(满满干货)
2.Jetpack App Startup如何使用及原理分析
3.源码分析 | ThreadedRenderer空指针问题,顺便把Choreographer认识一下
4.源码分析 | 事件是怎么传递到Activity的?
5.聊聊CountDownLatch 源码
6.Jetpack Compose - Accompanist 组件库
7.Jetpack Compose - FloatingActionButton 展开/折叠 的多级悬浮菜单
8.Jetpack Compose - 实现Text跑马灯Marquee效果
9.如何禁用无障碍模拟点击,提高应用收益