Android正確的保活方案,拒絕陷入需求死迴圈!

語言: CN / TW / HK

 BATcoder技術 群,讓一部分人先進大廠

大家好,我是劉望舒,騰訊最具價值專家,著有三本業內知名暢銷書,連續五年蟬聯電子工業出版社年度優秀作者, 百度百科收錄的資深技術專家。

前華為面試官、獨角獸公司技術總監。

想要 加入  BATcoder技術群,公號回覆 BAT  即可。

作者:Halifax

https://juejin.cn/post/7003992225575075876

正文

在開始前,還是給大家簡單介紹一下,以前出現過的一些黑科技:

大概在6年前Github中出現過一個叫MarsDaemon,這個庫通過雙程序守護的方式實現保活,一時間風頭無兩。好景不長,進入Android 8.0時代之後,這個庫就廢掉了。

最近2年Github上面出來一個Leoric,感興趣的可以去看一下原始碼,誰敢用在生產環境呢,也就自己玩玩的才會用吧(不能因為保活而導致手機卡巴斯基),我沒有試過這個,我想說的是:黑科技能黑的了一時,能黑的了一世嗎?

沒有規矩,不成方圓,要提升產品的存活率,最終還是要落到產品本身上面來,尊重使用者,提升使用者體驗才是正道。

以前我也是深受保活需求的壓迫,最近發現QQ群裡有人又提到了如何保活,那麼我們就來說一說,如何來正確保活App?

Android 8.0之後: 加強了應用後臺限制,當時測試過一組資料:

應用處於前臺,啟動一個前臺Service,裡面使用JobScheduler啟動定時任務(30秒觸發一次 , 此時手機鎖屏,前10分鐘內,定時任務都是正常執行;大概在12分鐘左右,發現應用程序就被kill掉了,解鎖螢幕,app也不在前臺了。

各大國產手機廠商底層都經過自己魔改,自家都有自己的一套自啟動管理,小米手機更亂(當時有個神隱模式的概念,那也是殺後臺高手),只能說當時Android手機各種效能方面都不足,各家都會有自己的一套省電模式,以此來達到省電和提高手機效能,Android系統變得越來越完善,但是廠商定製的自啟動、省電模式還在,所以我們要做保活。

Android 8.0之前-常用的保活方案

  1. 開啟一個前臺Service

  2. Android 6.0+ 忽略電池優化開關(稍後會有程式碼)

  3. 無障礙服務(只針對有用這個功能的app,如支付寶語音增強提醒用了它)

Android 8.0之後-常用的保活方案

  1. 開啟一個前臺Service(可以加上,單獨啟用的話無法滿足保活需求)

  2. Android 6.0+ 忽略電池優化開關(稍後會有程式碼)

  3. 無障礙服務(只針對有用這個功能的app,如支付寶語音增強提醒用了它)

  4. 應用自啟動許可權(最簡單的方案是針對不同系統提供教程圖片-讓使用者自己去開啟)

  5. 多工列表視窗加鎖(提供GIF教程圖片-讓使用者自己去開啟)

  6. 多工列表視窗隱藏App(僅針對有這方面需求的App)

  7. 應用後臺高耗電(僅針對Vivo手機)

保活方案實現步驟

前臺Service

//前臺服務

class ForegroundCoreServiceService () {

override fun   onBind (intent:  Intent ?) : IBinder? =  null

private var mForegroundNF:ForegroundNF  by lazy {

ForegroundNF( this )

}

override fun   onCreate () {

super .onCreate()

mForegroundNF.startForegroundNotification()

}

override fun   onStartCommand (intent:  Intent ?, flags:  Int , startId:  Int ) Int {

if ( null == intent){

//服務被系統kill掉之後重啟進來的

return START_NOT_STICKY

}

mForegroundNF.startForegroundNotification()

return super .onStartCommand(intent, flags, startId)

}

override fun   onDestroy () {

mForegroundNF.stopForegroundNotification()

super .onDestroy()

}

}

//初始化前臺通知,停止前臺通知

class ForegroundNF ( private val service: ForegroundCoreService) : ContextWrapper(service) {

companion object {

private const  val START_ID =  101

private const  val CHANNEL_ID =  "app_foreground_service"

private const  val CHANNEL_NAME =  "前臺保活服務"

}

private var mNotificationManager: NotificationManager? =  null

private var mCompatBuilder:NotificationCompat.Builder?= null

private val compatBuilder: NotificationCompat.Builder?

get () {

if (mCompatBuilder ==  null ) {

val notificationIntent = Intent( this , MainActivity:: class . java )

notificationIntent.action = Intent.ACTION_MAIN

notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER)

notificationIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED

//動作意圖

val pendingIntent = PendingIntent.getActivity(

this , (Math.random() *  1010 ).toInt(),

notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT

)

val notificationBuilder: NotificationCompat.Builder = NotificationCompat.Builder( this ,CHANNEL_ID)

//標題

notificationBuilder.setContentTitle(getString(R.string.notification_content))

//通知內容

notificationBuilder.setContentText(getString(R.string.notification_sub_content))

//狀態列顯示的小圖示

notificationBuilder.setSmallIcon(R.mipmap.ic_coolback_launcher)

//通知內容開啟的意圖

notificationBuilder.setContentIntent(pendingIntent)

mCompatBuilder = notificationBuilder

}

return mCompatBuilder

}

init {

createNotificationChannel()

}

//建立通知渠道

private fun   createNotificationChannel () {

mNotificationManager =

getSystemService(Context.NOTIFICATION_SERVICE)  as NotificationManager

//針對8.0+系統

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

val channel = NotificationChannel(

CHANNEL_ID,

CHANNEL_NAME,

NotificationManager.IMPORTANCE_LOW

)

channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC

channel.setShowBadge( false )

mNotificationManager?.createNotificationChannel(channel)

}

}

//開啟前臺通知

fun startForegroundNotification () {

service.startForeground(START_ID, compatBuilder?.build())

}

//停止前臺服務並清除通知

fun stopForegroundNotification () {

mNotificationManager?.cancelAll()

service.stopForeground( true )

}

}

忽略電池優化(Android 6.0+)

我們需要在AndroidManifest.xml中宣告一下許可權。

< uses-permission android:name = "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

通過Intent來請求忽略電池優化的許可權(需要引導使用者點選)。

//在Activity的onCreate中註冊ActivityResult,一定要在onCreate中註冊

//監聽onActivityForResult回撥

mIgnoreBatteryResultContract = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->

//查詢是否開啟成功

if (queryBatteryOptimizeStatus()){

//忽略電池優化開啟成功

} else {

//開啟失敗

}

}

通過Intent開啟忽略電池優化彈框:

val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)

intent. data = Uri.parse( "package: $packageName " )

//啟動忽略電池優化,會彈出一個系統的彈框,我們在上面的

launchActivityResult(intent)

查詢是否成功開啟忽略電池優化開關:

fun Context. queryBatteryOptimizeStatus () : Boolean {

val powerManager = getSystemService(Context.POWER_SERVICE)  as PowerManager?

return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

powerManager?.isIgnoringBatteryOptimizations(packageName)?: false

else {

true

}

}

無障礙服務

看官方文件:建立自己的無障礙服務,地址如下:

https://developer.android.google.cn/guide/topics/ui/accessibility/service

它也是一個Service,它的優先順序比較高,提供介面增強功能,初衷是幫助視覺障礙的使用者或者是可能暫時無法與裝置進行全面互動的使用者完成操作。

可以做很多事情,使用了此Service,在6.0+不需要申請懸浮窗許可權,直接使用WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY 挺方便的(僅針對有需要此服務的app,可以開啟增強後臺保活)

自啟動許可權(即:白名單管理列表頁面)

是系統給使用者自己去開啟“自啟動許可權”開關的入口,我們需要針對不同的手機廠商和系統版本,彈出提示引導使用者是否前去開啟“自啟動許可權”。

有的手機廠商叫白名單管理,有的叫自啟動許可權,兩個是一個概念。點選檢視跳轉到『手機自啟動設定頁面』完整程式碼:

https://gist.github.com/TheMelody/5044dd1b697707b18e94b89f97f55db6

需要注意:如果是程式碼控制跳轉,無法保證永遠可以調整,系統升級可能就給你遮蔽了, 最簡單的方法是顯示一個如何找到自啟動頁面的引導圖,下面以華為手機為例

華為手機-自啟動管理

多工列表視窗加鎖

可以針對不同手機廠商,顯示引導使用者,開啟App視窗加鎖之後,點選清理加速不會導致應用被kill。

華為手機視窗加鎖-教程圖

多工列表視窗隱藏App視窗

剛剛上面多工視窗加鎖完,再提示使用者去App裡面把隱藏App視窗開關開啟,這樣使用者就不會在多工列表裡面把App視窗給手抖劃掉。

多工視窗中『隱藏App視窗』,可以用如下程式碼控制。

(這個也只是針對有這方面需求App提供的一種增強方案罷了。因為隱藏了視窗,使用者就不會去想他,不會去手癢去劃掉它)

//在多工列表頁面隱藏App視窗

fun hideAppWindow (context:  Context ,isHide: Boolean ) {

try {

val activityManager: ActivityManager = context.getSystemService(Context.ACTIVITY_SERVICE)  as ActivityManager

//控制App的視窗是否在多工列表顯示

activityManager.appTasks[ 0 ].setExcludeFromRecents(isHide)

} catch (e:Exception){

.....

}

}

應用後臺高耗電(Vivo手機獨有)

開啟的入口:“設定”>“電池”>“後臺高耗電”>“找到xxxApp開啟開關”

vivo允許後臺高耗電

最後還是奉勸那些,仍然執著於找尋黑科技的開發者,醒醒吧,太陽晒屁股了。

如果說你的App使用者群體不是普通使用者,是專門給一些玩機大神們用的,都可以root手機的話,那麼直接 move 到系統目錄 priv/system/app 即可, 即使被使用者強殺也會自動重新拉起。