Android應用保活實踐

最近在作的項目中須要app在後臺常駐,用於實時上傳一些健康信息數據,便於後臺實時查看用戶的健康情況。自從Android7.0以上後臺常駐實現愈來愈難,尤爲是8.0及以上。關於保活的文章比比皆是,可是效果並不理想,關於保活的方法也就常說的哪幾種,重點在於怎麼組合運用。最終實現效果爲:用戶不主動強制殺死的話,可以一直存活(小米,華爲,vivo,oppo,三星)。其中三星s8,華爲nova2s用戶強制殺死也能存活。java

項目結構


常見的保活方案

關於Android應用保活的文章不少,這裏再也不闡述,可自行百度。重點在於運用這樣方案來實現保活功能。android

代碼實現

1.監聽鎖屏廣播,開啓1個像素的Activity。

在鎖屏的時候啓動一個1個像素的Activity,當用戶解鎖之後將這個Activity結束掉。api

定義一個1像素的Activity,在該Activity中動態註冊自定義的廣播。app

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.雙進程守護

定義一個本地服務,在該服務中播放無聲音樂,並綁定遠程服務。ide

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中增長的接口,用於在某些指定條件下執行後臺任務。oop

定義一個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強制殺死也會被殺死,全部使用了有聲的文件。ui

5.提升Service優先級

onStartCommand()方法中開啓一個通知,提升進程的優先級。注意:從Android 8.0(API級別26)開始,全部通知必需要分配一個渠道,對於每一個渠道,能夠單獨設置視覺和聽覺行爲。而後用戶能夠在設置中修改這些設置,根據應用程序來決定哪些通知能夠顯示或者隱藏。this

定義一個通知工具類,兼容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" />
複製代碼


+qq羣457848807:。獲取以上高清技術思惟圖,以及相關技術的免費視頻學習資料

相關文章
相關標籤/搜索