前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解Android 8.0以上系统应用如何保活

详解Android 8.0以上系统应用如何保活

作者头像
砸漏
发布2020-10-16 11:10:15
2.1K0
发布2020-10-16 11:10:15
举报
文章被收录于专栏:恩蓝脚本

最近在做一个埋点的sdk,由于埋点是分批上传的,不是每次都上传,所以会有个进程保活的机制,这也是自研推送的实现技术之一:如何保证Android进程的存活。

对于Android来说,保活主要有以下一些方法:

  • 开启前台Service(效果好,推荐)
  • Service中循环播放一段无声音频(效果较好,但耗电量高,谨慎使用)
  • 双进程守护(Android 5.0前有效)
  • JobScheduler(Android 5.0后引入,8.0后失效)
  • 1 像素activity保活方案(不推荐)
  • 广播锁屏、自定义锁屏(不推荐)
  • 第三方推送SDK唤醒(效果好,缺点是第三方接入)

下面是具体的实现方案:

1.监听锁屏广播,开启1个像素的Activity

最早见到这种方案的时候是2015年,有个FM的app为了向投资人展示月活,在Android应用中开启一个1像素的Activity。

由于Activity的级别是比较高的,所以开启1个像素的Activity的方式就可以保证进程是不容易被杀掉的。

具体来说,定义一个1像素的Activity,在该Activity中动态注册自定义的广播。

代码语言:javascript
复制
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, 双进程守护

双进程守护,在Android 5.0前是有效的,5.0之后就不行了。首先,我们定义定义一个本地服务,在该服务中播放无声音乐,并绑定远程服务

代码语言:javascript
复制
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()
}
}
}

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

代码语言:javascript
复制
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是Android从5.0增加的支持一种特殊的任务调度机制,可以用它来实现进程保活,不过在Android8.0系统中,此种方法也失效。

首先,我们定义一个JobService,开启本地服务和远程服务。

代码语言:javascript
复制
@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,提高Service优先级

在onStartCommand()方法中开启一个通知,提高进程的优先级。注意:从Android 8.0(API级别26)开始,所有通知必须要分配一个渠道,对于每个渠道,可以单独设置视觉和听觉行为。然后用户可以在设置中修改这些设置,根据应用程序来决定哪些通知可以显示或者隐藏。

首先,定义一个通知工具类,此工具栏兼容Android 8.0。

代码语言:javascript
复制
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)
}
}

5,Workmanager方式

Workmanager是Android JetPac中的一个API,借助Workmanager,我们可以用它来实现应用饿保活。使用前,我们需要依赖Workmanager库,如下:

代码语言:javascript
复制
implementation "android.arch.work:work-runtime:1.0.0-alpha06"

Worker是一个抽象类,用来指定需要执行的具体任务。

代码语言:javascript
复制
public class KeepLiveWork extends Worker {
private static final String TAG = "KeepLiveWork";
@NonNull
@Override
public WorkerResult doWork() {
Log.d(TAG, "keep-  doWork: startKeepService");
//启动job服务
startJobService();
//启动相互绑定的服务
startKeepService();
return WorkerResult.SUCCESS;
}
}

然后,启动keepWork方法,

代码语言:javascript
复制
public void startKeepWork() {
WorkManager.getInstance().cancelAllWorkByTag(TAG_KEEP_WORK);
Log.d(TAG, "keep-  dowork startKeepWork");
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(KeepLiveWork.class)
.setBackoffCriteria(BackoffPolicy.LINEAR, 5, TimeUnit.SECONDS)
.addTag(TAG_KEEP_WORK)
.build();
WorkManager.getInstance().enqueue(oneTimeWorkRequest);
}

关于WorkManager,可以通过下面的文章来详细了解:WorkManager浅谈

以上就是本文的全部内容,希望对大家的学习有所帮助。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档