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

来源:http://www.cnblogs.com/ghylzwsb/archive/2017/07/16/service.html
-Advertisement-
Play Games

七月中旬了,大家的實習有著落了嗎?秋招又準備的怎麼樣了呢?我依舊在準備著秋招,每當想到自己以應屆生的身份找著工作而工作卻不一定要你的時候,難免也會有點失落。互聯網行業的大佬們求賢若渴但對賢才也十分的苛刻,看到內推正如火如荼的進行著,深怕自己被這場浪潮甩在身後,所以也不得不苦心的準備著。如果你也是20... ...


歡迎訪問我的個人博客 ,原文鏈接:http://wensibo.top/2017/07/16/service/ ,未經允許不得轉載!

七月中旬了,大家的實習有著落了嗎?秋招又準備的怎麼樣了呢?我依舊在準備著秋招,每當想到自己以應屆生的身份找著工作而工作卻不一定要你的時候,難免也會有點失落。互聯網行業的大佬們求賢若渴但對賢才也十分的苛刻,看到內推正如火如荼的進行著,深怕自己被這場浪潮甩在身後,所以也不得不苦心的準備著。如果你也是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
接下來我們就看看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

realStartServiceLocked

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

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哈哈~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天看的《JavaScript設計模式》-作者:張容銘。主要看了js繼承。下麵我將看的,以及代碼貼出來,跟大家一起學習,分享。共同進步。 先來個簡單是 單繼承 多繼承 類繼承 是通過子類的原型prototype對父類實例化來實現的 缺點:父類中的共有屬性如果是引用類型就會在子類的所有實例中共擁有,一 ...
  • JavaWeb01_html basic html操作思想 使用標簽把要操作的數據包起來,通過修改標簽的屬性值,來實現標簽內數據樣式的變化 font標簽 屬性:size取值範圍1-7 color:英文單詞,十六進位數 #ffffff 標題標簽 <h1> </h1> ... <h6> </h6> 從h ...
  • 綱要 =============================== 計劃佈局,劃分整體結構 內容區域,從整體到局部,局部中的通用部分,根據上下文應用樣式 公共頭部(public header)、尾部(public footer) 公共容器(public container/inner center ...
  • [1]概述 [2]入門實例 [3]生成器 [4]HTTP模塊 [5]中間件 [6]托管靜態資源 [7]常用中間件 [8]路由 [9]路由器實例 [10]響應方法 [11]請求方法 [12]APP方法 [13]HTTPS [14]模板引擎 [15]資料庫 [16]上傳文件 [17]開發實例 ...
  • 一,工程圖。 二,代碼。 ViewController.m ...
  • Google 更新了最新的 Support Library 版本,其中最為顯眼的功能莫過於 support-v4 大拆分,然後這個拆分現在看來並沒有那麼美好。 v4 包從 2011 年開始引入,包含 ViewPager、FragmentActivity 等我們常用的功能,目前已經達到 1.3 M,G ...
  • 原作者:現在是實踐所有已經學習到Kotlin技術,以及充分利用它提供功能的時候。如果你還有任何疑問,在本文就給你一些做出最終決定的理由。 ...
  • 打開app/src/main/AndroidManifest。 1.註冊當前活動。通過<activity android:name>標簽註冊當前活動,Android studio會自動註冊,eclipse需要手動註冊。.MainActivity其中 . 表示包名,在上面package(包)中已經註冊 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...