Android Service

本文基于 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)。如下圖所示,

圖1 service在pkms中的數(shù)據(jù)類型

如圖中所示,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啟動流程

圖2 flow_startService

其中 retriveServiceLocked 函數(shù),主要去建立如下的關(guān)系圖


圖3retriveServiceLocked

從圖中可以看出,可以通過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流程

bindService類圖

flow_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.


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

    1. 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 ...
    1. Service.apk不啟動
  adb shell am start-forceground-service -n xxxx/xxx.MyService
啟動成功
StartService.apk中調(diào)用 startForegroundService 啟動成功
    1. Service.apk已經(jīng)啟動
      此時再次啟動Service.apk里的Service都會成功。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內(nèi)容