1. 程式人生 > >Android中利用前臺服務白色保活

Android中利用前臺服務白色保活

程序優先順序

程序

我們都知道,每一個APP程序(process)都擁執行在獨立的虛擬機器(virtual machine)中,這樣就保證了每個APP的獨立。在系統資源緊張的時候會選擇一部分程序殺掉釋放記憶體,那麼系統是如何選取殺掉哪些留下哪些呢?這就涉及到程序的優先順序。

優先順序

為了對執行的程序進行統一管理,系統根據程序的不同狀態進行了分離,分為如下五個狀態:

前臺程序 (Foreground process)

可與與使用者互動的程序,滿足以下的都是:

  • 有可與使用者互動的Activity(已調onResume());
  • 有廣播接收者(BroadcastReceiver)正在接收廣播;
  • 有服務(Service)正在執行它的回撥方法(Service.onCreate(), Service.onStart(), or Service.onDestroy());
  • 有Service呼叫了startForeground()方法使之位於前臺執行。

前臺程序系統是不會銷燬的,除非極端情況。

可見程序 (Visible process)

  • 有不在前臺、但仍對使用者可見的 Activity(已呼叫 onPause());
  • 有繫結到可見(或前臺)Activity 的 Service;

一般系統也不會回收可見程序的,除非要保持某個或多個前臺程序存活而且資源吃緊的情況下。

服務程序 (Service process)

  • 執行著一個通過startService()啟動的Service;

在記憶體不夠維持所有前臺程序與可見程序執行時,服務程序會被銷燬。

後臺程序 (Background process)

  • 使用者返回到介面或切換到其他APP,看不到但是還在執行的程式。

可能隨時被系統銷燬,回收記憶體。

空程序 (Empty process)

  • 沒有任何活躍元件的程序;

最容易被銷燬的程序

程序檢視

獲取程序pid

通過adb shell ps檢視程序

B0000AS212345L:~ zwenkai$ adb shell ps
USER PID PPID VSIZE RSS WCHAN PC NAME root 1 0 932 688 ffffffff 00000000 S /init root 2 0 0 0 ffffffff 00000000 S kthreadd root 3 2 0 0 ffffffff 00000000 S ksoftirqd/0 root 6 2 0 0 ffffffff 00000000 S migration/0 ... ...

由於進行資訊太多,可以進行過濾下,比如我們只想查手百的程序ID,儘管不知道它的包名是什麼,但一定包含baidu。ps|grep

B0000AS212345L:~ zwenkai$ adb shell ps | grep baidu
USER     PID   PPID  VSIZE  RSS     WCHAN    PC        NAME
u0_a201   9610  165   1387724 143820 ffffffff 00000000 S com.baidu.searchbox
u0_a201   10452 165   1052784 45944 ffffffff 00000000 S com.baidu.searchbox:megappInstaller
USER PID PPID VSIZE RSS WCHAN PC NAME
使用者 程序ID 父程序ID 虛擬記憶體大小 實際記憶體大小 0:代表正在執行 Program Counter 程序名

可以看到它的程序ID為9610

檢視程序優先順序

通過上一步找到程序ID為9610,在proc目錄下找到對應的程序ID資料夾,檢視裡面的oom_adj就可以獲取到程序的優先順序了。

B0000AS212345L:~ zwenkai$ adb shell
[email protected]:/ $ cat proc/9610/oom_adj
0

可以看到值為0手百目前在前臺,把它後臺之後再看一下。

[email protected]:/ $ cat proc/9610/oom_adj
6

那這裡的06分別代表什麼呢?具體定義可以在com.android.server.am.ProcessList檢視:

常量名稱 級別(android M) 級別(android N) 描述
NATIVE_ADJ -17 -1000 native程序
SYSTEM_ADJ -16 -900 系統程序
PERSISTENT_PROC_ADJ -12 -800 系統persistent程序,如telephony
PERSISTENT_SERVICE_ADJ -11 -700 繫結系統程序或persistent程序的服務
FOREGROUND_APP_ADJ 0 0 前臺程序,正在前臺執行的APP
VISIBLE_APP_ADJ 1 100 可見程序
PERCEPTIBLE_APP_ADJ 2 200 使用者可感知程序,如音樂播放
BACKUP_APP_ADJ 3 300 正在執行備份的程序
HEAVY_WEIGHT_APP_ADJ 4 400 高權重程序
SERVICE_ADJ 5 500 服務程序
HOME_APP_ADJ 6 600 與Home互動的程序
PREVIOUS_APP_ADJ 7 700 切換程序
CACHED_APP_MIN_ADJ 9 900 快取程序即空程序
CACHED_APP_MAX_ADJ 15 906 快取程序即空程序
UNKNOWN_ADJ 16 1001 未知程序

其他APP程序分析

看下其他APP的程序是怎麼搞的,這裡挑幾款一定有Server後臺程序的。

網易雲音樂

Service宣告

<service
    android:name="com.netease.play.player.service.VideoPlayService"
    android:process=":play" />

程序ID

1|[email protected]:/ $ ps | grep netease
u0_a87    16029 165   1142056 104776 ffffffff 00000000 R com.netease.cloudmusic
u0_a87    16084 165   1072908 68608 ffffffff 00000000 S com.netease.cloudmusic:browser
u0_a87    16178 165   1058940 61900 ffffffff 00000000 S com.netease.cloudmusic:play

程序優先順序

有三個程序,對應程序優先順序:

狀態 主程序 play程序 browser程序
前臺(未播放) 0 0 7
後臺(未播放) 6 4 7
前臺(播放) 0 0 7
後臺(播放) 6 1 7

通過以上前後臺變化驗證了猜想,play程序在播放音樂的時候儘管後臺,它的程序優先順序是1,是不會被銷燬的。

喜馬拉雅

Service 宣告

<service
    android:name="com.ximalaya.ting.android.opensdk.player.service.XmPlayerService"
    android:process=":player">
    <intent-filter
        android:priority="1000">
        <action
            android:name="com.ximalaya.ting.android.mainapp.player.service.XmPlayerService" />
    </intent-filter>
</service>

程序ID

[email protected]:/ $ ps | grep ximalaya
u0_a92    16944 165   1245432 156184 ffffffff 00000000 S com.ximalaya.ting.android
u0_a92    16979 165   1029400 59772 ffffffff 00000000 S com.ximalaya.ting.android:player

程序優先順序

有兩個程序,對應程序優先順序:

狀態 主程序 player程序
前臺(未播放) 0 0
後臺(未播放) 1 1
前臺(播放) 0 0
後臺(播放) 1 1

player程序後臺播放時是符合預期的,發現它NB的是後臺時主程序程序優先順序竟然也是1,這裡懷疑它進行了黑操作。

建立前臺服務

TestService

class TestService : Service() {

    private val NOTICE_ID = 100

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    override fun onCreate() {
        super.onCreate()
        val builder = Notification.Builder(this)
        builder.setSmallIcon(R.mipmap.ic_launcher)
        builder.setContentTitle("TestService")
        builder.setContentText("TestService is running...")
        startForeground(NOTICE_ID, builder.notification)
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 如果Service被終止, 當資源允許情況下,重啟service
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        // 如果Service被殺死,幹掉通知
        val mManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        mManager.cancel(NOTICE_ID)
        // 重啟
        val intent = Intent(applicationContext, TestService::class.java)
        startService(intent)
    }

}

註冊Service

<service android:name=".TestService"
    android:process=":test"/>

開啟服務

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        startService(Intent(this, TestService::class.java))
    }
}

檢視程序ID

1|[email protected]:/ $ ps | grep kevin
u0_a93    24386 165   999248 44576 ffffffff 00000000 S com.kevin.foregroundservice
u0_a93    24406 165   976268 29064 ffffffff 00000000 S com.kevin.foregroundservice:test

程序優先順序

有兩個程序,對應程序優先順序:

狀態 主程序 test程序
前臺 0 1
後臺 6 1

和分析的兩款APP還是有差距,就是前臺的時候我們的test程序優先順序是1而不是0,其實通過bindService的方式建立就可以啦。

參考

  1. Processes and Application Lifecycle