详解Android 8.0以上系统应用如何保活
最近在做一个埋点的sdk,由于埋点是分批上传的,不是每次都上传,所以会有个进程保活的机制,这也是自研推送的实现技术之一:如何保证android进程的存活。
对于android来说,保活主要有以下一些方法:
- 开启前台service(效果好,推荐)
- service中循环播放一段无声音频(效果较好,但耗电量高,谨慎使用)
- 双进程守护(android 5.0前有效)
- jobscheduler(android 5.0后引入,8.0后失效)
- 1 像素activity保活方案(不推荐)
- 广播锁屏、自定义锁屏(不推荐)
- 第三方推送sdk唤醒(效果好,缺点是第三方接入)
下面是具体的实现方案:
1.监听锁屏广播,开启1个像素的activity
最早见到这种方案的时候是2015年,有个fm的app为了向投资人展示月活,在android应用中开启一个1像素的activity。
由于activity的级别是比较高的,所以开启1个像素的activity的方式就可以保证进程是不容易被杀掉的。
具体来说,定义一个1像素的activity,在该activity中动态注册自定义的广播。
class onepixelactivity : appcompatactivity() { private lateinit var br: broadcastreceiver override fun oncreate(savedinstancestate: bundle?) { super.oncreate(savedinstancestate) //设定一像素的activity val window = window window.setgravity(gravity.left or gravity.top) val params = window.attributes params.x = 0 params.y = 0 params.height = 1 params.width = 1 window.attributes = params //在一像素activity里注册广播接受者 接受到广播结束掉一像素 br = object : broadcastreceiver() { override fun onreceive(context: context, intent: intent) { finish() } } registerreceiver(br, intentfilter("finish activity")) checkscreenon() } override fun onresume() { super.onresume() checkscreenon() } override fun ondestroy() { try { //销毁的时候解锁广播 unregisterreceiver(br) } catch (e: illegalargumentexception) { } super.ondestroy() } /** * 检查屏幕是否点亮 */ private fun checkscreenon() { val pm = this@onepixelactivity.getsystemservice(context.power_service) as powermanager val isscreenon = if (build.version.sdk_int >= build.version_codes.kitkat_watch) { pm.isinteractive } else { pm.isscreenon } if (isscreenon) { finish() } } }
2, 双进程守护
双进程守护,在android 5.0前是有效的,5.0之后就不行了。首先,我们定义定义一个本地服务,在该服务中播放无声音乐,并绑定远程服务
class localservice : service() { private var mediaplayer: mediaplayer? = null private var mbilder: mybilder? = null override fun oncreate() { super.oncreate() if (mbilder == null) { mbilder = mybilder() } } override fun onbind(intent: intent): ibinder? { return mbilder } override fun onstartcommand(intent: intent, flags: int, startid: int): int { //播放无声音乐 if (mediaplayer == null) { mediaplayer = mediaplayer.create(this, r.raw.novioce) //声音设置为0 mediaplayer?.setvolume(0f, 0f) mediaplayer?.islooping = true//循环播放 play() } //启用前台服务,提升优先级 if (keeplive.foregroundnotification != null) { val intent2 = intent(applicationcontext, notificationclickreceiver::class.java) intent2.action = notificationclickreceiver.click_notification val notification = notificationutils.createnotification(this, keeplive.foregroundnotification!!.gettitle(), keeplive.foregroundnotification!!.getdescription(), keeplive.foregroundnotification!!.geticonres(), intent2) startforeground(13691, notification) } //绑定守护进程 try { val intent3 = intent(this, remoteservice::class.java) this.bindservice(intent3, connection, context.bind_above_client) } catch (e: exception) { } //隐藏服务通知 try { if (build.version.sdk_int < 25) { startservice(intent(this, hideforegroundservice::class.java)) } } catch (e: exception) { } if (keeplive.keepliveservice != null) { keeplive.keepliveservice!!.onworking() } return service.start_sticky } private fun play() { if (mediaplayer != null && !mediaplayer!!.isplaying) { mediaplayer?.start() } } private inner class mybilder : guardaidl.stub() { @throws(remoteexception::class) override fun wakeup(title: string, discription: string, iconres: int) { } } private val connection = object : serviceconnection { override fun onservicedisconnected(name: componentname) { val remoteservice = intent(this@localservice, remoteservice::class.java) this@localservice.startservice(remoteservice) val intent = intent(this@localservice, remoteservice::class.java) this@localservice.bindservice(intent, this, context.bind_above_client) } override fun onserviceconnected(name: componentname, service: ibinder) { try { if (mbilder != null && keeplive.foregroundnotification != null) { val guardaidl = guardaidl.stub.asinterface(service) guardaidl.wakeup(keeplive.foregroundnotification?.gettitle(), keeplive.foregroundnotification?.getdescription(), keeplive.foregroundnotification!!.geticonres()) } } catch (e: remoteexception) { e.printstacktrace() } } } override fun ondestroy() { super.ondestroy() unbindservice(connection) if (keeplive.keepliveservice != null) { keeplive.keepliveservice?.onstop() } } }
然后再定义一个远程服务,绑定本地服务。
class remoteservice : service() { private var mbilder: mybilder? = null override fun oncreate() { super.oncreate() if (mbilder == null) { mbilder = mybilder() } } override fun onbind(intent: intent): ibinder? { return mbilder } override fun onstartcommand(intent: intent, flags: int, startid: int): int { try { this.bindservice(intent(this@remoteservice, localservice::class.java), connection, context.bind_above_client) } catch (e: exception) { } return service.start_sticky } override fun ondestroy() { super.ondestroy() unbindservice(connection) } private inner class mybilder : guardaidl.stub() { @throws(remoteexception::class) override fun wakeup(title: string, discription: string, iconres: int) { if (build.version.sdk_int < 25) { val intent = intent(applicationcontext, notificationclickreceiver::class.java) intent.action = notificationclickreceiver.click_notification val notification = notificationutils.createnotification(this@remoteservice, title, discription, iconres, intent) this@remoteservice.startforeground(13691, notification) } } } private val connection = object : serviceconnection { override fun onservicedisconnected(name: componentname) { val remoteservice = intent(this@remoteservice, localservice::class.java) this@remoteservice.startservice(remoteservice) this@remoteservice.bindservice(intent(this@remoteservice, localservice::class.java), this, context.bind_above_client) } override fun onserviceconnected(name: componentname, service: ibinder) {} } } /** * 通知栏点击广播接受者 */ class notificationclickreceiver : broadcastreceiver() { companion object { const val click_notification = "click_notification" } override fun onreceive(context: context, intent: intent) { if (intent.action == notificationclickreceiver.click_notification) { if (keeplive.foregroundnotification != null) { if (keeplive.foregroundnotification!!.getforegroundnotificationclicklistener() != null) { keeplive.foregroundnotification!!.getforegroundnotificationclicklistener()?.foregroundnotificationclick(context, intent) } } } } }
3,jobscheduler
jobscheduler是android从5.0增加的支持一种特殊的任务调度机制,可以用它来实现进程保活,不过在android8.0系统中,此种方法也失效。
首先,我们定义一个jobservice,开启本地服务和远程服务。
@suppresswarnings(value = ["unchecked", "deprecation"]) @requiresapi(build.version_codes.lollipop) class jobhandlerservice : jobservice() { private var mjobscheduler: jobscheduler? = null override fun onstartcommand(intent: intent?, flags: int, startid: int): int { var startid = startid startservice(this) if (build.version.sdk_int >= build.version_codes.lollipop) { mjobscheduler = getsystemservice(context.job_scheduler_service) as jobscheduler val builder = jobinfo.builder(startid++, componentname(packagename, jobhandlerservice::class.java.name)) if (build.version.sdk_int >= 24) { builder.setminimumlatency(jobinfo.default_initial_backoff_millis) //执行的最小延迟时间 builder.setoverridedeadline(jobinfo.default_initial_backoff_millis) //执行的最长延时时间 builder.setminimumlatency(jobinfo.default_initial_backoff_millis) builder.setbackoffcriteria(jobinfo.default_initial_backoff_millis, jobinfo.backoff_policy_linear)//线性重试方案 } else { builder.setperiodic(jobinfo.default_initial_backoff_millis) } builder.setrequirednetworktype(jobinfo.network_type_any) builder.setrequirescharging(true) // 当插入充电器,执行该任务 mjobscheduler?.schedule(builder.build()) } return service.start_sticky } private fun startservice(context: context) { if (build.version.sdk_int >= build.version_codes.o) { if (keeplive.foregroundnotification != null) { val intent = intent(applicationcontext, notificationclickreceiver::class.java) intent.action = notificationclickreceiver.click_notification val notification = notificationutils.createnotification(this, keeplive.foregroundnotification!!.gettitle(), keeplive.foregroundnotification!!.getdescription(), keeplive.foregroundnotification!!.geticonres(), intent) startforeground(13691, notification) } } //启动本地服务 val localintent = intent(context, localservice::class.java) //启动守护进程 val guardintent = intent(context, remoteservice::class.java) startservice(localintent) startservice(guardintent) } override fun onstartjob(jobparameters: jobparameters): boolean { if (!isservicerunning(applicationcontext, "com.xiyang51.keeplive.service.localservice") || !isservicerunning(applicationcontext, "$packagename:remote")) { startservice(this) } return false } override fun onstopjob(jobparameters: jobparameters): boolean { if (!isservicerunning(applicationcontext, "com.xiyang51.keeplive.service.localservice") || !isservicerunning(applicationcontext, "$packagename:remote")) { startservice(this) } return false } private fun isservicerunning(ctx: context, classname: string): boolean { var isrunning = false val activitymanager = ctx .getsystemservice(context.activity_service) as activitymanager val serviceslist = activitymanager .getrunningservices(integer.max_value) val l = serviceslist.iterator() while (l.hasnext()) { val si = l.next() if (classname == si.service.classname) { isrunning = true } } return isrunning } }
4,提高service优先级
在onstartcommand()方法中开启一个通知,提高进程的优先级。注意:从android 8.0(api级别26)开始,所有通知必须要分配一个渠道,对于每个渠道,可以单独设置视觉和听觉行为。然后用户可以在设置中修改这些设置,根据应用程序来决定哪些通知可以显示或者隐藏。
首先,定义一个通知工具类,此工具栏兼容android 8.0。
class notificationutils(context: context) : contextwrapper(context) { private var manager: notificationmanager? = null private var id: string = context.packagename + "51" private var name: string = context.packagename private var context: context = context private var channel: notificationchannel? = null companion object { @suppresslint("staticfieldleak") private var notificationutils: notificationutils? = null fun createnotification(context: context, title: string, content: string, icon: int, intent: intent): notification? { if (notificationutils == null) { notificationutils = notificationutils(context) } var notification: notification? = null notification = if (build.version.sdk_int >= 26) { notificationutils?.createnotificationchannel() notificationutils?.getchannelnotification(title, content, icon, intent)?.build() } else { notificationutils?.getnotification_25(title, content, icon, intent)?.build() } return notification } } @requiresapi(api = build.version_codes.o) fun createnotificationchannel() { if (channel == null) { channel = notificationchannel(id, name, notificationmanager.importance_min) channel?.enablelights(false) channel?.enablevibration(false) channel?.vibrationpattern = longarrayof(0) channel?.setsound(null, null) getmanager().createnotificationchannel(channel) } } private fun getmanager(): notificationmanager { if (manager == null) { manager = getsystemservice(context.notification_service) as notificationmanager } return manager!! } @requiresapi(api = build.version_codes.o) fun getchannelnotification(title: string, content: string, icon: int, intent: intent): notification.builder { //pendingintent.flag_update_current 这个类型才能传值 val pendingintent = pendingintent.getbroadcast(context, 0, intent, pendingintent.flag_update_current) return notification.builder(context, id) .setcontenttitle(title) .setcontenttext(content) .setsmallicon(icon) .setautocancel(true) .setcontentintent(pendingintent) } fun getnotification_25(title: string, content: string, icon: int, intent: intent): notificationcompat.builder { val pendingintent = pendingintent.getbroadcast(context, 0, intent, pendingintent.flag_update_current) return notificationcompat.builder(context, id) .setcontenttitle(title) .setcontenttext(content) .setsmallicon(icon) .setautocancel(true) .setvibrate(longarrayof(0)) .setsound(null) .setlights(0, 0, 0) .setcontentintent(pendingintent) } }
5,workmanager方式
workmanager是android jetpac中的一个api,借助workmanager,我们可以用它来实现应用饿保活。使用前,我们需要依赖workmanager库,如下:
implementation "android.arch.work:work-runtime:1.0.0-alpha06"
worker是一个抽象类,用来指定需要执行的具体任务。
public class keeplivework extends worker { private static final string tag = "keeplivework"; @nonnull @override public workerresult dowork() { log.d(tag, "keep-> dowork: startkeepservice"); //启动job服务 startjobservice(); //启动相互绑定的服务 startkeepservice(); return workerresult.success; } }
然后,启动keepwork方法,
public void startkeepwork() { workmanager.getinstance().cancelallworkbytag(tag_keep_work); log.d(tag, "keep-> dowork startkeepwork"); onetimeworkrequest onetimeworkrequest = new onetimeworkrequest.builder(keeplivework.class) .setbackoffcriteria(backoffpolicy.linear, 5, timeunit.seconds) .addtag(tag_keep_work) .build(); workmanager.getinstance().enqueue(onetimeworkrequest); }
关于workmanager,可以通过下面的文章来详细了解:workmanager浅谈
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。