欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

详解Android 8.0以上系统应用如何保活

程序员文章站 2022-04-29 10:34:07
最近在做一个埋点的sdk,由于埋点是分批上传的,不是每次都上传,所以会有个进程保活的机制,这也是自研推送的实现技术之一:如何保证android进程的存活。 对于andro...

最近在做一个埋点的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浅谈

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。