One, the introduction

As one of the four major components, Service is second only to Activity in common development. However, when we generate a Service, we generally do not override its onStartCommand method. What does the return value of this method mean, and why does the Android system give us such a method? Let’s look for the answer from the source code.

Second, source code analysis

ActiveService realStartServiceLocked () {ActiveService realStartServiceLocked () {ActiveService realStartServiceLocked ();

private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException { ... Omit Boolean created =false; try { ... Omit the mAm. NotifyPackageUse (r.s erviceInfo packageName, PackageManager. NOTIFY_PACKAGE_USE_SERVICE); 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; } finally { ... Omit}if (r.whitelistManager) {
        app.whitelistManager = true;
    }

    requestServiceBindingsLocked(r, execInFg);

    updateServiceClientActivitiesLocked(app, null, true);

    if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
        r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                null, null, 0));
    }

    sendServiceArgsLocked(r, execInFg, true);

    ...省略
}
Copy the code

The method through the app. Thread. ScheduleCreateService (app. The thread is ActivityThread object) to create a Service, and then call the ActiveService sendServiceArgsLocked method, The code is as follows:

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
        boolean oomAdjusted) throws TransactionTooLargeException {
    final int N = r.pendingStarts.size();
    if (N == 0) {
        return; } ArrayList<ServiceStartArgs> args = new ArrayList<>(); . Omit ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args); slice.setInlineCountLimit(4); Exception caughtException = null; try { r.app.thread.scheduleServiceArgs(r, slice); } catch (TransactionTooLargeException e) { ... Ellipsis} catch (RemoteException e) {... Ellipsis} catch (Exception e) {... Omit}... Omit}Copy the code

SendServiceArgsLocked method calls in the end of state Richard armitage pp. Thread. ScheduleServiceArgs method, namely ActivityThread scheduleServiceArgs method, the code is as follows:

public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
    List<ServiceStartArgs> list = args.getList();

    for(int i = 0; i < list.size(); i++) { ServiceStartArgs ssa = list.get(i); ServiceArgsData s = new ServiceArgsData(); s.token = token; s.taskRemoved = ssa.taskRemoved; s.startId = ssa.startId; s.flags = ssa.flags; s.args = ssa.args; sendMessage(H.SERVICE_ARGS, s); }}Copy the code

ScheduleServiceArgs uses the message sending mechanism to send a message with the flag H. service_args to ActivityThread H. The process logic of h. service_args is as follows:

case SERVICE_ARGS:
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
    handleServiceArgs((ServiceArgsData)msg.obj);
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    break;
Copy the code

In the case of SERVICE_ARGS in handleMessage, the handleServiceArgs method is called. The code for handleServiceArgs is as follows:

private void handleServiceArgs(ServiceArgsData data) {
    Service s = mServices.get(data.token);
    if(s ! = null) { try {if(data.args ! = null) { data.args.setExtrasClassLoader(s.getClassLoader()); data.args.prepareToEnterProcess(); } int res;if(! data.taskRemoved) { res = s.onStartCommand(data.args, data.flags, data.startId); }else {
                s.onTaskRemoved(data.args);
                res = Service.START_TASK_REMOVED_COMPLETE;
            }

            QueuedWork.waitToFinish();

            try {
                ActivityManager.getService().serviceDoneExecuting(
                        data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            ensureJitEnabled();
        } catch (Exception e) {
            if(! mInstrumentation.onException(s, e)) { throw new RuntimeException("Unable to start service " + s
                        + " with " + data.args + ":"+ e.toString(), e); }}}}Copy the code

The above method explicitly calls the onStartCommand method of the Service, yielding an integer return value res, which we will examine in this article. Can see this return value res ended up as a ActivityManager. GetService () serviceDoneExecuting method of parameters, The ActivityManager. GetService () is ActivityManagerService object, including serviceDoneExecuting method is as follows:

public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {
    synchronized(this) {
        if(! (token instanceof ServiceRecord)) { Slog.e(TAG,"serviceDoneExecuting: Invalid service token=" + token);
            throw new IllegalArgumentException("Invalid service token");
        }
        mServices.serviceDoneExecutingLocked((ServiceRecord)token, type, startId, res); }}Copy the code

Call again in the serviceDoneExecuting method ActiveServices serviceDoneExecutingLocked method, as shown below:

void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) {
    boolean inDestroying = mDestroyingServices.contains(r);
    if(r ! = null) {if (type == ActivityThread.SERVICE_DONE_EXECUTING_START) {
            // This is a call from a service start...  take care of
            // book-keeping.
            r.callStart = true;
            switch (res) {
                case Service.START_STICKY_COMPATIBILITY:
                case Service.START_STICKY: {
                    // We are done with the associated start arguments.
                    r.findDeliveredStart(startId, true);
                    // Don't stop if killed. r.stopIfKilled = false; break; } case Service.START_NOT_STICKY: { // We are done with the associated start arguments. r.findDeliveredStart(startId, true); if (r.getLastStartId() == startId) { // There is no more work, and this service // doesn't want to hang around if killed.
                        r.stopIfKilled = true;
                    }
                    break;
                }
                case Service.START_REDELIVER_INTENT: {
                    // We'll keep this item until they explicitly // call stop for it, but keep track of the fact // that it was delivered. ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); if (si ! = null) { si.deliveryCount = 0; si.doneExecutingCount++; // Don't stop if killed.
                        r.stopIfKilled = true;
                    }
                    break;
                }
                case Service.START_TASK_REMOVED_COMPLETE: {
                    // Special processing for onTaskRemoved().  Don't // impact normal onStartCommand() processing. r.findDeliveredStart(startId, true); break; } default: throw new IllegalArgumentException( "Unknown service start result: " + res); } if (res == Service.START_STICKY_COMPATIBILITY) { r.callStart = false; }}... Omit the final long origId = Binder. ClearCallingIdentity (); serviceDoneExecutingLocked(r, inDestroying, inDestroying); Binder.restoreCallingIdentity(origId); } else { Slog.w(TAG, "Done executing unknown service from pid " + Binder.getCallingPid()); }}Copy the code

In serviceDoneExecutingLocked approach, for the value of the parameters of the res did different processing:

  • START_STICKY_COMPATIBILITY calls ServiceRecord’s findDeliveredStart method to remove the current Intent and set stopIfKilled to false. View the START_STICKY_COMPATIBILITY comment, which is described as follows:

It functions as a compatible version of START_STICKY, but there is no guarantee that the onStartCommand method will be called.

  • START_STICKY calls ServiceRecord’s findDeliveredStart method to remove the current Intent and sets stopIfKilled to false. View the START_STICKY comment, which is described as follows:

After a Service is started, if the Service is killed, the system recreates the Service. The onStartCommand method is called, but the intent passed to the Service is null. This mode is suitable for music playing in the background with Service.

  • START_NOT_STICKY calls ServiceRecord’s findDeliveredStart method to remove the current Intent and sets stopIfKilled to true. View the START_NOT_STICKY comment as follows:

If a Service is killed after it is started, the system does not recreate the Service. Unless the developer explicitly starts the Service as startService. This mode applies to operations where a Service is used to store some data, and it is acceptable for the operation to stop when the system kills the Service.

  • START_REDELIVER_INTENT calls ServiceRecord’s findDeliveredStart method without removing the current Intent and setting stopIfKilled to true. Check out the START_REDELIVER_INTENT comment, which reads:

After a Service is started, if the Service is killed by the system, the system recreates the Service. The onStartCommand method is called and the last Intent is passed in via the onStartCommand method. The Service is not stopped unless the developer explicitly stops it using the stopSelf method.

Res of value in the parameter after different treatment, call the serviceDoneExecutingLocked method, a global search of ServiceRecord stopIfKilled reference value, Only ActiveService’s killServicesLocked method references it as follows:

final void killServicesLocked(ProcessRecord app, boolean allowRestart) { ... omitfor (int i=app.services.size()-1; i>=0; i--) {
        ServiceRecord sr = app.services.valueAt(i);

        // Unless the process is persistent, this process record is going away,
        // so make sure the service is cleaned out of it.
        if(! app.persistent) { app.services.removeAt(i); } // Sanity check:if the service listed for the app is not one
        // we actually are maintaining, just let it drop.
        final ServiceRecord curRec = smap.mServicesByName.get(sr.name);
        if(curRec ! = sr) {if(curRec ! = null) { Slog.wtf(TAG,"Service " + sr + " in process " + app
                        + " not same as in map: " + curRec);
            }
            continue;
        }

        // Any services running in the application may need to be placed
        // back in the pending list.
        if (allowRestart && sr.crashCount >= mAm.mConstants.BOUND_SERVICE_MAX_CRASH_RETRY
                && (sr.serviceInfo.applicationInfo.flags
                    &ApplicationInfo.FLAG_PERSISTENT) == 0) {
            Slog.w(TAG, "Service crashed " + sr.crashCount
                    + " times, stopping: " + sr);
            EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH,
                    sr.userId, sr.crashCount, sr.shortName, app.pid);
            bringDownServiceLocked(sr);
        } else if(! allowRestart || ! mAm.mUserController.isUserRunningLocked(sr.userId, 0)) { bringDownServiceLocked(sr); }else {
            boolean canceled = scheduleServiceRestartLocked(sr, true);

            // Should the service remain running?  Note that in the
            // extreme case of so many attempts to deliver a command
            // that it failed we also will stop it here.
            if (sr.startRequested && (sr.stopIfKilled || canceled)) {
                if (sr.pendingStarts.size() == 0) {
                    sr.startRequested = false;
                    if(sr.tracker ! = null) { sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),
                                SystemClock.uptimeMillis());
                    }
                    if(! sr.hasAutoCreateConnections()) { // Whoops, no reason to restart! bringDownServiceLocked(sr); } } } } }if(! allowRestart) { app.services.clear(); // Make sure there are no more restarting servicesfor this process.
        for (int i=mRestartingServices.size()-1; i>=0; i--) {
            ServiceRecord r = mRestartingServices.get(i);
            if(r.processName.equals(app.processName) && r.serviceInfo.applicationInfo.uid == app.info.uid) { mRestartingServices.remove(i); clearRestartingIfNeededLocked(r); }}for (int i=mPendingServices.size()-1; i>=0; i--) {
            ServiceRecord r = mPendingServices.get(i);
            if (r.processName.equals(app.processName) &&
                    r.serviceInfo.applicationInfo.uid == app.info.uid) {
                mPendingServices.remove(i);
            }
        }
    }

    // Make sure we have no more records on the stopping list.
    int i = mDestroyingServices.size();
    while (i > 0) {
        i--;
        ServiceRecord sr = mDestroyingServices.get(i);
        if (sr.app == app) {
            sr.forceClearTracker();
            mDestroyingServices.remove(i);
            if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "killServices remove destroying " + sr);
        }
    }

    app.executingServices.clear();
}
Copy the code

KillServicesLocked does not understand the stopIfKilled logic of ServiceRecord and does not match the comments in Service. Later there is time to continue to study, if any friends know, also please comment, thank you!

Third, summary

This paper analyzes the return value of Service onStartCommand from the point of view of source code and the source code annotation, and analyzes that different return values have different effects on the reconstruction of Service. A small Demo is written to verify this conclusion. OnStartCommand = onStartCommand ()