KeepLiveDemo[浅谈8.0时代的保活]

1. 前言

随着android8.0时代的普及, 不同手机都对app在后台的进驻有自己的一套算法. 原有的app保活机制, 很容易被系统的管理机制给杀死. 这其实也是对市场滥用后台的一种抵制. 个人不喜欢这种必须要让自己的产品停驻在后台的需求, 可是有的时候产品的需求甩过来的时候,我们作为开发者, 应该想的是怎样去实现, 然后再喷. 这篇文章就以一个demo, 谈谈保活的一些看法. 这个demo是用kotlin写的, 主要是练习练习kotlin的使用. 有不足的, 欢迎指正. 

2. 正文

    1) android 6.0以上, 有个电池优化的机制,如图所示, 这个主要的作用是, 减少在电池休眠的时候杀死进程的概率, 弹出提示就是提示用户去忽略电池优化. 代码如下:

/**去设置电池忽略优化**/
private val REQUEST_IGNORE_BATTERY_CODE = 1001
private fun gotoSettingIgnoringBatteryOptimizations(context: Activity) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        try {
            val intent = Intent()
            val packageName = context.packageName
            intent.action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
            intent.data = Uri.parse("package:$packageName")
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            context.startActivityForResult(intent, REQUEST_IGNORE_BATTERY_CODE)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

弹出窗口:

KeepLiveDemo[浅谈8.0时代的保活]_第1张图片

    2) 双进程守护: 通过aidl 绑定一个service, 实现双进程守护. 

inner class ProgressAidlBinder(name:String): IProgressAidlInterface.Stub() {
    var name = name
    override fun getServiceName(): String {
        return name
    }
}

    3) 用AlarmManager实现定时唤醒任务: 实现服务在超过一定时间后重启, 现在是立即重启, 后期迭代版本会让用户自定义选择重启时间的间隔. 

private fun restartDelay() {
    val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
    val alarmIntent = Intent(this, MainOService::class.java)
    alarmIntent.action = WakeReceiver.GRAY_WAKE_ACTION
    val operation = PendingIntent.getBroadcast(this, WAKE_REQUEST_CODE, alarmIntent
            , PendingIntent.FLAG_UPDATE_CURRENT)
    alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
            , ALARM_INTERVAL, operation)
}

    4) remote进程绑定, 这个就比较简单了, 通过与其他service绑定, 实现两个service同时守护.

internal inner class Myconn : ServiceConnection {
    override fun onServiceDisconnected(componentName: ComponentName) {
        Log.i("zune:", "远程服务进程被杀死")
        AlarmSendUtil.instanse.sendCallLiveBroadcast(applicationContext, 0)
        try {
            MainService.start(applicationContext)
            MainService.bind(applicationContext, this@RemoteService.conn!!)
        } catch (e: Exception) {
            Log.i("zune: ", "不允许后台启动 e = $e")
        }
    }

    override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
        Log.i("zune:", "连接远程服务进程成功")
    }
}

    5) 跳转到app白名单列表, 让用户自己选择.这是android 7.0后出来的新功能,  这个是我认为最有效,也是最符合市场的一个选择. 进程常驻不常驻, 用户说了算. 这个只能由隐示意图跳转的方式, 跳到白名单列表, 可是有的手机厂商为了和某些app合作, 强制白名单, 且不公开白名单列表, 所以只能跳转到白名单的上一级, 由用户自己去选择是否要加入白名单. 

/**进入白名单列表上一层级**/
private fun enterSetting(activity: Context) {
    try {
        val intent = Intent()
        intent.action = "com.android.settings.action.SETTINGS"
        intent.addCategory("com.android.settings.category")
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        intent.`package` = "com.android.settings"
        intent.setClassName("com.android.settings"
                , "com.android.settings.Settings\$PowerUsageSummaryActivity")
        activity.startActivity(intent)
        SPUtils.getInstance().put("enter", true)
    } catch (e:Exception) {
        println("enterSetting e = $e")
    }
}

    6) 循环播放一段空白音乐, 这个据说是咕咚里面这样用来保活的. 但是开启之后耗电量特别大, 为了优化, 我把播放音乐在service走onDestroy的时候, 或者划掉应用卡片的回调里面加上了, 在start里面结束播放. 

/**
 * 最近任务列表中划掉卡片时回调
 */
override fun onTaskRemoved(rootIntent: Intent) {
    onEnd(rootIntent)
}
 
  
protected open fun onEnd(rootIntent: Intent?) {
    ....
    if (ConstantsConfig.musicToggle) {
        Thread(Runnable {
            startPlayMusic()
        }).start()
    }
    ....
}

    7) 一个像素点Activity, 配合锁屏开屏, 提高进程优先级. 这个是暂时强制开启的, 开关控制的逻辑还未调试., 后期版本迭代的时候会更新. 

private fun registScreen() {
    if (listener == null) {
        listener = object : ScreenBroadcastListener.ScreenStateListener {
            override fun onScreenOn() {
                println("zune: 开屏 开启activity")
                KeepLiveHelper.screenOn = true
                OnePointActivity.start(this@BaseOService, true)
            }

            override fun onScreenOff() {
                println("zune: 锁屏 开启activity")
                KeepLiveHelper.screenOn = false
                OnePointActivity.start(this@BaseOService)
            }
        }
    }
    ScreenBroadcastListener.getInstance(this)
            .registerListener(listener!!)
}

    8) 开启前台服务, 前台服务开启的时候 7.0 以上api有所调整

    

@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
fun sendNotification(){
    if (Build.VERSION.SDK_INT >= O) {
        createNotificationChannel()
        val notification = getChannelNotification()!!.build()
        notification.flags = Notification.FLAG_NO_CLEAR
        getManager().notify(1, notification)
    } else {
        val notification = getNotification_25().build()
        getManager().notify(1, notification)
    }
}

fun sendNotification(service: Service) {
    val intent = Intent(service, OnePointActivity::class.java)
    val pi = PendingIntent.getActivity(service, 0, intent, 0)
    val notification = NotificationCompat.Builder(service)
            .setContentTitle(notification.title)
            .setContentText(notification.content)
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(notification.resId)
            .setLargeIcon(BitmapFactory.decodeResource(service.resources, notification.resId))
            .setContentIntent(pi)
            .build()
    service.startForeground(1, notification)
}

    9) 加入了小米推送, 因为该sdk不大, 并且在miui上有很好的保活效果, 于是引入. 如果感兴趣还可引入华为推送, 友盟推送, 个推等api, 因为这些推送平台都有相互唤醒的机制, 会有很好的保活效果, 但是要考虑app大小.

    MiPushClient.registerPush(context, PUSH_APP_ID, PUSH_APP_KEY)
    MiPushClient.enablePush(context)
    MiPushClient.setAlias(context, "keeplive", null)

    10) 适配7.0的jobIntentService, 不懂请百度之. 在7.0以上使用的JobIntentService, 因为7.0以上不再支持service中开启service, 只能借助于任务调度的形式做个简单处理. 

fun start(context: Context) {
    val intent = Intent()
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    enqueueWork(context, MainOService::class.java, JOB_ID, intent)
}    

     11) daemon_service第三方依赖, 由于我是在lib中写的控制逻辑, 引入的时候有问题, 所以这个第三方库暂不引入, 后期如有时间会全部搬过来.

3. 使用

使用很简单, github地址为 https://github.com/leigong2/KeepLiveDemo

    1)首先在项目的gradle文件中添加maven

  1. allprojects {

     repositories {
    
     	...
    
     	maven { url 'https://www.jitpack.io' }
    
     }
    

                }

    2) 其次在工程中, 添加依赖:

                dependencies {

                 implementation 'com.github.leigong2:KeepLiveDemo:v0.8.8'

                }

    3) 在MainApp中, 初始化

KeepLiveHelper.getDefault().init(this, PUSH_APP_ID, PUSH_APP_KEY)
NotificationUtils().setNotification("测试变化标题", "测试变化内容", R.mipmap.ic_launcher)
if (android.os.Build.VERSION.SDK_INT >= N) {
    KeepLiveHelper.getDefault().bindService(BindOService::class.java)
} else {
    KeepLiveHelper.getDefault().bindService(BindService::class.java)
}

    记得要在内存释放时, 解除EventBus注册

override fun onTerminate() {
    super.onTerminate()
    KeepLiveHelper.getDefault().onTerminate()
}

    4)  在任意一个Activity中初始化全局开关, 记得要在onDestroy中调用onActivityRelease

fun initToggle() {
    ConstantsConfig.powToggle = SPUtils.getInstance().getBoolean("power_lay")
    ConstantsConfig.aidlToggle = SPUtils.getInstance().getBoolean("two_process")
    ConstantsConfig.alarmToggle = SPUtils.getInstance().getBoolean("alarm")
    ConstantsConfig.remoteToggle = SPUtils.getInstance().getBoolean("remote")
    ConstantsConfig.whiteToggle = SPUtils.getInstance().getBoolean("white_list")
    ConstantsConfig.musicToggle = SPUtils.getInstance().getBoolean("music")
    ConstantsConfig.onepointToggle = SPUtils.getInstance().getBoolean("one_point")
    ConstantsConfig.forceToggle = SPUtils.getInstance().getBoolean("force_service")
    ConstantsConfig.pushToggle = SPUtils.getInstance().getBoolean("push_service")
    ConstantsConfig.screenToggle = SPUtils.getInstance().getBoolean("screen_service")
    ConstantsConfig.daemonToggle = SPUtils.getInstance().getBoolean("daemon_service")
    ConstantsConfig.nToggle = SPUtils.getInstance().getBoolean("for_n")
}

4.说明

    我是用kotlin写在lib中的, 如果强行引入到主工程中, 不要让它调用其他函数, 因为java调用kotlin无缝接入, kotlin调用java会有各种各样问题, 特别是牵涉到空指针和数据类型的时候. 如果想对其做大的修改的话, 请放入主工程试试. 另外, 有bug请反馈[email protected], 或在下方留言, 谢谢! 没积分了, 有哪位大佬积分比较多, 打赏一两个嘛! 

下载地址: https://download.csdn.net/download/leigong2/10486175


.

你可能感兴趣的:(开发demo)