详解Android 8.0以上系统应用如何保活
最近在做一个埋点的sdk,由于埋点是分批上传的,不是每次都上传,所以会有个进程保活的机制,这也是自研推送的实现技术之一:如何保证Android进程的存活。
对于Android来说,保活主要有以下一些方法:
- 开启前台Service(效果好,推荐)
- Service中循环播放一段无声音频(效果较好,但耗电量高,谨慎使用)
- 双进程守护(Android5.0前有效)
- JobScheduler(Android5.0后引入,8.0后失效)
- 1像素activity保活方案(不推荐)
- 广播锁屏、自定义锁屏(不推荐)
- 第三方推送SDK唤醒(效果好,缺点是第三方接入)
下面是具体的实现方案:
1.监听锁屏广播,开启1个像素的Activity
最早见到这种方案的时候是2015年,有个FM的app为了向投资人展示月活,在Android应用中开启一个1像素的Activity。
由于Activity的级别是比较高的,所以开启1个像素的Activity的方式就可以保证进程是不容易被杀掉的。
具体来说,定义一个1像素的Activity,在该Activity中动态注册自定义的广播。
classOnePixelActivity:AppCompatActivity(){ privatelateinitvarbr:BroadcastReceiver overridefunonCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) //设定一像素的activity valwindow=window window.setGravity(Gravity.LEFTorGravity.TOP) valparams=window.attributes params.x=0 params.y=0 params.height=1 params.width=1 window.attributes=params //在一像素activity里注册广播接受者接受到广播结束掉一像素 br=object:BroadcastReceiver(){ overridefunonReceive(context:Context,intent:Intent){ finish() } } registerReceiver(br,IntentFilter("finishactivity")) checkScreenOn() } overridefunonResume(){ super.onResume() checkScreenOn() } overridefunonDestroy(){ try{ //销毁的时候解锁广播 unregisterReceiver(br) }catch(e:IllegalArgumentException){ } super.onDestroy() } /** *检查屏幕是否点亮 */ privatefuncheckScreenOn(){ valpm=this@OnePixelActivity.getSystemService(Context.POWER_SERVICE)asPowerManager valisScreenOn=if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT_WATCH){ pm.isInteractive }else{ pm.isScreenOn } if(isScreenOn){ finish() } } }
2,双进程守护
双进程守护,在Android5.0前是有效的,5.0之后就不行了。首先,我们定义定义一个本地服务,在该服务中播放无声音乐,并绑定远程服务
classLocalService:Service(){ privatevarmediaPlayer:MediaPlayer?=null privatevarmBilder:MyBilder?=null overridefunonCreate(){ super.onCreate() if(mBilder==null){ mBilder=MyBilder() } } overridefunonBind(intent:Intent):IBinder?{ returnmBilder } overridefunonStartCommand(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){ valintent2=Intent(applicationContext,NotificationClickReceiver::class.java) intent2.action=NotificationClickReceiver.CLICK_NOTIFICATION valnotification=NotificationUtils.createNotification(this,KeepLive.foregroundNotification!!.getTitle(),KeepLive.foregroundNotification!!.getDescription(),KeepLive.foregroundNotification!!.getIconRes(),intent2) startForeground(13691,notification) } //绑定守护进程 try{ valintent3=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() } returnService.START_STICKY } privatefunplay(){ if(mediaPlayer!=null&&!mediaPlayer!!.isPlaying){ mediaPlayer?.start() } } privateinnerclassMyBilder:GuardAidl.Stub(){ @Throws(RemoteException::class) overridefunwakeUp(title:String,discription:String,iconRes:Int){ } } privatevalconnection=object:ServiceConnection{ overridefunonServiceDisconnected(name:ComponentName){ valremoteService=Intent(this@LocalService, RemoteService::class.java) this@LocalService.startService(remoteService) valintent=Intent(this@LocalService,RemoteService::class.java) this@LocalService.bindService(intent,this, Context.BIND_ABOVE_CLIENT) } overridefunonServiceConnected(name:ComponentName,service:IBinder){ try{ if(mBilder!=null&&KeepLive.foregroundNotification!=null){ valguardAidl=GuardAidl.Stub.asInterface(service) guardAidl.wakeUp(KeepLive.foregroundNotification?.getTitle(),KeepLive.foregroundNotification?.getDescription(),KeepLive.foregroundNotification!!.getIconRes()) } }catch(e:RemoteException){ e.printStackTrace() } } } overridefunonDestroy(){ super.onDestroy() unbindService(connection) if(KeepLive.keepLiveService!=null){ KeepLive.keepLiveService?.onStop() } } }
然后再定义一个远程服务,绑定本地服务。
classRemoteService:Service(){ privatevarmBilder:MyBilder?=null overridefunonCreate(){ super.onCreate() if(mBilder==null){ mBilder=MyBilder() } } overridefunonBind(intent:Intent):IBinder?{ returnmBilder } overridefunonStartCommand(intent:Intent,flags:Int,startId:Int):Int{ try{ this.bindService(Intent(this@RemoteService,LocalService::class.java), connection,Context.BIND_ABOVE_CLIENT) }catch(e:Exception){ } returnService.START_STICKY } overridefunonDestroy(){ super.onDestroy() unbindService(connection) } privateinnerclassMyBilder:GuardAidl.Stub(){ @Throws(RemoteException::class) overridefunwakeUp(title:String,discription:String,iconRes:Int){ if(Build.VERSION.SDK_INT<25){ valintent=Intent(applicationContext,NotificationClickReceiver::class.java) intent.action=NotificationClickReceiver.CLICK_NOTIFICATION valnotification=NotificationUtils.createNotification(this@RemoteService,title,discription,iconRes,intent) this@RemoteService.startForeground(13691,notification) } } } privatevalconnection=object:ServiceConnection{ overridefunonServiceDisconnected(name:ComponentName){ valremoteService=Intent(this@RemoteService, LocalService::class.java) this@RemoteService.startService(remoteService) this@RemoteService.bindService(Intent(this@RemoteService, LocalService::class.java),this,Context.BIND_ABOVE_CLIENT) } overridefunonServiceConnected(name:ComponentName,service:IBinder){} } } /** *通知栏点击广播接受者 */ classNotificationClickReceiver:BroadcastReceiver(){ companionobject{ constvalCLICK_NOTIFICATION="CLICK_NOTIFICATION" } overridefunonReceive(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) classJobHandlerService:JobService(){ privatevarmJobScheduler:JobScheduler?=null overridefunonStartCommand(intent:Intent?,flags:Int,startId:Int):Int{ varstartId=startId startService(this) if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){ mJobScheduler=getSystemService(Context.JOB_SCHEDULER_SERVICE)asJobScheduler valbuilder=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()) } returnService.START_STICKY } privatefunstartService(context:Context){ if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){ if(KeepLive.foregroundNotification!=null){ valintent=Intent(applicationContext,NotificationClickReceiver::class.java) intent.action=NotificationClickReceiver.CLICK_NOTIFICATION valnotification=NotificationUtils.createNotification(this,KeepLive.foregroundNotification!!.getTitle(),KeepLive.foregroundNotification!!.getDescription(),KeepLive.foregroundNotification!!.getIconRes(),intent) startForeground(13691,notification) } } //启动本地服务 vallocalIntent=Intent(context,LocalService::class.java) //启动守护进程 valguardIntent=Intent(context,RemoteService::class.java) startService(localIntent) startService(guardIntent) } overridefunonStartJob(jobParameters:JobParameters):Boolean{ if(!isServiceRunning(applicationContext,"com.xiyang51.keeplive.service.LocalService")||!isServiceRunning(applicationContext,"$packageName:remote")){ startService(this) } returnfalse } overridefunonStopJob(jobParameters:JobParameters):Boolean{ if(!isServiceRunning(applicationContext,"com.xiyang51.keeplive.service.LocalService")||!isServiceRunning(applicationContext,"$packageName:remote")){ startService(this) } returnfalse } privatefunisServiceRunning(ctx:Context,className:String):Boolean{ varisRunning=false valactivityManager=ctx .getSystemService(Context.ACTIVITY_SERVICE)asActivityManager valservicesList=activityManager .getRunningServices(Integer.MAX_VALUE) vall=servicesList.iterator() while(l.hasNext()){ valsi=l.next() if(className==si.service.className){ isRunning=true } } returnisRunning } }
4,提高Service优先级
在onStartCommand()方法中开启一个通知,提高进程的优先级。注意:从Android8.0(API级别26)开始,所有通知必须要分配一个渠道,对于每个渠道,可以单独设置视觉和听觉行为。然后用户可以在设置中修改这些设置,根据应用程序来决定哪些通知可以显示或者隐藏。
首先,定义一个通知工具类,此工具栏兼容Android8.0。
classNotificationUtils(context:Context):ContextWrapper(context){ privatevarmanager:NotificationManager?=null privatevarid:String=context.packageName+"51" privatevarname:String=context.packageName privatevarcontext:Context=context privatevarchannel:NotificationChannel?=null companionobject{ @SuppressLint("StaticFieldLeak") privatevarnotificationUtils:NotificationUtils?=null funcreateNotification(context:Context,title:String,content:String,icon:Int,intent:Intent):Notification?{ if(notificationUtils==null){ notificationUtils=NotificationUtils(context) } varnotification: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() } returnnotification } } @RequiresApi(api=Build.VERSION_CODES.O) funcreateNotificationChannel(){ 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) } } privatefungetManager():NotificationManager{ if(manager==null){ manager=getSystemService(Context.NOTIFICATION_SERVICE)asNotificationManager } returnmanager!! } @RequiresApi(api=Build.VERSION_CODES.O) fungetChannelNotification(title:String,content:String,icon:Int,intent:Intent):Notification.Builder{ //PendingIntent.FLAG_UPDATE_CURRENT这个类型才能传值 valpendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_UPDATE_CURRENT) returnNotification.Builder(context,id) .setContentTitle(title) .setContentText(content) .setSmallIcon(icon) .setAutoCancel(true) .setContentIntent(pendingIntent) } fungetNotification_25(title:String,content:String,icon:Int,intent:Intent):NotificationCompat.Builder{ valpendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_UPDATE_CURRENT) returnNotificationCompat.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是AndroidJetPac中的一个API,借助Workmanager,我们可以用它来实现应用饿保活。使用前,我们需要依赖Workmanager库,如下:
implementation"android.arch.work:work-runtime:1.0.0-alpha06"
Worker是一个抽象类,用来指定需要执行的具体任务。
publicclassKeepLiveWorkextendsWorker{ privatestaticfinalStringTAG="KeepLiveWork"; @NonNull @Override publicWorkerResultdoWork(){ Log.d(TAG,"keep->doWork:startKeepService"); //启动job服务 startJobService(); //启动相互绑定的服务 startKeepService(); returnWorkerResult.SUCCESS; } }
然后,启动keepWork方法,
publicvoidstartKeepWork(){ WorkManager.getInstance().cancelAllWorkByTag(TAG_KEEP_WORK); Log.d(TAG,"keep->doworkstartKeepWork"); OneTimeWorkRequestoneTimeWorkRequest=newOneTimeWorkRequest.Builder(KeepLiveWork.class) .setBackoffCriteria(BackoffPolicy.LINEAR,5,TimeUnit.SECONDS) .addTag(TAG_KEEP_WORK) .build(); WorkManager.getInstance().enqueue(oneTimeWorkRequest); }
关于WorkManager,可以通过下面的文章来详细了解:WorkManager浅谈
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。