Service啟動流程

更木小八發表於2018-11-07

文中的原始碼版本為api23

Service啟動流程

啟動Service的方式有兩種:startServicebindService

startService

先祭上一張流程圖看個大概

Service啟動流程
此圖省略了一些程式間通訊的細節,同時假設Service程式已經啟動

Context.startService會經歷以下呼叫鏈 Context.startService-> ContextImpl.startService-> ContextImpl.startServiceCommon

ContextImpl.startServiceCommon

private ComponentName startServiceCommon(Intent service, UserHandle user) {
    try {
        //做一些校驗,如Android L版本及以上不允許隱式Intent啟動Service
        validateServiceIntent(service);
        service.prepareToLeaveProcess();
        
        //呼叫AMS的startService
        ComponentName cn = ActivityManagerNative.getDefault().startService(
            mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                        getContentResolver()), getOpPackageName(), user.getIdentifier());
        if (cn != null) {
            //一些異常判斷
        }
        return cn;
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
}
複製程式碼

該方法最核心的作用是將啟動Service的請求通過IPC傳送給了AMS

AMS接收到請求之後會經歷以下方法呼叫 ActivityManagerService.startService-> ActiveService.startServiceLocked

ActiveService.startServiceLocked

ComponentName startServiceLocked(IApplicationThread caller, Intent service,
                                     String resolvedType,//mimetype
            int callingPid, int callingUid, String callingPackage, int userId)
            throws TransactionTooLargeException {
    //...
    //記錄啟動Service的程式是否處於前臺
    final boolean callerFg;
    if (caller != null) {
        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
        if (callerApp == null) {
            //...
        }
        callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;
    } else {
        callerFg = true;
    }


    //根據Intent資訊查詢ServiceLookupResult
    //ServiceLookupResult由ServiceRecord以及Service對應的permisson組成
    ServiceLookupResult res =
        retrieveServiceLocked(service, resolvedType, callingPackage,
                callingPid, callingUid, userId, true, callerFg);
    //...
    r.lastActivity = SystemClock.uptimeMillis();
    r.startRequested = true;
    r.delayedStop = false;
    //儲存請求資料,服務啟動之後會遍歷pendingStarts,取出StartItem
    //拿裡面的資料觸發Service.onStartCommand
    r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
            service, neededGrants));

    //ServiceMap與userId一一對應,儲存服務資訊
    final ServiceMap smap = getServiceMap(r.userId);
    //是否新增到服務啟動列表中
    //只有是後臺程式發起呼叫,且目標程式未啟動或者state>=PROCESS_STATE_SERVICE
    //才為true
    boolean addToStarting = false;
    //延遲啟動的判斷
    if (!callerFg//呼叫方處於後臺,可能會延遲啟動Service
            && r.app == null //ServiceRecord未被分配程式
            && mAm.mStartedUsers.get(r.userId) != null) {
        //獲取應用程式例項
        ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false);
        if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) {
            //...
            if (r.delayed) {
                // This service is already scheduled for a delayed start; just leave
                // it still waiting.
                if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Continuing to delay: " + r);
                return r.name;
            }
            if (smap.mStartingBackground.size() >= mMaxStartingBackground) {
                // Something else is starting, delay!
                Slog.i(TAG_SERVICE, "Delaying start of: " + r);
                smap.mDelayedStartList.add(r);
                r.delayed = true;
                return r.name;
            }
            if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "Not delaying: " + r);
            addToStarting = true;
        } else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) {
            addToStarting = true;
            //...
        } else if (DEBUG_DELAYED_STARTS) {
            //debug資訊
        }
    } else if (DEBUG_DELAYED_STARTS) {
        //debug資訊
    }

    return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);

複製程式碼

ServiceMap這個結構用於儲存使用者當前執行的服務資訊,retrieveServiceLocked方法邏輯就是通過IntentServiceMap中查詢Service,如果當前服務還未啟動,那麼會建立一個ServiceRecord物件,儲存在ServiceMap中。 還有一個需要注意的點是後臺服務以及延遲啟動機制,所有的後臺服務都會被新增至一個名為mStartingBackground的列表中(新增邏輯在startServiceInnerLocked方法中),如果當前後臺服務超過閾值,那麼服務將會被新增至延遲啟動列表中。

ActiveService.startServiceInnerLocked

ComponentName startServiceInnerLocked(ServiceMap smap, Intent service,
        ServiceRecord r,
        boolean callerFg,//服務啟動者是否是前臺程式
        boolean addToStarting) //是否新增到後臺服務列表中
        throws TransactionTooLargeException {
    //...
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
    if (error != null) {
        return new ComponentName("!!", error);
    }
    
    //將服務新增至後臺服務列表中
    if (r.startRequested && addToStarting) {
        boolean first = smap.mStartingBackground.size() == 0;
        smap.mStartingBackground.add(r);
        r.startingBgTimeout = SystemClock.uptimeMillis() + BG_START_TIMEOUT;
        //...
        if (first) {
            smap.rescheduleDelayedStarts();
        }
    } else if (callerFg) {
        smap.ensureNotStartingBackground(r);
    }

    return r.name;
}
複製程式碼

該方法主要是呼叫了bringUpServiceLocked來進行後續的服務啟動功能,同時會判斷addToStarting值決定是否將服務新增至後臺服務列表中。

ActiveService.bringUpServiceLocked

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

    //服務程式已經啟動,且服務也已啟動,分發onStartCommand事件
    if (r.app != null && r.app.thread != null) {
        sendServiceArgsLocked(r, execInFg, false);
        return null;
    }

    //...

    final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
    final String procName = r.processName;
    ProcessRecord app;

    //應用程式已經啟動,呼叫realStartServiceLocked,返回
    if (!isolated) {
        app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
        if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
                    + " app=" + app);
        if (app != null && app.thread != null) {
            try {
                app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
                realStartServiceLocked(r, app, execInFg);
                return null;
            } catch (TransactionTooLargeException e) {
                throw e;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting service " + r.shortName, e);
            }

            // If a dead object exception was thrown -- fall through to
            // restart the application.
        }
    } else {
        app = r.isolatedProc;
    }
   
    //啟動服務程式
    if (app == null) {
        if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                "service", r.name, false, isolated, false)) == null) {
            String msg = "Unable to launch app "
                    + r.appInfo.packageName + "/"
                    + r.appInfo.uid + " for service "
                    + r.intent.getIntent() + ": process is bad";
            Slog.w(TAG, msg);
            bringDownServiceLocked(r);
            return msg;
        }
        if (isolated) {
            r.isolatedProc = app;
        }
    }

    //程式啟動需要時間,先將服務儲存起來
    if (!mPendingServices.contains(r)) {
        mPendingServices.add(r);
    }

    //...

    return null;
}
複製程式碼

這個方法根據服務程式的狀態以及服務的狀態產生了幾個分支

  1. 如果服務程式已經啟動,且服務也已經啟動,那麼呼叫sendServiceArgsLocked方法觸發Service.onStartCommand
  2. 如果服務還未啟動,且不是以isotate模式啟動,那麼呼叫ActivityManagerService.getProcessRecordLocked查詢是否有可用程式,如果有就呼叫realStartServiceLocked啟動服務
  3. 服務未啟動,且服務的程式也沒啟動,那麼呼叫ActivityManagerService.startProcessLocked啟動程式,同時將服務儲存至待啟動列表中,等待程式啟動完畢之後繼續下面的流程。

sendServiceArgsLocked這個方法在realStartServiceLocked中也會被呼叫,我們到時候再分析。

關於程式啟動,大家可以參考其他文章,這裡不去展開講。 程式啟動之後,最終還是會進入到realStartServiceLocked方法中,方法呼叫鏈如下 ActivityThread.main-> ActivityThread.attach-> ActivityManagerService.attachApplication-> ActivityManagerService.attachApplicationLocked-> ActiveService.attachApplicationLocked-> ActiveService.realStartServiceLocked

下面我們就來分析一下ActiveService.realStartServiceLocked

ActiveService.realStartServiceLocked

private final void realStartServiceLocked(ServiceRecord r,
            ProcessRecord app, boolean execInFg) throws RemoteException {
    //...
    boolean created = false;
    try {
        //...
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
        r.postNotification();
        created = true;
    } catch (DeadObjectException e) {
        //...
    } finally {
        //...
    }

    //step2
    requestServiceBindingsLocked(r, execInFg);

    //...

    //step3
    sendServiceArgsLocked(r, execInFg, true);

    //...
}
複製程式碼

這個方法我做了很多精簡,我們只關注一些最核心的部分

  1. 跨程式呼叫ApplicationThrad.scheduleCreateService方法,最終會走到ActivityThread.handleCreateService中,該方法通過反射建立一個Service的例項,並呼叫其onCreate方法,這部分的程式碼並不複雜,大家可以自行分析。
  2. 呼叫requestServiceBindingsLocked處理bindService請求,我們放到bindService啟動流程去講
  3. 呼叫sendServiceArgsLocked處理startService請求

下面我們分析一下sendServiceArgsLocked

ActiveService.sendServiceArgsLocked

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

    while (r.pendingStarts.size() > 0) {
        Exception caughtException = null;
        ServiceRecord.StartItem si;
        try {
            si = r.pendingStarts.remove(0);
            //...
            si.deliveredTime = SystemClock.uptimeMillis();
            r.deliveredStarts.add(si);
            si.deliveryCount++;
            //...
            r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
        } catch (TransactionTooLargeException e) {
           //...
        } catch (RemoteException e) {
            //...
        } catch (Exception e) {
            //...
        }

        //...
    }
}
複製程式碼

還記得pendingStarts麼?在ActiveService.startServiceLocked方法中,ActiveService將啟動服務的請求的資訊儲存在pendingStarts中。 sendServiceArgsLocked的邏輯非常簡單,就是遍歷pendingStarts,將所有的start請求傳送出去。 傳送的過程有涉及到了程式間通訊,呼叫了ApplicationThrad.scheduleServiceArgs方法,該方法最終會進入到ActivityThread.handleServiceArgs中,觸發Service.onStartCommand

至此,startService的流程就結束了。

bindService

bindService的大致流程如下

Service啟動流程
圖中的流程是基於應用程式未啟動的場景進行繪製的

Context.bindService會經歷以下呼叫鏈 Context.bindService-> ContextImpl.bindService-> ContextImpl.bindServiceCommon

ContextImpl.bindServiceCommon

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
            UserHandle user) {
    IServiceConnection sd;
    if (conn == null) {
        throw new IllegalArgumentException("connection is null");
    }
    if (mPackageInfo != null) {
        //bindService成功之後會回撥給ServiceConnection一個binder物件
        //用於後續的C/S通訊,AMS直接通過IServiceConnection介面進行回撥
        //由於ServiceConnection不是aidl介面,因此需要進行一定的包裝
        sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
                mMainThread.getHandler(), flags);
    } else {
        throw new RuntimeException("Not supported in system context");
    }
    validateServiceIntent(service);
    try {
        //...
        service.prepareToLeaveProcess();
        int res = ActivityManagerNative.getDefault().bindService(
            mMainThread.getApplicationThread(), getActivityToken(), service,
            service.resolveTypeIfNeeded(getContentResolver()),
            sd, flags, getOpPackageName(), user.getIdentifier());
        if (res < 0) {
            //...
        }
        return res != 0;
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
}
複製程式碼

bindServiceCommon的主要職責

  1. 呼叫LoadedApk.getServiceDispatcher是包裝ServiceConnection成一個IServiceConnection物件,後續AMS可以直接通過該物件釋出客戶端與服務端進行通訊的Binder物件。這個方法就不展開講了。
  2. 跨程式呼叫ActivityManagerService.bindService方法繼續服務啟動流程

ActivityManagerService.bindService會直接呼叫ActiveService.bindServiceLocked

ActiveService.bindServiceLocked

int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
            String resolvedType, IServiceConnection connection, int flags,
            String callingPackage, int userId) throws TransactionTooLargeException {
    //bind請求發起程式
    final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
    //...

    //請求發起程式是否處於前臺
    final boolean callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE;

    ServiceLookupResult res =
        retrieveServiceLocked(service, resolvedType, callingPackage,
                Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
    //...
    ServiceRecord s = res.record;

    final long origId = Binder.clearCallingIdentity();

    try {
        //...

        //下面這些操作是在AMS中建立起客戶端和服務端的繫結關係
        //主要是儲存客戶端的一些資訊到ServiceRecord中
        //儲存服務端的一些資訊到客戶端(ActivityRecord、ProcessRecord)中
        
        //retrieveAppBindingLocked方法執行完畢之後會在ServiceRecord的bindings
        //欄位中新增一條記錄。bindings是Map型別,值型別為IntentBindRecord
        AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
        ConnectionRecord c = new ConnectionRecord(b, activity,
                connection, flags, clientLabel, clientIntent);

        IBinder binder = connection.asBinder();
        ArrayList<ConnectionRecord> clist = s.connections.get(binder);
        if (clist == null) {
            clist = new ArrayList<ConnectionRecord>();
            s.connections.put(binder, clist);
        }
        clist.add(c);
        b.connections.add(c);
        if (activity != null) {
            if (activity.connections == null) {
                activity.connections = new HashSet<ConnectionRecord>();
            }
            activity.connections.add(c);
        }
        b.client.connections.add(c);
        
        //...

        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
            s.lastActivity = SystemClock.uptimeMillis();
            if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
                return 0;
            }
        }

        //...

        if (s.app != null && b.intent.received) {
            // Service is already running, so we can immediately
            // publish the connection.
            try {
                //conn就是客戶端建立的IServiceConnection物件
                c.conn.connected(s.name, b.intent.binder);
            } catch (Exception e) {
                Slog.w(TAG, "Failure sending service " + s.shortName
                        + " to connection " + c.conn.asBinder()
                        + " (in " + c.binding.client.processName + ")", e);
            }

            //...
        } else if (!b.intent.requested) {
            requestServiceBindingLocked(s, b.intent, callerFg, false);
        }

        //...

    } finally {
        //...
    }

    return 1;
}
複製程式碼

該方法的核心有兩點

  1. 呼叫bringUpServiceLocked啟動服務
  2. 呼叫IServiceConnection.connected方法釋出用於客戶端與服務端進行通訊的binder物件

看似簡單,但是如果將BIND_AUTO_CREATE標誌以及bringUpServiceLocked方法內部邏輯分支,場景會變得稍微複雜一些。這裡我對各個場景下Service.onBindServiceConnection.onServiceConnected的呼叫時機做一下簡單分析

設定BIND_AUTO_CREATE標誌,服務未啟動(包括程式未啟動)

由於設定了BIND_AUTO_CREATE標誌,接下來就會進入到bringUpServiceLocked中。該方法在分析startService流程的時候我們分析過。如果程式已經啟動則會進入到realStartServiceLocked中,如果程式未啟動,那麼待程式啟動之後通過程式間通訊最終還是會走到realStartServiceLocked方法中。 realStartServiceLocked方法內會通過程式間通訊,通知服務程式建立Service例項,並呼叫onCreate方法,之後會呼叫requestServiceBindingsLocked方法處理bind請求,這個方法之前特意留到這裡進行分析。

ActiveService.requestServiceBindingsLocked
private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
            throws TransactionTooLargeException {
    for (int i=r.bindings.size()-1; i>=0; i--) {
        IntentBindRecord ibr = r.bindings.valueAt(i);
        if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
            break;
        }
    }
}
複製程式碼

該方法很簡單,遍歷ServiceRecord.bindings的值,並挨個兒呼叫requestServiceBindingLocked

ActiveService.requestServiceBindingLocked
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
            boolean execInFg, boolean rebind) throws TransactionTooLargeException {
    //...
    if ((!i.requested || rebind) && i.apps.size() > 0) {
        try {
            //...
            r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                    r.app.repProcState);
            //...
        } catch (TransactionTooLargeException e) {
            //...
        } catch (RemoteException e) {
            //...
        }
    }
    return true;
}
複製程式碼

requestServiceBindingLocked也很簡單,通過Binder通訊,呼叫ApplicationThread.scheduleBindService方法,該方法在經過一些列呼叫之後,Service.onBind就會被呼叫。

ServiceConnection.onServiceConnected的回撥則是在Service.onBind方法被呼叫之後。 ActivityThread在調完Service.onBind之後,會回撥AMS,最終進入到ActiveService.publishServiceLocked

ActiveService.publishServiceLocked
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
    final long origId = Binder.clearCallingIdentity();
    try {
        //...
        if (r != null) {
            Intent.FilterComparison filter
                    = new Intent.FilterComparison(intent);
            IntentBindRecord b = r.bindings.get(filter);
            if (b != null && !b.received) {
                //...
                for (int conni=r.connections.size()-1; conni>=0; conni--) {
                    ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);
                    for (int i=0; i<clist.size(); i++) {
                        ConnectionRecord c = clist.get(i);
                        //...
                        try {
                            c.conn.connected(r.name, service);
                        } catch (Exception e) {
                            //...
                        }
                    }
                }
            }
            //...
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
}
複製程式碼

publishServiceLocked呼叫了IServiceConnection.connected,之後會經過程式間通訊以及IServiceConnection實現類的內部邏輯,最終呼叫ServiceConnection.onServiceConnected方法。

case 2 : 設定BIND_AUTO_CREATE標誌,服務已啟動

此時bringUpServiceLocked會馬上返回,繼續bindServiceLocked的流程

ActiveService.bindServiceLocked
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
            String resolvedType, IServiceConnection connection, int flags,
            String callingPackage, int userId) throws TransactionTooLargeException {
    //...

    try {
        //...

        if (s.app != null && b.intent.received) {
            // Service is already running, so we can immediately
            // publish the connection.
            try {
                //conn就是客戶端建立的IServiceConnection物件
                c.conn.connected(s.name, b.intent.binder);
            } catch (Exception e) {
                Slog.w(TAG, "Failure sending service " + s.shortName
                        + " to connection " + c.conn.asBinder()
                        + " (in " + c.binding.client.processName + ")", e);
            }

            //...
        } else if (!b.intent.requested) {
            requestServiceBindingLocked(s, b.intent, callerFg, false);
        }

        //...

    } finally {
        //...
    }

    return 1;
}
複製程式碼

這裡要注意的有兩個欄位IntentBindRecord.requestedIntentBindRecord.received,它們會影響接下來的流程走向。

  • IntentBindRecord.requested 是否已經發起了Application.scheduleBindService呼叫,通知服務程式回撥Service.onBind方法。
  • IntentBindRecord.received 該欄位如果為true,說明Service.onBind方法已經被回撥

而此時由於還沒有呼叫ApplicationThread.scheduleBindService,因此receivedfalse,requested也是false,這時走requestServiceBindingLocked方法。 後續的流程,則跟case 1是一樣的。

case 3 : 未設定BIND_AUTO_CREATE,服務已啟動

如果未設定BIND_AUTO_CREATE,那麼在bind流程中是不會呼叫bringUpServiceLocked啟動服務的。而此時服務已經啟動,那麼可能是之前有呼叫過startService啟動過服務。 此時receivedfalse,requested也是false,直接走requestServiceBindingLocked,跟case 2是一樣的

case 4 : 未設定BIND_AUTO_CREATE,服務未啟動

這種場景就需要等待服務啟動了,服務啟動的機制是下一次的startCommand被呼叫或者帶有BIND_AUTO_CREATE標誌的bindService被呼叫。無論程式是否啟動,最終都會走到realStartServiceLocked,後續流程case 1一樣。

至此,bindService的流程就分析完畢了。

相關文章