Android應用框架之Service

之前的博客已經介紹了應用框架中的ActivityApplication,今天來講四大組件之一的Service。對于Service大家肯定都比較熟悉,與Activity最大的不同就是Service不會與界面打交道,而是始終工作在后臺,執行一些與UI無關的操作和計算。即便用戶切換了其他應用,啟動的Service仍可在后臺運行。一個組件可以與Service綁定并與之交互,甚至是跨進程通信(IPC)。
Service運行在主線程中(A service runs in the main thread of its hosting process),Service并不是一個新的線程,也不是新的進程。也就是說,若您需要在Service中執行較為耗時的操作(如播放音樂、執行網絡請求等),需要在Service中創建一個新的線程。這可以防止ANR的發生,同時主線程可以執行正常的UI操作。
Service有兩種啟動方式,一個是startService,一個是bindService,接下來分別介紹一下兩種方式的啟動邏輯。

1.startService

通常情況下啟動一個Service的代碼如下:

Intent intent = new Intent(this, MyService.class);
context.startService(intent);

啟動過程是從Context開始的,而這個Context實際是一個ContextWrapper,而從ContextWrapper的實現看來,其內部實現都是通過ContextImpl來完成的,這是一種典型的橋接模式。通過調用ContextImplstartService,會啟動一個服務,核心代碼如下所示:

private Component startServiceCommon(Intent service, UserHandle user) {
......
Component cn = ActivityManagerNative.getDefault().startService(mMainThread.getApplicationThread(),service,service.resolveTypeIfNeeded(getContentResolver()),user.getIdentifier());
......

ContextImpl通過ActivityManagerNative.getDefault()獲取到一個服務,這個服務就是熟悉的Activity Manager Service(AMS),啟動這個服務的方式當然還是Binder機制。所起啟動Service的工作就轉移到了AMS身上。在AMS的內部還有一個mServices,這個對象是輔助AMS進行service管理的類,包括Service的啟動、綁定和停止等等。同時一個Service在AMS內部對應一個ServiceRecord,AMS用它來記錄各個Service。
而在AMS內部會通過realStartServiceLocked方法來啟動Service,其實在AMS內部的啟動步驟還有還經過了很多方法,不過最為核心的就是realStartServiceLocked,該方法的核心代碼如下:

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
......                  app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
app.thread.scheduleCreateService(r, r.serviceInfo, mMm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState);
r.postNotification();
......
}

這里的app是一個ProcessRecord對象,就是在之前的博客中提到的AMS中用于記錄一個Application的對象。通過app.thread.scheduleCreateService方法來創建Service并調用其onCreate方法,接著在通過sendServiceArgsLocked方法來調用Service的其他方法,比如onStartCommand。而這兩個過程均是進程間通信,app.thread其實是一個IApplicationThread類型,實際就是一個Binder。而scheduleCreateService就是這個binder中的一個接口方法,接下來看一下對應的scheduleCreateService方法:

public final void scheduleCreateService(Binder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(procesState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;

sendMessage(H.CREATE_SERVICE, s);
}

從代碼中可以看到,最后的創建工作又通過發送消息給Handler H將創建Service的工作又回到了ActivityThread中。最后再來看看ActivityThread的handleCreateService

private void handleCreateService(CreateServiceData data){
......
Service service = null;
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service)cl.loadClass(data.info.name).newInstance();
......
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);

Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault());
service.onCreate();
mServices.put(data.token, service);
......
}

這個方法主要做了以下幾件事:

1.通過創建類加載器,創建Service實例
2.創建Application對象,并調用其onCreate方法,當然Application對象只會被創建一次
3.創建ContextImpl對象,并通過service的onAttach方法建立兩者之間的聯系。這個過程和Activity類似,畢竟Activity和Service都是一個Context
4.最后調用Service的onCreate方法,并將Service保存在ActivityThread中的一個列表mServices。

由于Service的onCreate方法被執行了,接下來AcitivtyThread還會通過handleServiceArgs方法調用Service的onStartCommand方法:

private void handleServiceArgs(ServiceData data) {
Service s = mServices.get(data.token);
......
if(!data.taskRemoved) {
    res = s.onStartCommand(data.args, data.flags, data.startId);
} else {
    s.onTaskRemoved(data.args);
    res = Service.START_TASK_REMOVED_COMPLETE;
}
......
ActivityManagerNative.getDefault().serviceDoneExecuting(data.token, 1, data.startId, res);
......
}

在這個方法中可以看到,在service執行完成之后,還會通過ActivityManagerNative.getDefault().serviceDoneExecuting來通知AMS service已經執行完畢。
最后來總結一下Service啟動的主要步驟:

Context-->AMS-->app.thread-->ActivityThread-->Service

為什么要去繞這么一大圈呢?其實很好理解,AMS管理各個組件,要創建一個新的service當然要通過AMS來維護一個與該service對應的實例并與對應的進程實現關聯,app.thread只是一個應用通信的接口,并將對應的工作交接給ActivityThread,ActivityThread才是應用的真正實例,它當然也要管理該Service,并維護一個對應的記錄(mServices)。其實Activity和Service的啟動過程大致相同,從中可以更加了解Android的應用框架。

2.bindService

bindService的大致過程過程和startService類似,還是通過contextImpl.bindServiceCommon來啟動:

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, UserHandle user) {
IServiceConnction sd;
......
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), mMainThread.getHandler(), flags);
......
int res = ActivityManagerNative.getDefault().bindService(mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), sd, flags, user.getIdentifier());
......
}

這里主要做了兩件事:

  • 將客戶端的ServiceConnection轉化為ServiceDispatcher.InnerConnection。之所以不能直接使用ServiceConnection是因為綁定的服務可能是跨進程的,所以必須借助于Binder才能讓遠程服務回調自己的方法。而ServiceDispatcher的內部類InnerConnction正好充當了這個Binder。所以ServiceDispatcher的作用就是ServiceConnection和InnerConnection連接的橋梁。

  • 調用AMS的bindService方法來完成Service的具體綁定過程。

接下來重點講一下AMS的bindService方法。和startService方法類似的是,bindService最終會將調用到app.thread.scheduleBindService():

public final void scheduleBindService(Binder token, Intent intent, boolean rebind, int processState) {
    updateProcessState(processState, false);
    BindServiceData s = new BindServiceData();
    s.token = token;
    s.intent = intent;
    s.rebind = rebind;
    ......
    sendMessage(H.BIND_SERVICE, s);
}

接下來又轉移到了ActivityThread中,而這個方法就是ActivityThread.handleBindService()

private void handleBindService(BindServiceData data) {
    Service s = mServices.get(data.token);
    ......
    IBiner binder = s.onBind(data.intent);               ActivityManagerNative.getDefault().publishService(data.token, data.intent, binder);
    ......
}

handleBindService中,首先根據Service的token取出Service對象,然后調用Service的onBind方法。但是onBind方法是Service的方法,這個時候客戶端并不知道已經綁定成功了,所以還必須調用客戶端的ServiceConnection中的onServiceConnected,這個是由ActivityManagerNative.getDefault().publishService方法來完成的。最終指令流會轉移到mServices(AMS內部的輔助Service)的publishServiceLocked。其核心代碼只有一行:c.conn.connected(r.name, service),其中c.conn類型是ServiceDispatcher.InnerConnection,service就是Service的onBind返回的Binder對象。接下來看看ServiceDispatcher.InnerConnection的定義:

private static class InnerConnection extends IServiceConnection.Stub {
    ...
    private void connected(ComponentName name, Binder service) throws RemoteException {
        LoadedApk.ServiceDispatcher sd = mDispacher.get();
        if(sd != null) {
            sd.connectd(name, service);
        }
    }
}

InnerConnection最后通過ServiceDispatcher的connected方法來調用ServiceConnection的onServiceConnected,至此綁定完成。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容