详解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浅谈

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

(0)

相关推荐

  • Android应用保活实践详解

    最近在做的项目中需要app在后台常驻,用于实时上传一些健康信息数据,便于后台实时查看用户的健康状况.自从Android7.0以上后台常驻实现越来越难,尤其是8.0及以上.关于保活的文章比比皆是,但是效果并不理想,关于保活的方法也就常说的哪几种,重点在于怎么组合运用.最终实现效果为:用户不主动强制杀死的话,能够一直存活(小米,华为,vivo,oppo,三星).其中三星s8,华为nova2s用户强制杀死也能存活. 项目结构 常见的保活方案 关于Android应用保活的文章很多,这里不再阐述,可自行百

  • 详解Android进程保活的方法

    关于 Android 平台的进程保活这一块,想必是所有 Android 开发者瞩目的内容之一.你到网上搜 Android 进程保活,可以搜出各种各样神乎其技的做法,绝大多数都是极其不靠谱.前段时间,Github还出现了一个很火的"黑科技"进程保活库,声称可以做到进程永生不死. 怀着学习和膜拜的心情进去Github围观,结果发现很多人提了 Issue 说各种各样的机子无法成功保活. 看到这里,我瞬间就放心了.坦白的讲,我是真心不希望有这种黑科技存在的,它只会滋生更多的流氓应用,拖垮我大

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

    最近在做一个埋点的sdk,由于埋点是分批上传的,不是每次都上传,所以会有个进程保活的机制,这也是自研推送的实现技术之一:如何保证Android进程的存活. 对于Android来说,保活主要有以下一些方法: 开启前台Service(效果好,推荐) Service中循环播放一段无声音频(效果较好,但耗电量高,谨慎使用) 双进程守护(Android 5.0前有效) JobScheduler(Android 5.0后引入,8.0后失效) 1 像素activity保活方案(不推荐) 广播锁屏.自定义锁屏(

  • 详解Android 7.0 Settings 加载选项

    先写在前面,这说的Settings加载选项是指Settings这个应用显示在主界面的选项,这个修改需要对系统源码进行修改. Android 7.0 Settings顶部多了一个建议选项,多了个侧边栏,操作更加便捷了.       原生7.0主界面                                                          原生7.0侧边栏 Android 6.0 之前做Android 6.0开发的,都会了解到6.0的Settings加载选项是通过加载dash

  • 详解Android获得系统GPU参数 gl.glGetString

    详解Android获得系统GPU参数 gl.glGetString 通过文档的查找,以及源码的剖析,Android的GPU信息需要通过OpenGL来获取,android framework层提供GL10来获取相应的参数,而GL10要在使用自定义的View时才可以获得,下面是获得GPU信息的例子: 1.实现Render类 class DemoRenderer implements GLSurfaceView.Renderer { public void onSurfaceCreated(GL10

  • 详解Android中PopupWindow在7.0后适配的解决

    本文介绍了详解Android中PopupWindow在7.0后适配的解决,分享给大家,具体如下: 这里主要记录一次踩坑的经历. 需求:如上图左侧效果,想在按钮的下方弹一个PopupWindow.嗯,很简单一个效果,然当适配7.0后发现这个PopupWindow显示异常,然后网上找到了下面这种方案. 7.0适配方案(但7.1又复现了) // 将popupWindow显示在anchor下方 public void showAsDropDown(PopupWindow popupWindow, Vie

  • Android+OpenCV4.2.0环境配置详解(Android studio)

    仅是个人记录,希望能对有需要的给予一些小小的帮助 首先我们肯定是要去到OpenCV的官网下载对应的SDK,并解压得到文件夹(opencv-4.2.0-android-sdk) 其次是NDK环境搭建(双击shift,输入sdk,找到sdk manager,将下面红色框框勾选安装) 创建项目,我选用的是(并不是只有这一选择) 导入Module File->New->Import Module 路径选择**\opencv-4.2.0-android-sdk\OpenCV-android-sdk\sd

  • 详解Android中Intent对象与Intent Filter过滤匹配过程

    如果对Intent不是特别了解,可以参见博文<详解Android中Intent的使用方法>,该文对本文要使用的action.category以及data都进行了详细介绍.如果想了解在开发中常见Intent的使用,可以参见<Android中Intent习惯用法>. 本文内容有点长,希望大家可以耐心读完. 本文在描述组件在manifest中注册的Intent Filter过滤器时,统一用intent-filter表示. 一.概述 我们知道,Intent是分两种的:显式Intent和隐式

  • 详解Android中图片的三级缓存及实例

    详解Android中图片的三级缓存及实例 为什么要使用三级缓存 如今的 Android App 经常会需要网络交互,通过网络获取图片是再正常不过的事了 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量.在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响 特别是,当我们想要重复浏览一些图片时,如果每一次浏览都需要通过网络获取,流量的浪费可想而知 所以提出三级缓存策略,通过网络.本地.内存三级缓存图片,来减少不必要的网络交互,避免浪费流量

  • 详解Android中的Service

    Service简介: Service是被设计用来在后台执行一些需要长时间运行的操作. Android由于允许Service在后台运行,甚至在结束Activity后,因此相对来说,Service相比Activity拥有更高的优先级. 创建Service: 要创建一个最基本的Service,需要完成以下工作:1)创建一个Java类,并让其继承Service 2)重写onCreate()和onBind()方法 其中,onCreate()方法是当该Service被创建时执行的方法,onBind()是该S

  • 详解Android 检测权限的三种写法

    本文介绍了详解Android 检测权限的三种写法,小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 权限检测生效条件: targetSdkVersion 以及 compileSdkVersion 升级到 23 及以上 运行 Android 系统 6.0 及以上 三种检测权限写法: public static boolean checkPermission1(Context context, String[] permissions) { PackageManager p

  • 详解Android观察者模式的使用与优劣

    一.简介 观察者模式(又被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己.该模式一个重要作用就是解耦,将被观察者和观察者进行解耦,使他们之间的依赖性更小 二.使用场景 关联行为场景,需要注意的是关联行为是可拆分的而不是"组合"关系 事件多级触发场景 跨系统的消息交换场景,如消息队列.事件总线的处理机制 三.简单实

随机推荐