從原始碼的角度看 Service 是如何啟動的

溫斯渤發表於2019-02-28

歡迎掘金的小夥伴們訪問我的個人部落格 ,原文連結:wensibo.top/2017/07/16/…

七月中旬了,大家的實習有著落了嗎?秋招又準備的怎麼樣了呢?我依舊在準備著秋招,每當想到自己以應屆生的身份找著工作而工作卻不一定要你的時候,難免也會有點失落。網際網路行業的大佬們求賢若渴但對賢才也十分的苛刻,看到內推正如火如荼的進行著,深怕自己被這場浪潮甩在身後,所以也不得不苦心的準備著。如果你也是2018屆應屆生,如果你也看到了這篇文章,請你在留言區留下你找工作,準備秋招的感受,我們一起交流交流。
今天接著上篇文章一起來看看四大元件的老二——Service。話不多說我們開始吧!

前言

我們一般使用Service有兩種方式,startService和bindService,這兩種方法使用場景各有不同,本篇文章以startService為例講解Service的啟動過程,而bindService大體上與startService相近,只是一些邏輯呼叫上有所區別。
在這裡我先貼上通過本次分析得到的Service完整的啟動流程圖,現在不需要理解其中的過程,只需要一步步分析原始碼的時候回過頭來看看這幅圖,以免迷失方向。當然我在每一步都會貼出相對應的流程圖。

總體流程圖
總體流程圖

認識ContextImpl

首先先給出一張類圖,我們從大局上看一下這些類的關係。

類圖
類圖

從上面這張圖我們可以看到Activity繼承了ContextWrapper類,而在ContextWrapper類中,實現了startService方法。在ContextWrapper類中,有一個成員變數mBase,它是一個ContextImpl例項,而ContextImpl類和ContextWrapper類一樣繼承於Context類。為什麼會給出這張圖呢?這對我們接下來的分析十分有用。

啟動Service的入口

我們在Activity中使用startService(Intent service)來啟動一個服務,其呼叫的方法如下:

//ContextWrapper類
@Override
public ComponentName startService(Intent service) {
    return mBase.startService(service);
}複製程式碼

可以看到其呼叫了ContextWrapper的startService方法,而這個方法內部使用mBase的startService方法,我們再看回剛才的類圖,可以看到,這裡的mBase其實就是ContextImpl,從而我們可以得出ContextWrapper類的startService方法最終是通過呼叫ContextImpl類的startService方法來實現的。那接下來就來看看ContextImpl.startService()。

@Override
public ComponentName startService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, mUser);
}

private ComponentName startServiceCommon(Intent service, UserHandle user) {
    try {
        validateServiceIntent(service);
        service.prepareToLeaveProcess(this);
        ComponentName cn = ActivityManagerNative.getDefault().startService(
            mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                        getContentResolver()), getOpPackageName(), user.getIdentifier());
        if (cn != null) {
            if (cn.getPackageName().equals("!")) {
                throw new SecurityException(
                        "Not allowed to start service " + service
                        + " without permission " + cn.getClassName());
            } else if (cn.getPackageName().equals("!!")) {
                throw new SecurityException(
                        "Unable to start service " + service
                        + ": " + cn.getClassName());
            }
        }
        return cn;
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}複製程式碼

我們可以看到startService方法其實是呼叫了startServiceCommon,而startServiceCommon做了些什麼呢?我們看到了一個熟悉的傢伙:ActivityManagerNative.getDefault(),在上篇文章分析Activity啟動的時候我們也看過他,並且我們也知道ActivityManagerNative.getDefault()返回的就是一個ActivityManagerProxy物件,這裡使用Binder機制(如果你對Binder機制不是很瞭解,那可以看一下我之前寫的這篇文章)將代理Proxy返回給客戶端,而客戶端通過將引數寫入Proxy類,接著Proxy就會通過Binder去遠端呼叫服務端的具體方法,因此,我們只是借用ActivityManagerProxy來呼叫ActivityManagerService的方法。所以這裡其實是呼叫了遠端ActivityManagerService的startService方法。
到這裡我們先用流程圖來看一下目前Service啟動的情況

流程圖1
流程圖1

接下來我們就看看ActivityManagerService是如何實現的。

AMS如何進一步啟動Service

//ActivityManagerService類
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
        String resolvedType, int userId) {
    //...
    synchronized(this) {
        final int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        ComponentName res = mServices.startServiceLocked(caller, service,
                resolvedType, callingPid, callingUid, userId);
        Binder.restoreCallingIdentity(origId);
        return res;
    }
}複製程式碼

可以看到這裡呼叫了mService的startServiceLocked方法,那mService是幹嘛的呢?此處的mServices是一個ActiveServices物件,從名字上也能看出該類主要是封裝了一些處於活動狀態的service元件的方法的呼叫。那接下來就看看他的startServiceLocked是如何實現的。

ComponentName startServiceLocked(IApplicationThread caller,
        Intent service, String resolvedType,
        int callingPid, int callingUid, int userId) {
    //...
    final boolean callerFg;
    if (caller != null) {
        //mAm 是ActivityManagerService.
        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
        if (callerApp == null) {
            throw new SecurityException(
                    "Unable to find app for caller " + caller
                    + " (pid=" + Binder.getCallingPid()
                    + ") when starting service " + service);
        }
        callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
    } else {
        callerFg = true;
    }
    ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, 
                     callingPid, callingUid, userId, true, callerFg);
    if (res == null) {
        return null;
    }
    if (res.record == null) {//許可權拒絕.
        return new ComponentName("!", res.permission != null
                ? res.permission : "private to package");
    }

    ServiceRecord r = res.record;
    //
    r.lastActivity = SystemClock.uptimeMillis();
    r.startRequested = true;
    r.delayedStop = false;
    r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
            service, neededGrants));

    final ServiceMap smap = getServiceMap(r.userId);
    boolean addToStarting = false;
    //...
    return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}複製程式碼

程式碼稍微有點長,但是最重要的邏輯在於最後一句的startServiceInnerLocked方法,他的內部實現是這樣的。

ComponentName startServiceInnerLocked(ServiceMap smap, Intent service,
        ServiceRecord r, boolean callerFg, boolean addToStarting) { 
    //...
    //真正開啟service的地方。
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
    //注意此處反回值是null的時候,證明沒有異常.
    if (error != null) {
        return new ComponentName("!!", error);
    }
    //...
    return r.name;
}複製程式碼

startServiceInnerLocked方法內部呼叫了bringUpServiceLocked方法來進行後續的啟動。

private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
    boolean whileRestarting) throws TransactionTooLargeException {

    //...
    if (app != null && app.thread != null) {
      try {
        app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
        realStartServiceLocked(r, app, execInFg);
        return null;
      } 

    //...

    return null;
}複製程式碼

在bringUpServiceLocked方法中呼叫了realStartServiceLocked方法,在Activity的啟動過程中我們也曾看過相似的方法,說明到了這裡我們也快看到真正的Service啟動了,接著來看realStartServiceLocked。

private final void realStartServiceLocked(ServiceRecord r,
    ProcessRecord app, boolean execInFg) throws RemoteException {

  //...

  boolean created = false;
  try {

    //...
    app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
    app.thread.scheduleCreateService(r, r.serviceInfo,
        mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
        app.repProcState);
    r.postNotification();
    created = true;
  } catch (DeadObjectException e) {
    Slog.w(TAG, "Application dead when creating service " + r);
    mAm.appDiedLocked(app);
    throw e;
  } 

  //這裡會呼叫Service的onStartCommand
  sendServiceArgsLocked(r, execInFg, true);

  //...

}複製程式碼

我們先用流程圖看看Service啟動的邏輯。接著在下面會著重講解上方的realStartServiceLocked方法。

流程圖2
流程圖2

realStartServiceLocked

這裡需要對上面的app.thread做一下特殊的說明。如果你已經瞭解了Binder的通訊機制,那你應該知道一般我們的服務都是由客戶端向服務端發出請求,接著服務端向客戶端返回結果,這個是單向的通訊,但是如果反過來服務端要向客戶端傳送請求的話,那麼同樣的,在服務端也應該持有另外一個Proxy,而在客戶端也同樣需要一個Manager與之對應。
在這裡app是要執行 Service 的程式對應的ProcessRecord物件,代表一個應用程式,而thread是一個ApplicationThreadProxy物件,它執行在AMS(現在AMS就是客戶端了),而與之對應的服務端則是在應用程式中的ApplicatonThread,還是有點繞,我們用一張圖來展示他們的關係。

兩對Binder
兩對Binder

相信通過上面的圖示大家應該能夠明白應用程式程式與系統服務程式之間的雙向通訊了吧!

還需要再嘮叨一下ApplicationThread與ApplicationThreadProxy之間的關係,我們通過這兩個類的定義可以更深刻的理解他們的關係

  • IApplicationThread
public interface IApplicationThread extends IInterface複製程式碼

IApplicationThread其實是一個IBinder型別的介面。並且在這個介面中宣告瞭許多與Activity,Service生命週期相關的方法,那麼它的實現類又是誰呢?答案就是ApplicationThreadNative

  • ApplicationThreadNative
public abstract class ApplicationThreadNative extends Binder implements IApplicationThread複製程式碼

可以看到ApplicationThreadNative是一個抽象類,我們不能直接建立其物件,應該使用其子類,而恰好ApplicationThread就是其子類

  • ApplicationThread
private class ApplicationThread extends ApplicationThreadNative複製程式碼

ApplicationThread就是真正意義上的服務端,它的父類ApplicationThreadNative就是將具體的操作將給它來執行的。

  • ApplicationThreadProxy
class ApplicationThreadProxy implements IApplicationThread複製程式碼

說了那麼多,在客戶端執行的ApplicationThreadProxy在哪裡呢?其實如果理解了Binder機制,那麼我們應該知道他就是ApplicationThreadNative的內部類,客戶端(AMS)就是通過它與service所在的程式進行通訊的。因此我們接著要看的當然是ApplicationThread的scheduleCreateService了。

ApplicationThread.scheduleCreateService

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

            sendMessage(H.CREATE_SERVICE, s);
        }複製程式碼

可以看到在scheduleCreateService方法中傳送了一個message,其訊息型別為CREATE_SERVICE,它是在H類中定義的一個常量,而H類其實就是繼承於Handler的,它專門用來處理髮送過來的請求。接下來就來看看它是如何處理建立service這個訊息的。

H.handleMessage

public void handleMessage(Message msg) {
    switch (msg.what) {
        ...
        case CREATE_SERVICE:
            handleCreateService((CreateServiceData)msg.obj); //【見流程15】
            break;
        case BIND_SERVICE:
            handleBindService((BindServiceData)msg.obj);
            break;
        case UNBIND_SERVICE:
            handleUnbindService((BindServiceData)msg.obj);
            break;
        ...
    }
}複製程式碼

可以看到handleMessage處理了很多型別的訊息,包括service的建立、繫結、解綁、銷燬等等,我們直接看建立的邏輯,也就是handleCreateService方法。不過有一個問題,那就是Handler建立之前必須要建立Looper,否則會報錯,那麼Looper是在哪裡建立的呢?答案就是ActivityThread的main方法。

public static void main(String[] args) {
        //...
        //在主執行緒建立Looper
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }複製程式碼

我們可以看到Looper就是在這裡建立的,而Handler也是在Service程式中的主執行緒,也就是說它處理訊息也是在主執行緒,那麼Service的建立自然也就是在主執行緒中。可是ActivityThread是什麼鬼呢?其實剛才的ApplicationThread就是它的內部類。
接下來繼續看Handler如何處理訊息。

private void handleCreateService(CreateServiceData data) {
    unscheduleGcIdler();
    LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo);

    java.lang.ClassLoader cl = packageInfo.getClassLoader();
    //通過反射建立目標服務物件
    Service service = (Service) cl.loadClass(data.info.name).newInstance();
    ...

    try {
        //建立ContextImpl物件
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        context.setOuterContext(service);
        //建立Application物件
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManagerNative.getDefault());
        //呼叫服務onCreate()方法
        service.onCreate();
        mServices.put(data.token, service);
        //呼叫服務建立完成
        ActivityManagerNative.getDefault().serviceDoneExecuting(
                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
    } catch (Exception e) {
        ...
    }
}複製程式碼

在這裡Handler通過反射機制拿到Service物件,於是就呼叫了service的onCreate方法,所以Service就算是啟動了。我們通過層層的尋找總算是見到了onCreate的廬山真面目。
我們依舊用流程圖來展示一下Service啟動的全過程。

流程圖3
流程圖3

onStartCommand

可是還有另外一個重要的方法onStartCommand呢?不急,我們剛才在sendServiceArgsLocked方法中還有另外一個sendServiceArgsLocked方法沒有講到,他就是onStartCommand的入口,我們看看他是如何實現的。

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) {
        final int N = r.pendingStarts.size();
        if (N == 0) {
            return;
        }

        while (r.pendingStarts.size() > 0) {
            try {
                //與呼叫scheduleCreateService方法一樣,遠端呼叫ApplicationThread的scheduleServiceArgs方法
                r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
            } catch (RemoteException e) {
                // Remote process gone...  we`ll let the normal cleanup take
                // care of this.
                if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r);
                break;
            } catch (Exception e) {
                Slog.w(TAG, "Unexpected exception", e);
                break;
            }
        }
    }複製程式碼

我們看到這裡依舊呼叫了遠端服務端ApplicationThread的方法來執行後面的邏輯,其實通過上面分析onCreate的邏輯大家應該能夠知道呼叫onStartCommand也是大同小異的,具體的呼叫邏輯就交給大家去分析啦!

後記

到這裡呢,我們就把Service的啟動流程完整的講解了一遍,希望大家通過這篇文章能夠對Service更加的瞭解,畢竟四大元件中Service的地位還是舉足輕重的,並且在面試中也會經常被問到,希望大家逢問必會O(∩_∩)O哈哈~

相關文章