纵有疾风起
人生不言弃

Android应用保活实践

最近在做的项目中需要app在后台常驻,用于实时上传一些健康信息数据,便于后台实时查看用户的健康状况。自从Android7.0以上后台常驻实现越来越难,尤其是8.0及以上。关于保活的文章比比皆是,但是效果并不理想,关于保活的方法也就常说的哪几种,重点在于怎么组合运用。最终实现效果为:用户不主动强制杀死的话,能够一直存活(小米,华为,vivo,oppo,三星)。其中三星s8,华为nova2s用户强制杀死也能存活。

一.项目结构

Android应用保活实践插图

二.常见的保活方案

关于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        val window = window        window.setGravity(Gravity.LEFT or Gravity.TOP)        val params = window.attributes        params.x = 0        params.y = 0        params.height = 1        params.width = 1        window.attributes = params        //在一像素activity里注册广播接受者    接受到广播结束掉一像素        br = object : BroadcastReceiver() {            override fun onReceive(context: Context, intent: Intent) {                finish()            }        }        registerReceiver(br, IntentFilter("finish activity"))        checkScreenOn()    }    override fun onResume() {        super.onResume()        checkScreenOn()    }    override fun onDestroy() {        try {            //销毁的时候解锁广播            unregisterReceiver(br)        } catch (e: IllegalArgumentException) {        }        super.onDestroy()    }    /**     * 检查屏幕是否点亮     */    private fun checkScreenOn() {        val pm = this@OnePixelActivity.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()        }    }}
2.双进程守护

定义一个本地服务,在该服务中播放无声音乐,并绑定远程服务。

class LocalService : Service() {    private var mediaPlayer: MediaPlayer? = null    private var mBilder: MyBilder? = null    override fun onCreate() {        super.onCreate()        if (mBilder == null) {            mBilder = MyBilder()        }    }    override fun onBind(intent: Intent): IBinder? {        return mBilder    }    override fun onStartCommand(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) {            val intent2 = Intent(applicationContext, NotificationClickReceiver::class.java)            intent2.action = NotificationClickReceiver.CLICK_NOTIFICATION            val notification = NotificationUtils.createNotification(this, KeepLive.foregroundNotification!!.getTitle(), KeepLive.foregroundNotification!!.getDescription(), KeepLive.foregroundNotification!!.getIconRes(), intent2)            startForeground(13691, notification)        }        //绑定守护进程        try {            val intent3 = 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()        }        return Service.START_STICKY    }    private fun play() {        if (mediaPlayer != null &amp;&amp; !mediaPlayer!!.isPlaying) {            mediaPlayer?.start()        }    }    private inner class MyBilder : 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)            val intent = Intent(this@LocalService, RemoteService::class.java)            this@LocalService.bindService(intent, this,                    Context.BIND_ABOVE_CLIENT)        }        override fun onServiceConnected(name: ComponentName, service: IBinder) {            try {                if (mBilder != null &amp;&amp; KeepLive.foregroundNotification != null) {                    val guardAidl = GuardAidl.Stub.asInterface(service)                    guardAidl.wakeUp(KeepLive.foregroundNotification?.getTitle(), KeepLive.foregroundNotification?.getDescription(), KeepLive.foregroundNotification!!.getIconRes())                }            } catch (e: RemoteException) {                e.printStackTrace()            }        }    }    override fun onDestroy() {        super.onDestroy()        unbindService(connection)        if (KeepLive.keepLiveService != null) {            KeepLive.keepLiveService?.onStop()        }    }}

定义一个远程服务,绑定本地服务。

class RemoteService : Service() {    private var mBilder: MyBilder? = null    override fun onCreate() {        super.onCreate()        if (mBilder == null) {            mBilder = MyBilder()        }    }    override fun onBind(intent: Intent): IBinder? {        return mBilder    }    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 Service.START_STICKY    }    override fun onDestroy() {        super.onDestroy()        unbindService(connection)    }    private inner class MyBilder : 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(Intent(this@RemoteService,                    LocalService::class.java), this, Context.BIND_ABOVE_CLIENT)        }        override fun onServiceConnected(name: ComponentName, service: IBinder) {}    }}/** * 通知栏点击广播接受者 */class NotificationClickReceiver : BroadcastReceiver() {    companion object {        const val CLICK_NOTIFICATION = "CLICK_NOTIFICATION"    }    override fun onReceive(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和JobService是安卓在api 21中增加的接口,用于在某些指定条件下执行后台任务。

定义一个JobService,开启本地服务和远程服务

@SuppressWarnings(value = ["unchecked", "deprecation"])@RequiresApi(Build.VERSION_CODES.LOLLIPOP)class JobHandlerService : JobService() {    private var mJobScheduler: JobScheduler? = null    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {        var startId = startId        startService(this)        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {            mJobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler            val builder = 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())        }        return Service.START_STICKY    }    private fun startService(context: Context) {        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {            if (KeepLive.foregroundNotification != null) {                val intent = Intent(applicationContext, NotificationClickReceiver::class.java)                intent.action = NotificationClickReceiver.CLICK_NOTIFICATION                val notification = NotificationUtils.createNotification(this, KeepLive.foregroundNotification!!.getTitle(), KeepLive.foregroundNotification!!.getDescription(), KeepLive.foregroundNotification!!.getIconRes(), intent)                startForeground(13691, notification)            }        }        //启动本地服务        val localIntent = Intent(context, LocalService::class.java)        //启动守护进程        val guardIntent = Intent(context, RemoteService::class.java)        startService(localIntent)        startService(guardIntent)    }    override fun onStartJob(jobParameters: JobParameters): Boolean {        if (!isServiceRunning(applicationContext, "com.xiyang51.keeplive.service.LocalService") || !isServiceRunning(applicationContext, "$packageName:remote")) {            startService(this)        }        return false    }    override fun onStopJob(jobParameters: JobParameters): Boolean {        if (!isServiceRunning(applicationContext, "com.xiyang51.keeplive.service.LocalService") || !isServiceRunning(applicationContext, "$packageName:remote")) {            startService(this)        }        return false    }    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 context: Context = context    private var channel: NotificationChannel? = null    companion object {        @SuppressLint("StaticFieldLeak")        private var notificationUtils: NotificationUtils? = null        fun createNotification(context: Context, title: String, content: String, icon: Int, intent: Intent): Notification? {            if (notificationUtils == null) {                notificationUtils = NotificationUtils(context)            }            var notification: 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()            }            return notification        }    }    @RequiresApi(api = Build.VERSION_CODES.O)    fun createNotificationChannel() {        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)        }    }    private fun getManager(): NotificationManager {        if (manager == null) {            manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager        }        return manager!!    }    @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(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)        return Notification.Builder(context, id)                .setContentTitle(title)                .setContentText(content)                .setSmallIcon(icon)                .setAutoCancel(true)                .setContentIntent(pendingIntent)    }    fun getNotification_25(title: String, content: String, icon: Int, intent: Intent): NotificationCompat.Builder {        val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)        return NotificationCompat.Builder(context, id)                .setContentTitle(title)                .setContentText(content)                .setSmallIcon(icon)                .setAutoCancel(true)                .setVibrate(longArrayOf(0))                .setSound(null)                .setLights(0, 0, 0)                .setContentIntent(pendingIntent)    }}

使用

将保活的功能封装成了一个单独的库,依赖该库即可。

app中使用:

KeepLive.startWork(this, KeepLive.RunMode.ROGUE, ForegroundNotification("Title", "message",        R.mipmap.ic_launcher, object : ForegroundNotificationClickListener {    override fun foregroundNotificationClick(context: Context, intent: Intent) {        //点击通知回调    }}), object : KeepLiveService {    override fun onStop() {        //可能调用多次,跟onWorking匹配调用    }    override fun onWorking() {        //一直存活,可能调用多次    }})

清单文件配置:

 <!--权限配置--><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" /><service    android:name="com.xiyang51.keeplive.service.JobHandlerService"    android:permission="android.permission.BIND_JOB_SERVICE" /><service    android:name="com.xiyang51.keeplive.service.RemoteService"    android:process=":remote" />

关于我

更多信息可以点击关于我 , 非常希望和大家一起交流 , 共同进步

文章转载于:https://www.jianshu.com/p/16d8c0c8116a

原著是一个有趣的人,若有侵权,请通知删除

未经允许不得转载:起风网 » Android应用保活实践
分享到: 生成海报

评论 抢沙发

评论前必须登录!

立即登录