详解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浅谈
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。