首页 > 系统 > Android > 正文

Android应用保活实践详解

2019-12-12 00:12:58
字体:
来源:转载
供稿:网友

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

代码地址 github

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表