最近在做的项目中需要app在后台常驻,用于实时上传一些健康信息数据,便于后台实时查看用户的健康状况。自从Android7.0以上后台常驻实现越来越难,尤其是8.0及以上。关于保活的文章比比皆是,但是效果并不理想,关于保活的方法也就常说的哪几种,重点在于怎么组合运用。最终实现效果为:用户不主动强制杀死的话,能够一直存活(小米,华为,vivo,oppo,三星)。其中三星s8,华为nova2s用户强制杀死也能存活。
项目结构
常见的保活方案
关于Android应用保活的文章很多,这里不再阐述,可自行百度。重点在于运用这样方案来实现保活功能。
代码实现
1.监听锁屏广播,开启1个像素的Activity。
在锁屏的时候启动一个1个像素的Activity,当用户解锁以后将这个Activity结束掉。
定义一个1像素的Activity,在该Activity中动态注册自定义的广播。
class OnePixelActivity : AppCompatActivity() {
private lateinit var br: BroadcastReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//设定一像素的activity
with(window){
setGravity(Gravity.START or Gravity.TOP)
attributes =attributes.apply {
this.x =0
this.y =0
this.height =1
this.width =1
}
}
//注册广播
registerDestroyReceiver()
}
/** 注册1像素页面销毁广播*/
private fun registerDestroyReceiver() {
//在一像素activity里注册广播接受者 接受到广播结束掉一像素
br =object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
finish()
}
}
registerReceiver(br, IntentFilter("finish activity"))
checkScreenOn()
}
/** 检查屏幕是否点亮 */
private fun checkScreenOn() {
val pm =application.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()
}
}
override fun onResume() {
super.onResume()
checkScreenOn()
}
override fun onDestroy() {
try {
//销毁的时候解锁广播
unregisterReceiver(br)
}catch (e: IllegalArgumentException) {
}
super.onDestroy()
}
}
2.双进程守护
定义一个本地服务,在该服务中播放无声音乐,并绑定远程服务。
class LocalService : Service() {
private lateinit var mediaPlayer: MediaPlayer
private lateinit var mBinder: MyBinder
override fun onCreate() {
super.onCreate()
mBinder = MyBinder()
}
override fun onBind(intent: Intent): IBinder? {
return mBinder
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
try {
//播放无声音乐
playMusic()
//启用前台服务,提升优先级
startForegroundService()
//绑定守护进程
bindGuardService()
//隐藏服务通知
hideServiceNotification()
KeepLive.keepLiveService.onWorking()
}catch (e: Exception) {
}
return START_STICKY
}
/** 隐藏服务通知*/
private fun hideServiceNotification() {
if (Build.VERSION.SDK_INT <25)
startService(Intent(this, HideForegroundService::class.java))
}
/** 绑定守护进程*/
private fun bindGuardService() {
val intent = Intent(this, RemoteService::class.java)
bindService(intent, connection, Context.BIND_ABOVE_CLIENT)
}
/** 启动前台服务*/
private fun startForegroundService() {
val intent = Intent(applicationContext, NotificationClickReceiver::class.java)
intent.action = NotificationClickReceiver.CLICK_NOTIFICATION
val notification = NotificationUtils.createNotification(
this,
KeepLive.foregroundNotification.title,
KeepLive.foregroundNotification.description,
KeepLive.foregroundNotification.iconRes,
intent)
startForeground(13691, notification)
}
/** 播放音乐*/
private fun playMusic() {
mediaPlayer = MediaPlayer.create(this, R.raw.novioce)
//声音设置为0
mediaPlayer.setVolume(0f, 0f)
mediaPlayer.isLooping =true//循环播放
if (!mediaPlayer.isPlaying)mediaPlayer.start()
}
private inner class MyBinder : 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)
this@LocalService.bindService(remoteService, this, Context.BIND_ABOVE_CLIENT)
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {
try {
val guardAidl = GuardAidl.Stub.asInterface(service)
guardAidl.wakeUp(
KeepLive.foregroundNotification.title,
KeepLive.foregroundNotification.description,
KeepLive.foregroundNotification.iconRes)
}catch (e: RemoteException) {
e.printStackTrace()
}
}
}
override fun onDestroy() {
super.onDestroy()
//解绑服务
unbindService(connection)
KeepLive.keepLiveService.onStop()
}
}
定义一个远程服务,绑定本地服务。
class RemoteService : Service() {
private lateinit var mBinder: MyBinder
override fun onCreate() {
super.onCreate()
mBinder = MyBinder()
}
override fun onBind(intent: Intent): IBinder? =mBinder
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 START_STICKY
}
override fun onDestroy() {
super.onDestroy()
unbindService(connection)
}
private inner class MyBinder : 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(remoteService, this, Context.BIND_ABOVE_CLIENT)
}
override fun onServiceConnected(name: ComponentName, service: IBinder) {}
}
}
3.JobScheduler
JobScheduler和JobService是安卓在api 21中增加的接口,用于在某些指定条件下执行后台任务。
定义一个JobService,开启本地服务和远程服务
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
class JobHandlerService : JobService() {
private var mJobScheduler: JobScheduler? =null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
startService(this)
startJobScheduler(startId)
return Service.START_STICKY
}
/** 启动 JobScheduler*/
private fun startJobScheduler(id: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mJobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE)as JobScheduler
val builder = JobInfo.Builder(id, 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())
}
}
/**
* 启动服务
* @param context Context
*/
private fun startService(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val intent = Intent(applicationContext, NotificationClickReceiver::class.java)
intent.action = NotificationClickReceiver.CLICK_NOTIFICATION
val notification = NotificationUtils.createNotification(
this, KeepLive.foregroundNotification.title,
KeepLive.foregroundNotification.description,
KeepLive.foregroundNotification.iconRes,
intent)
startForeground(13691, notification)
}
//启动本地服务
val localIntent = Intent(context, LocalService::class.java)
//启动守护进程
val guardIntent = Intent(context, RemoteService::class.java)
startService(localIntent)
startService(guardIntent)
}
/**
* 启动job
* @param jobParameters JobParameters
* @return Boolean
*/
override fun onStartJob(jobParameters: JobParameters): Boolean {
if (!isServiceRunning(applicationContext, "com.xiyang51.keeplive.service.LocalService") || !isServiceRunning(applicationContext, "$packageName:remote")) {
startService(this)
}
return false
}
/**
* 停止job
* @param jobParameters JobParameters
* @return Boolean
*/
override fun onStopJob(jobParameters: JobParameters): Boolean {
if (!isServiceRunning(applicationContext, "com.xiyang51.keeplive.service.LocalService") || !isServiceRunning(applicationContext, "$packageName:remote")) {
startService(this)
}
return false
}
/**
* 服务是否正在运行
* @param ctx Context
* @param className String
* @return Boolean
*/
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.播放无声音乐
这里使用的是有声的mp3文件,只是在代码中把声音设置成了0;如果使用真正的无声的音乐文件,在oppo手机上按下返回键会被立刻杀死,并且在三星手机,华为nova2s强制杀死也会被杀死,所有使用了有声的文件。
5.提高Service优先级
在onStartCommand()方法中开启一个通知,提高进程的优先级。注意:从Android 8.0(API级别26)开始,所有通知必须要分配一个渠道,对于每个渠道,可以单独设置视觉和听觉行为。然后用户可以在设置中修改这些设置,根据应用程序来决定哪些通知可以显示或者隐藏。
定义一个通知工具类,兼容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 channel: NotificationChannel? =null
companion object {
private var notificationUtils: NotificationUtils? =null
/**
* 创建一个通知
* @param context Context
* @param title String
* @param content String
* @param icon Int
* @param intent Intent
* @return Notification
*/
fun createNotification(context: Context, title: String, content: String, icon: Int, intent: Intent): Notification {
if (notificationUtils ==null)notificationUtils = NotificationUtils(context)
return if (Build.VERSION.SDK_INT >=26) {
notificationUtils!!.createNotificationChannel()
notificationUtils!!.getChannelNotification(title, content, icon, intent).build()
}else {
notificationUtils!!.getNotification25(title, content, icon, intent).build()
}
}
}
/**
* 创建通知渠道8.0及以上
*/
@RequiresApi(api = Build.VERSION_CODES.O)
fun createNotificationChannel() {
if (channel ==null) {
channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_MIN)
channel?.let {
it.enableLights(false)
it.enableVibration(false)
it.vibrationPattern =longArrayOf(0)
it.setSound(null, null)
}
getManager().createNotificationChannel(channel!!)
}
}
/**
* 获取NotificationManager
* @return NotificationManager
*/
private fun getManager(): NotificationManager {
if (manager ==null) {
manager = getSystemService(Context.NOTIFICATION_SERVICE)as NotificationManager
}
return manager!!
}
/**
* 获取渠道通知
* @param title String
* @param content String
* @param icon Int
* @param intent Intent
* @return Notification.Builder
*/
@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(baseContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return Notification.Builder(baseContext, id)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(icon)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
}
/**
* 获取通知
* @param title String
* @param content String
* @param icon Int
* @param intent Intent
* @return NotificationCompat.Builder
*/
fun getNotification25(title: String, content: String, icon: Int, intent: Intent): NotificationCompat.Builder {
val pendingIntent = PendingIntent.getBroadcast(baseContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
return NotificationCompat.Builder(baseContext, id)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(icon)
.setAutoCancel(true)
.setVibrate(longArrayOf(0))
.setSound(null)
.setLights(0, 0, 0)
.setContentIntent(pendingIntent)
}
}
使用
将保活的功能封装成了一个单独的库,依赖该库即可。
app中使用:
class AppApplication : Application(),KeepLiveService {
override fun onCreate() {
super.onCreate()
//启动保活服务
KeepLive.startWork(this, KeepLive.RunMode.ROGUE, getNotification(),this)
}
//一直存活,可能调用多次
override fun onWorking() {
Log.e("KeepLive","onWorking()")
}
//可能调用多次,跟onWorking匹配调用
override fun onStop() {
Log.e("KeepLive","onStop()")
}
/**
* 创建通知
* @return ForegroundNotification
*/
private fun getNotification(): ForegroundNotification {
return ForegroundNotification("Title", "message", R.mipmap.ic_launcher,
object : ForegroundNotificationClickListener {
override fun foregroundNotificationClick(context: Context, intent: Intent) {
//点击通知回调
}
})
}
}
清单文件配置:
<!--权限配置-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<!--保活相关配置-->
<receiver android:name="com.xiyang51.keeplive.receiver.NotificationClickReceiver" />
<activity android:name="com.xiyang51.keeplive.activity.OnePixelActivity" />
<service android:name="com.xiyang51.keeplive.service.LocalService" />
<service android:name="com.xiyang51.keeplive.service.HideForegroundService" />
android:name="com.xiyang51.keeplive.service.JobHandlerService"
android:permission="android.permission.BIND_JOB_SERVICE" />
android:name="com.xiyang51.keeplive.service.RemoteService"
android:process=":remote" />
网友评论