Android应用保活实践

最近在做的项目中需要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)

[email protected](remoteService)

[email protected](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)

[email protected](13691, notification)

}

}

}

private val connection =object : ServiceConnection {

override fun onServiceDisconnected(name: ComponentName) {

val remoteService = Intent(this@RemoteService, LocalService::class.java)

[email protected](remoteService)

[email protected](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) {

//点击通知回调

                    }

})

}

}


清单文件配置:


    android:name="com.xiyang51.keeplive.service.JobHandlerService"

    android:permission="android.permission.BIND_JOB_SERVICE" />

    android:name="com.xiyang51.keeplive.service.RemoteService"

    android:process=":remote" />

代码地址

github

你可能感兴趣的:(Android应用保活实践)