最近在做一个埋点的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: BroadcastReceiveroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//设定一像素的activityval window = windowwindow.setGravity(Gravity.LEFT or Gravity.TOP)val params = window.attributesparams.x = 0params.y = 0params.height = 1params.width = 1window.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 PowerManagerval 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? = nullprivate var mBilder: MyBilder? = nulloverride 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)//声音设置为0mediaPlayer?.setVolume(0f, 0f)mediaPlayer?.isLooping = true//循环播放play()}//启用前台服务,提升优先级if (KeepLive.foregroundNotification != null) {val intent2 = Intent(applicationContext, NotificationClickReceiver::class.java)intent2.action = NotificationClickReceiver.CLICK_NOTIFICATIONval 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 &amp;&amp; !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 &amp;&amp; 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? = nulloverride 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_NOTIFICATIONval 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? = nulloverride fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {var startId = startIdstartService(this)if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {mJobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobSchedulerval 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_NOTIFICATIONval 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 = falseval activityManager = ctx.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManagerval 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? = nullprivate var id: String = context.packageName + "51"private var name: String = context.packageNameprivate var context: Context = contextprivate var channel: NotificationChannel? = nullcompanion object {@SuppressLint("StaticFieldLeak")private var notificationUtils: NotificationUtils? = nullfun createNotification(context: Context, title: String, content: String, icon: Int, intent: Intent): Notification? {if (notificationUtils == null) {notificationUtils = NotificationUtils(context)}var notification: Notification? = nullnotification = 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@Overridepublic 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浅谈

Android 8.0以上系统应用如何保活相关推荐

  1. Android 4.0.4系统下实现apk的静默安装和启动

    转自http://www.linuxidc.com/Linux/2013-02/79403.htm 未亲测 最近在Android 4.0.4系统下实现apk的静默安装和启动的功能,这里和大家分享一下, ...

  2. android 7原生动态壁纸,手机里都是定制OS?谷歌Android 7.0原生系统壁纸邀你来尝鲜!...

    原标题:手机里都是定制OS?谷歌Android 7.0原生系统壁纸邀你来尝鲜! 安卓系统的碎片化一直是被人们所诟病,苹果手机IOS系统升级的速度和占比一直都非常高,而安卓手机却相比都非常差,三星,华为 ...

  3. 小米刷 android o,一代神机!五年前的小米2刷入Android 8.0原生系统

    五年前的小米手机2竟然能刷Android 8.0原生系统.做个比较,2012年推出的iPhone 5已经不能升级最新的iOS11了. 小米2是小米于2012年8月发布的一款产品,距今已经超过5年零三个 ...

  4. tiny4412开发板实现uboot引导启动android 5.0.2系统

    1目的 实现uboot引导启动android 5.0.2系统. 2 现有环境 友善之臂tiny 4412开发板,superboot引导启动android5.0.2系统,内核版本为linux3.0.8. ...

  5. Android 9.0 显示系统导航栏(左/右/底部)

    Android 9.0 显示系统导航栏(左/右/底部) 显示系统导航栏 一般有:HOME  BACK  最近应用列表 等组成. 先看下效果图: 显示在底部(最常见的做法): 显示在右边(平板): (请 ...

  6. android 8.0 调系统拍照_Android通知栏微技巧,8.0系统中通知栏的适配

    为什么要进行通知栏适配? 不得不说,通知栏真是一个让人又爱又恨的东西. 通知栏是Android系统原创的一个功能,虽说乔布斯一直认为Android系统是彻彻底底抄袭iOS的一个产品,但是通知栏确实是A ...

  7. android4.0 菜单,Android 4.0.4系统曝光 增新Power菜单

    [IT168 资讯]尽管Android4.0.3已经推出很久了,但是目前为止,三星Galaxy Nexus运行的仍旧是Android4.0.2的系统更新,与4.0.3相比,4.0.2版本不仅应用界面不 ...

  8. Android 最新 ios 11,差异性越来越小!iOS 11/Android 8.0最新系统对比

    北京时间9月13日凌晨1点,苹果在他的新飞船总部举办新品发布会.会上除了发布了一系列的智能设备外,更是发布了iOS 11操作系统的正式版本.iOS 11版本在今年6月份的苹果开发者大会正式亮相,但是之 ...

  9. nexus+7+android+5.0++wifi+代理,二代Nexus 7获Android 5.0.2系统更新

    据外媒Android Police报道,谷歌日前已经正式开始推送Android 5.0.2更新,作为亲儿子的Nexus 7(2013)已经推送,Nexus 10的更新也即将到来 日前,据外媒Andro ...

最新文章

  1. 让我去健身的不是漂亮小姐姐,居然是贝叶斯统计!
  2. linux桌面天气,Ubuntu桌面美化:添加Gmail +天气预报插件[图文]
  3. oracle如何复制表的索引,Oracle表与索引管理
  4. 36--斐波那契数列
  5. splunk中 如何隐藏input_翻糖制作中,如何避免裂缝,如何隐藏裂缝,如何防粘?...
  6. 【今日CS 视觉论文速览】Part2, 16 Jan 2019
  7. java textfield方法,Java TextField求教育
  8. 手把手教你在友善之臂tiny4412上用uboot启动Linux内核
  9. 我能想到的圆角背景的实现方法
  10. php 多级分成手机版,PHP 层级菜单数组处理,由一级数组转换为多级数组的递归实现...
  11. EIGRP路由协议实现网络互联
  12. 计算机管理磁盘分区,一分钟搞定电脑磁盘分区,再也不求人!
  13. SQL防注入大全——史上最全的 SQL 注入资料
  14. 源代码静态检测分析技术浅析
  15. 下载并安装windows版本的Redis
  16. 基于FPGA扰码的实现
  17. 海信电视云账号连不上服务器,海信云账号如何使用?图文教程详解
  18. A、B、H、S、N股 各是什么意思
  19. i710700和i510400f哪个好
  20. GF无菌动物实验室设计SICOLAB

热门文章

  1. “跳槽”一词的来历竟然是这样!汗
  2. Php 波场离线签名 Tron离线签名
  3. NOTA-WL12,68Ga-NOTA-WL12一种基于肽的正电子发射断层扫描 (PET) 显像剂
  4. Tomcat中如何配置使用APR
  5. tpl文件如何导入ps?tpl文件笔刷怎么安装?
  6. HM不只是到处开实体店了,这次它不想错过天猫新零售
  7. 零基础如何学习java,要学多久?
  8. EMC首款企业级SaaS产品亮相 产品策略初见端倪
  9. SQL server 变量、运算符
  10. 【软考】系统集成项目管理工程师(三)系统集成专业技术知识