A Service has two working states, the normal state and the bound state

The normal state is started by the context.startService () function, and the binding state by the context.bindService () function

The life cycle of a Service in the two states is different due to the different startup modes

In this article, we’ll take a look at the two different startup processes and the details we should pay attention to

StartService Overall process

The overall process of startService is mainly managed by AMS, which determines whether the target Service is running first

If the target Service is running, AMS requires AIDL to call the onStartCommand() function of the target Service back to the process in which the target Service is running

If the target Service is not started, you need to determine which process the target Service is running in

If the target Service is to run in the APP process, AMS requires AIDL to call the APP process, calling back the onCreate() and onStartCommand() functions of the target Service

If the target Service is to run in a separate process, AMS needs to access the Zygote process and request that a new process be started to run the target Service. When the target process is started, it is initialized to call back the onCreate() and onStartCommand() functions of the target Service

The overall process is shown as follows:

BindService overall process

The overall process of bindService is mainly managed by AMS. When the target Service is started, AMS will first judge whether the target Service has been bound to the APP process after starting

If the target Service is bound to the APP process after starting, AMS requires AIDL to call the APP process, calling back the onServiceConnected() function of the target ServiceConnection

If the target Service was destroyed the last time it was unbound, AMS requires AIDL to call back the onReBind() function of the target Service by calling the process in which the target Service is running

If the target Service is bound to the APP process for the first time after startup, AMS requires AIDL to call the process where the target Service is running and call the onBind() function of the target Service. At the same time, AIDL calls the APP process, calling back onServiceConnected() of the target ServiceConnection

The overall process is shown as follows:

The source code

StartService process

ContextImpl.startService()

// frameworks/base/core/java/android/app/ContextImpl.java
public ComponentName startService(Intent service)
{
	warnIfCallingFromSystemProcess();
	return startServiceCommon(service, mUser);
}
Copy the code

ContextImpl.startServiceCommon()

// frameworks/base/core/java/android/app/ContextImpl.java
private ComponentName startServiceCommon(Intent service, UserHandle user)
{...// AIDL calls AMS 'startService functionComponentName cn = ActivityManagerNative.getDefault().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), getOpPackageName(), user.getIdentifier()); .return cn;
}
Copy the code

AMS.startService()

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public ComponentName startService(IApplicationThread caller, Intent service,
		String resolvedType, String callingPackage, int userId)
{...// Call ActiveServices' startServiceLocked functionComponentName res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid, callingPackage, userId); .return res;
}
Copy the code

ActiveServices.startServiceLocked()

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
ComponentName startServiceLocked(IApplicationThread caller, Intent service, 
                                 String resolvedType, int callingPid, 
                                 int callingUid, String callingPackage, int userId
){...// Get information about the target Service (ServiceRecord)
    ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg); . ServiceRecord r = res.record; .// Caches information about the target Service as R.endingstarts
    r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), service, neededGrants)); .// Call ActiveServices' startServiceInnerLocked function
    return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}
Copy the code

ActiveServices.startServiceInnerLocked()

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, 
                                      ServiceRecord r, boolean callerFg, 
                                      boolean addToStarting
){...// Call ActiveServices bringUpServiceLocked
    String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false); .return r.name;
}
Copy the code

ActiveServices.bringUpServiceLocked()

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, 
                                          boolean execInFg, boolean whileRestarting
){
    // If the target Service is up and running
    if(r.app ! =null&& r.app.thread ! =null)
    {
        // Calls the onStartCommand function of the target Service
        sendServiceArgsLocked(r, execInFg, false);
        return null; }...// Check whether the target Service is FLAG_ISOLATED_PROCESS (indicating that the Service is running in a separate process)
    final booleanisolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) ! =0;
    final String procName = r.processName;
    ProcessRecord app;

    // If the target Service is to run in the APP process (Isolated = false)
    if(! isolated) {// Call AMS's getProcessRecordLocked,
        // Get the APP process information (ProcessRecord)
        app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);

        // If the APP process is started and running
        if(app ! =null&& app.thread ! =null) {...// Create the target Service instance
            realStartServiceLocked(r, app, execInFg);
            return null; }}else // If the target Service is to run in a separate process
    {
        // Prevent new processes from being started repeatedly
        app = r.isolatedProc;
    }

    // If you need to start a new process to run the Service
    if (app == null)
    {
        // Call AMS's startProcessLocked function,
        // The Zygote process is requested to start a new process to run the target Service
        if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                "service", r.name, false, isolated, false)) = =null)
        {
            // If the new process fails to start
            String msg = "Unable to launch app "
                    + r.appInfo.packageName + "/"
                    + r.appInfo.uid + " for service "
                    + r.intent.getIntent() + ": process is bad";
            // Perform clearing operations, such as unbinding Service and stopping Service
            bringDownServiceLocked(r);
            return msg;
        }
        if (isolated)
        {
            // Records information about newly started processes to prevent repeated startupr.isolatedProc = app; }}// All services that are being started are cached in mPendingServices,
    // If the target Service is not in the mPendingServices cache
    if(! mPendingServices.contains(r)) {// Put information about the target Service into the mPendingServices cache
        mPendingServices.add(r);
    }

    return null;
}
Copy the code

ActiveServices.sendServiceArgsLocked()

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, 
		boolean oomAdjusted
){
    final int N = r.pendingStarts.size();
    R.endingstarts, N > 0;
    // when bindService, N = 0,
    // bindService does not call the onStartCommand function of the target Service
    if (N == 0)
    {
        return;
    }

    / / traverse r.p endingStarts
    while (r.pendingStarts.size() > 0)
    {
        si = r.pendingStarts.remove(0); .// AIDL calls the scheduleServiceArgs function of the process ApplicationThread in which the target Service runsr.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent); . }}Copy the code

ApplicationThread. ScheduleServiceArgs () function sends a message (H.S ERVICE_ARGS) to the inner class H ActivityThread (Handler), Perform ActivityThread. HandleServiceArgs () function

ActivityThread. HandleServiceArgs () function callback the target Service’s onStartCommand () function

ActiveServices.realStartServiceLocked()

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
private final void realStartServiceLocked(ServiceRecord r, 
		ProcessRecord app, boolean execInFg
){...// AIDL calls the scheduleCreateService function of the process ApplicationThread that the target Service runs onapp.thread.scheduleCreateService(r, r.serviceInfo, mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), app.repProcState); .// Calls the onStartCommand function of the target Service
    sendServiceArgsLocked(r, execInFg, true); . }Copy the code

ApplicationThread. ScheduleCreateService () function sends a message (H.C REATE_SERVICE) to the inner class H ActivityThread (Handler), Perform ActivityThread. HandleCreateService () function

// frameworks/base/core/java/android/app/ActivityThread.java
private void handleCreateService(CreateServiceData data)
{... Service service =null;

    // Create a Service instance through reflectionjava.lang.ClassLoader cl = packageInfo.getClassLoader(); service = (Service) cl.loadClass(data.info.name).newInstance(); .// Create a context for this Service
    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);

    Application app = packageInfo.makeApplication(false, mInstrumentation);
    // Initialize parameters
    service.attach(context, this, data.info.name, data.token, app,
            ActivityManagerNative.getDefault());
    // Call the onCreate function of the Service
    service.onCreate();
    // Store the Service in the mServices cachemServices.put(data.token, service); . }Copy the code

AMS.startProcessLocked()

If you need to start the new process to run the target Service, is called ActivityManagerService. StartProcessLocked () function to access the Zygote process request to start a new process is used to run the target Service

When the process in which the target Service is running starts, it is initialized to call the main() function of the ActivityThread

// frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args)
{... thread.attach(false, startSeq); . }private void attach(boolean system, long startSeq)
{...AIDL calls AMS attachApplication
    finalIActivityManager mgr = ActivityManagerNative.getService(); mgr.attachApplication(mAppThread); . }// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public final void attachApplication(IApplicationThread thread)
{... attachApplicationLocked(thread, callingPid); . }private final boolean attachApplicationLocked(IApplicationThread thread, int pid)
{...boolean badApp = false; .if(! badApp) {// Call ActiveServices attachApplicationLockeddidSomething |= mServices.attachApplicationLocked(app, processName); }...return true;
}

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
boolean attachApplicationLocked(ProcessRecord proc, String processName)
{
    boolean didSomething = false;
    // All services that are being started are cached in mPendingServices,
    // If mpendingservices.size () > 0, another Service needs to be started
    if (mPendingServices.size() > 0)
    {
        ServiceRecord sr = null;
        / / traverse mPendingServices
        for (int i=0; i<mPendingServices.size(); i++)
        {
            sr = mPendingServices.get(i);
            // Continue skips the loop if the Service is not running in the process
            if(proc ! = sr.isolatedProc && (proc.uid ! = sr.appInfo.uid || ! processName.equals(sr.processName))) {continue;
            }

            mPendingServices.remove(i);
            i--;
            proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode,
                    mAm.mProcessStats);
            // Create a Service instance
            realStartServiceLocked(sr, proc, sr.createdFromFg);
            didSomething = true; }}...return didSomething;
}
Copy the code

BindService process

ContextImpl.bindService()

// frameworks/base/core/java/android/app/ContextImpl.java
public boolean bindService(Intent service, ServiceConnection conn, int flags)
{
    warnIfCallingFromSystemProcess();
    return bindServiceCommon(service, conn, flags, Process.myUserHandle());
}
Copy the code

ContextImpl.bindServiceCommon()

// frameworks/base/core/java/android/app/ContextImpl.java
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
		UserHandle user
){ IServiceConnection sd; .// APK packet information is not empty, if hit
    if(mPackageInfo ! =null)
    {
        // Call LoadedApk's getServiceDispatcher function,
        // Obtain ServiceConnection's Binder communication interface from the mServices cachesd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), mMainThread.getHandler(), flags); }...// AIDL calls AMS bindService
    intres = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), sd, flags, getOpPackageName(), user.getIdentifier()); .returnres ! =0;
}
Copy the code

All available (registered) Service information is stored in the mServices cache

MServices is an ArrayMap with Context as the key and ServiceDispatcher as the value

You can think of ServiceDispatcher as a wrapper around ServiceConnection and its Binder communication interface

AMS.bindService()

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public int bindService(IApplicationThread caller, IBinder token, 
		Intent service, String resolvedType, IServiceConnection connection, 
		int flags, String callingPackage, int userId
){...// Call ActiveServices' bindServiceLocked function
    return mServices.bindServiceLocked(caller, token, service,
            resolvedType, connection, flags, callingPackage, userId);
}
Copy the code

ActiveServices.bindServiceLocked()

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
int bindServiceLocked(IApplicationThread caller, IBinder token, 
		Intent service, String resolvedType, IServiceConnection connection, 
		int flags, String callingPackage, int userId
){...// Get information about the target Service (ServiceRecord)
    ServiceLookupResult res =
            retrieveServiceLocked(service, resolvedType, callingPackage,
                    Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg); . ServiceRecord s = res.record; .// Get the binding record between the target Service and the APP process (AppBindRecord)
    // If the target Service is bound to the APP process for the first time, a new AppBindRecord is created and savedAppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); .// If flag is set to BIND_AUTO_CREATE
    if((flags&Context.BIND_AUTO_CREATE) ! =0)
    {
        s.lastActivity = SystemClock.uptimeMillis();
        // Call ActiveServices bringUpServiceLocked to launch the target Service
        if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null)
        {
            // If the target Service fails to start, return directly
            return 0; }}...// If the target Service has been bound to the APP process after starting (received = true), if is hit
    if(s.app ! =null && b.intent.received)
    {
        // AIDL calls the connected function of InnerConnection in the APP process
        c.conn.connected(s.name, b.intent.binder);

        // If the target Service was destroyed the last time it unbound (doRebind = true),
        // Call the onReBind function of the target Service
        if (b.intent.apps.size() == 1 && b.intent.doRebind)
        {
            requestServiceBindingLocked(s, b.intent, callerFg, true); }}// If the target Service is first bound to the APP process after startup (requested = false),
    // else if hit, call the target Service's onBind function
    else if(! b.intent.requested) { requestServiceBindingLocked(s, b.intent, callerFg,false); }...return 1;
}
Copy the code

ActiveServices.requestServiceBindingLocked()

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, 
		boolean execInFg, boolean rebind
){
    // If the target Service is still not started, return directly
    if (r.app == null || r.app.thread == null)
    {
        return false;
    }

    // If must hit
    if((! i.requested || rebind) && i.apps.size() >0) {...// AIDL calls the scheduleBindService function of the process ApplicationThread in which the target Service runs
        r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                r.app.repProcState);
        if(! rebind) { i.requested =true;
        }

        i.doRebind = false; . }return true;
}
Copy the code

ApplicationThread.scheduleBindService()

// frameworks/base/core/java/android/app/ActivityThread$ApplicationThread.java
public final void scheduleBindService(IBinder token, Intent intent, 
		boolean rebind, int processState
){
    BindServiceData s = new BindServiceData();
    s.token = token;
    s.intent = intent;
    s.rebind = rebind;

    // Send a message (h.bin_service) to the inner class H in ActivityThread (Handler)
    sendMessage(H.BIND_SERVICE, s);
}
Copy the code

H.handleMessage()

// frameworks/base/core/java/android/app/ActivityThread$H.java
public void handleMessage(Message msg)
{
    switch (msg.what)
    {
        ......

        case BIND_SERVICE:
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
            handleBindService((BindServiceData)msg.obj);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break; . }... }Copy the code

ActivityThread.handleBindService()

// frameworks/base/core/java/android/app/ActivityThread.java
private void handleBindService(BindServiceData data)
{
    // Find the target Service in the mServices cache
    Service s = mServices.get(data.token);

    // If the target Service is started, then the target Service must be cached in the mServices.
    // that is, s is not empty, if hit
    if(s ! =null) {...// If the target Service is bound to the APP process for the first time after startup, if is hit
        if(! data.rebind) {// Call the target Service's onBind function
            IBinder binder = s.onBind(data.intent);
            // AIDL calls AMS publishService
            ActivityManagerNative.getDefault().publishService(
                    data.token, data.intent, binder);
        }
        // If the target Service was bound to the APP process after starting and was destroyed the last time it was unbound, else hit
        else
        {
            // Call the onRebind function of the target Services.onRebind(data.intent); . }... }}Copy the code

AMS.publishService()

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void publishService(IBinder token, Intent intent, IBinder service)
{...// Call the publishServiceLocked function for ActiveServices
    mServices.publishServiceLocked((ServiceRecord)token, intent, service);
}

// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service)
{.../ / if hit
    if(r ! =null) {... IntentBindRecord b = r.bindings.get(filter);// If the target Service is first bound to the APP process after starting (received = false)
        if(b ! =null && !b.received)
        {
            b.binder = service;
            b.requested = true;
            b.received = true;
            // connections caches all the ServiceConnection bound to the target Service.
            // Walk through connections to find the ServiceConnection for this binding
            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);
                    // Compare intEnts. If the intent is different, the current ServiceConnection does not correspond to the binding.
                    // continue skips this loop
                    if(! filter.equals(c.binding.intent.intent)) {continue; }...// AIDL calls the connected function of InnerConnection in the APP processc.conn.connected(r.name, service); . }}}... }... }// frameworks/base/core/java/android/app/LoadedApk$ServiceDispatcher$InnerConnection.java
public void connected(ComponentName name, IBinder service)
{
    // Get ServiceDispatcher (weak references to prevent memory leaks)
    LoadedApk.ServiceDispatcher sd = mDispatcher.get();
    if(sd ! =null) { sd.connected(name, service); }}// frameworks/base/core/java/android/app/LoadedApk$ServiceDispatcher.java
public void connected(ComponentName name, IBinder service)
{
    // mActivityThread = main thread Handler, not null, if hit
    if(mActivityThread ! =null)
    {
        // Send messages to the inner class H in ActivityThread (Handler)
        // Switch execution from Binder thread to main thread
        mActivityThread.post(new RunConnection(name, service, 0));
    }
    else{... }}// frameworks/base/core/java/android/app/LoadedApk$ServiceDispatcher$RunConnection.java
public void run(a)
{
    // mCommand = 0, if hit
    if (mCommand == 0)
    {
        doConnected(mName, mService);
    }
    else if (mCommand == 1) {... }}// frameworks/base/core/java/android/app/LoadedApk$ServiceDispatcher.java
public void doConnected(ComponentName name, IBinder service)
{...// service is not empty, if hit
    if(service ! =null)
    {
        // Call back the onServiceConnected function of the target ServiceConnectionmConnection.onServiceConnected(name, service); }}Copy the code

conclusion

This article looks at two different startup processes, startService and bindService, and looks at some of the code that makes sense to us as developers

Understanding these two startup processes helps us greatly in understanding the operation and life cycle of a Service

But the source code is still very large, there is no way to cover everything, I hope readers can refer to this article to try to read the source code, deepen understanding