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() } } }
弹出窗口:
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
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
.