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)

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" />

代码地址

github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,744评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,505评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,105评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,242评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,269评论 6 389
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,215评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,096评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,939评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,354评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,573评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,745评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,448评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,048评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,683评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,838评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,776评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,652评论 2 354

推荐阅读更多精彩内容