本文基于 AOSP android-9.0.0_r2
Android Service是Android四大組件之一,它主要用來執(zhí)行一些不與用戶交互的long-run的操作. 注意Service如非特意指定,它僅是運行于該進程的一部分代碼而已,另外Service并不是運行在單獨線程中,而是主線程中。所以盡量要避免一些ANR的操作。
一、Service的聲明
Service是Android中的四大組件,使用它一定要在AndroidManifest.xml中聲明,在AndroidManifest.xml中聲明是為了讓PackageManagerService能解析出該Service, 并建立對應(yīng)的數(shù)據(jù)結(jié)構(gòu)。如下圖所示,
如圖中所示,Service也可以定義IntentFilter.
Service分為如下三類
- foreground service
fg Service執(zhí)行一些對于用戶來說是可感知的操作,如audio應(yīng)用使用fg service來播放歌曲。 - background service
bg service執(zhí)行的操作對用戶而言是不可感知的。 - bound service
bound service主要是提供c/s接口,允許組件與service進行通信,或者是跨進程的通信。
其實說到底,由于啟動方式的不同導(dǎo)致了三種service,
startService -> background service.
startForegroundService -> foreground service
bindService -> bound service
二、foreground和background service
對于fg和bg service,它們的啟動方式不同,分別是startForegroundService和startService
@Override
public ComponentName startService(Intent service) {
return startServiceCommon(service, false, mUser);
}
@Override
public ComponentName startForegroundService(Intent service) {
return startServiceCommon(service, true, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
}
從啟動方式可以看出,它們的僅僅在于 requireForeground,即一個boolean形的標志位決定是bg還是fg service.
2.1 bg/fg啟動流程
其中 retriveServiceLocked 函數(shù),主要去建立如下的關(guān)系圖
從圖中可以看出,可以通過IntentFilter與ComponentName 兩種方式去指定一個service.
第一次啟動 service的生命周期 onCreate(scheduleCreateService) -> onStartCommand(AMS 調(diào)用scheduleServiceArgs)
多個地方(如Activity)可以多次調(diào)用startService, 如果之前已經(jīng)打開,直接進入onStartCommand就行了
注意: 需要手動調(diào)用 stopService去停止Service
而對于IntentService.
IntentService繼承于Service, 它的實現(xiàn)相當(dāng)于在Service的基礎(chǔ)上增加了一個HandlerThread, 以及自定義的Handler, IntentService將所有的業(yè)務(wù) route 到HandlerThread線程中去處理(onHandleIntent), 當(dāng)onHandleIntent處理完后,就會調(diào)用stopSelf來停止到這個Service,
所以每次啟動一個IntentService, 都是經(jīng)過這樣的生命周期
onCreate -> onStartCommand -> onStart -> onHandleIntent -> onDestroy
,
其中onStart/onStartCommand
都將Intent route到了onHandleIntent中去處理
2.2 bg service的限制
Android O開始對background的service做了限制, 具體可以參考https://developer.android.com/about/versions/oreo/background#services,
final boolean bgLaunch = !mAm.isUidActiveLocked(r.appInfo.uid);
boolean forcedStandby = false;
if (bgLaunch && appRestrictedAnyInBackground(r.appInfo.uid, r.packageName)) {
forcedStandby = true;
}
bgLaunch是檢查startService是否是background啟動,什么意思呢?如果要啟動的Service的UID并沒有running的進程的話,它就屬于background launch
比如, MyService在testA.apk里,testA.apk并沒有啟動
此時通過
adb shell am start-service -n xxxx/xxx.MyService
就屬于background的launch,
或者在testB.apk里通過startService去啟動MyService也屬于background的啟動。
而appRestrictedAnyInBackground檢查是否可以在background狀態(tài)時運行一些任務(wù),這里一般都不允許,所以forcedStandby一般都為false.
forcedStandy是一個boolean型的變量,如果它為true的話,會強制進入啟動模式的檢查。
// If this is a direct-to-foreground start, make sure it is allowed as per the app op.
boolean forceSilentAbort = false;
if (fgRequired) {
final int mode = mAm.mAppOpsService.checkOperation(
AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
switch (mode) {
case AppOpsManager.MODE_ALLOWED:
case AppOpsManager.MODE_DEFAULT:
// All okay.
break;
case AppOpsManager.MODE_IGNORED:
fgRequired = false;
forceSilentAbort = true;
break;
default:
return new ComponentName("!!", "foreground not allowed as per app op");
}
}
上面的代碼在foreground的檢查,如果指明要將service運行于foreground, 那檢查是否允許。
if (forcedStandby || (!r.startRequested && !fgRequired)) {
final int allowed = mAm.getAppStartModeLocked(r.appInfo.uid, r.packageName,
r.appInfo.targetSdkVersion, callingPid, false, false, forcedStandby);
if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
if (allowed == ActivityManager.APP_START_MODE_DELAYED || forceSilentAbort) {
return null;
}
if (forcedStandby) {
if (fgRequired) {
return null;
}
}
UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
由前面可知,foredStandby一般為false, 而 r.startRequested在沒有啟動service時,它也為false. 此時取決于是否是foreground service的請求,如果是fg請求的話。則不會進入啟動模式的檢查,反之,就會進入檢查。 getAppStartModeLocked 在之前Broadcast里有講,現(xiàn)在在看下對于Service是一種什么情況。
int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
UidRecord uidRec = mActiveUids.get(uid);
if (uidRec == null || alwaysRestrict || forcedStandby || uidRec.idle) {
final int startMode = (alwaysRestrict)
? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
: appServicesRestrictedInBackgroundLocked(uid, packageName,
packageTargetSdk);
...
}
從前面可知uidRec為空, alwaysRestrict為false, 進而會進入services restricted的檢查
int appServicesRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Persistent app?
if (mPackageManagerInt.isPackagePersistent(packageName)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
// Non-persistent but background whitelisted?
if (uidOnBackgroundWhitelist(uid)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
// Is this app on the battery whitelist?
if (isOnDeviceIdleWhitelistLocked(uid, /*allowExceptIdleToo=*/ false)) {
return ActivityManager.APP_START_MODE_NORMAL;
}
return appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk);
}
如果service是在persistent的apk里,或者在 mBackgroundAppIdWhitelist 里,這個是background白名單(aosp中僅有com.android.defcontainer這個app才在background whitelist中),或者在baterry白名單中的話,則直接allow, 否則就進入appRestrictedInBackgroundLocked, 而在appRestrictedInBackgroundLocked中,如果是在Android O及以事的版本中,會返回APP_START_MODE_DELAYED_RIGID。也就是不允許。
這就是Android O及以后版本對于background的廣播,service的相關(guān)的限制。
三、bound service
3.1 bindService流程
生命周期onCreate->onBind -> onUnbind -> onDestroy
bindService與當(dāng)前的Context綁定在一起,如果 Context 銷毀了,則Service將會被銷毀,執(zhí)行 onUnbind -> onDestroy
如果BroadcastReceiver是聲明在 AndroidManifest.xml 中的 <receiver>, 則bindService不能在Broadcast里調(diào)用, why?
因為從 <receiver> 里啟動(ActivityThread中的handleReceiver)的BroadcastReceiver的實例是一個局部變量,理論上onReceive后就沒用了,就會被GC掉。
所以如果在這種情況下去bindService,則使用傳入的Context后Service將會導(dǎo)致Receiver不能被回收掉,導(dǎo)致內(nèi)存漏洞。
注: 親自將ReceiverRestrictedContext里的bindService去掉,然后在BroadcastReceiver里bind service,沒有任何異常,除了BroadcastReciver沒有被回收。
另外BroadcastReceiver有10s(fg)/60s(bg)的超時。
注意,此時傳入BroadcastReceiver的Context是ReceiverRestrictedContext, 不是普通的Context,
在ReceiverRestrictedContext中bindService會報異常。
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
throw new ReceiverCallNotAllowedException(
"BroadcastReceiver components are not allowed to bind to services");
}
而如果是通過在代碼中注冊 (registerReceiver) , 這時廣播不會走 ActivityThread 中的handleReceiver, 所以此時 bindService是可行的,而是走的 LoadedApk.ReceiverDispatcher. 此時在代碼中bindService是可以的,因為此時沒有ReceiverRestrictedContext去做check了。
3.2 unbindService
unbindService并不會觸發(fā)Server調(diào)用 onServerDisconnected
, 相反,當(dāng)Server端被殺掉或者crash后就會調(diào)用 onServerDisconnected
函數(shù)通知Client關(guān)于Server掛掉了。
如果有多個Client綁定到Server端,并且不是最后一個Client調(diào)用unbindService的話,則不會觸發(fā)Server的onUnbind和onDestroy, 如果是最后一個Client調(diào)用unbindService, 則會調(diào)用 onUnbind -> onDestroy該Service.
四、Service ANR
AMS在通知App去調(diào)用Service的生命周期函數(shù)時,都會先執(zhí)行一次bumpServiceExecutingLocked.
而該函數(shù)最重要的一個功能就是設(shè)置timeout時間, 即scheduleServiceTimeoutLocked
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
if (proc.executingServices.size() == 0 || proc.thread == null) {
return;
}
Message msg = mAm.mHandler.obtainMessage(
ActivityManagerService.SERVICE_TIMEOUT_MSG);
msg.obj = proc;
mAm.mHandler.sendMessageDelayed(msg,
proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}
如果service運行在foreground,它的timeout時間是SERVICE_TIMEOUT即20s, 如果它運行在background, 它的timeout時間為SERVICE_BACKGROUND_TIMEOUT為200s.
那如果Service在timeout時間內(nèi)處理完了對應(yīng)的操作,ActivityThread會調(diào)用serviceDoneExecuting通知AMS, service已經(jīng)完成處理運動了。
serviceDoneExecuting 會在onCreate/onBind/onStartCommand/onUnbind/onDestroy執(zhí)行后被調(diào)用。
serviceDoneExecuting->serviceDoneExecutingLocked->serviceDoneExecutingLocked
mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
如果service在timeout時間內(nèi)沒有返回,此時調(diào)用serviceTimeout
···
void serviceTimeout(ProcessRecord proc) {
String anrMessage = null;
...
if (anrMessage != null) {
mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
}
}
···
最終觸發(fā) ANR
五、小節(jié)
Android在8.0后加入了對Service的諸多限制。
Debug技巧
Service.apk, StartService.apk
- Service.apk不啟動
Service.apk里調(diào)用startService會啟動成功。
adb shell am start-service -n xxxx/xxx.MyService
Error: app is in background uid
在StartService.apk中調(diào)用startService啟動Service.apk
此時直接throw exception, Erro: app is in backround uid ...
- Service.apk不啟動
adb shell am start-forceground-service -n xxxx/xxx.MyService
啟動成功
StartService.apk中調(diào)用 startForegroundService 啟動成功
- Service.apk已經(jīng)啟動
此時再次啟動Service.apk里的Service都會成功。
- Service.apk已經(jīng)啟動