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
那這裡的0
和6
分別代表什麼呢?具體定義可以在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
的方式建立就可以啦。