Android:应用保活实践

栏目: IOS · Android · 发布时间: 7年前

内容简介:最近在做的项目中需要app在后台常驻,用于实时上传一些健康信息数据,便于后台实时查看用户的健康状况。自从Android7.0以上后台常驻实现越来越难,尤其是8.0及以上。关于保活的文章比比皆是,但是效果并不理想,关于保活的方法也就常说的哪几种,重点在于怎么组合运用。

最近在做的项目中需要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 && !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 && 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" />
复制代码

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

老二非死不可

老二非死不可

方三文 / 机械工业出版社 / 2013-12 / 39.00

关于投资 价值投资者为啥都买茅台? 怎样识别好公司与坏公司? 做空者真的罪大恶极吗? 国际板对A股会有什么影响? 波段操作,止损割肉到底靠不靠谱? IPO真的是A股萎靡不振的罪魁祸首吗? 关于商业 搜狐的再造战略有戏吗? 新浪如何焕发第二春? 百度的敌人为什么是它自己? 我为什么比巴菲特早两年投资比亚迪? 民族品牌这张牌还靠谱......一起来看看 《老二非死不可》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具