最近在做一个埋点的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,startId: Int): Int {
- try {
- this.bindService(Intent(this@RemoteService,LocalService::class.java),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,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,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?,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,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,"$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,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,intent: Intent): Notification.Builder {
- //PendingIntent.FLAG_UPDATE_CURRENT 这个类型才能传值
- val pendingIntent = PendingIntent.getBroadcast(context,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,intent: Intent): NotificationCompat.Builder {
- val pendingIntent = PendingIntent.getBroadcast(context,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)
- .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浅谈