1. Why the Service has not been destroyed after calling stopService/unbindService?

Through the analysis of the process for Service destroyed before, stopService and unbindService can eventually into ActiveServices bringDownServiceIfNeededLocked method, This method determines whether the current Service needs to be destroyed, and the core needed method isService required.

private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn, boolean hasConn) {
    // Are we still explicitly being asked to run?
    if (r.startRequested) {
        return true;
    }

    // Is someone still bound to us keepign us running?
    if(! knowConn) { hasConn = r.hasAutoCreateConnections(); }if (hasConn) {
        return true;
    }

    return false;
}
Copy the code

There are two key variables: ServiceRecord startRequested and hasConn, the former is associated with the start, the latter is associated with the bind, can only be destroyed a Service both to false. Let’s take a look at startRequested

ServiceRecord.startRequested

Through global search, found that the field only in ActiveServices. StartServiceLocked method, namely the start process will be set to true. In ActiveServices. StopServiceLocked, ActiveServices. StopServiceTokenLocked, ActiveServices killServicesLocked of the three methods will be set to false , ActiveServices stopServiceTokenLocked is in Service call stopSelf triggered when, while ActiveServices. KillServicesLocked is applied in cleaning up trigger (out of memory, etc.).

Simply ServiceRecord. StartRequested will be in the start process is set to true, set to false in stop process. Therefore, no matter how many times you have previously invoked startService, if you have invoked stopService once (and have not invoked startService again), startRequested is set to false. ** The value of startRequested depends on whether startService or stopService is last invoked.

hasConn

The value of the field with ServiceRecord. HasAutoCreateConnection methods return values

public boolean hasAutoCreateConnections(a) {
    // XXX should probably keep a count of the number of auto-create
    // connections directly in the service.
    for (int conni=connections.size()-1; conni>=0; conni--) {
        ArrayList<ConnectionRecord> cr = connections.valueAt(conni);
        for (int i=0; i<cr.size(); i++) {
            // This flags is the flags used when calling bindService
            if((cr.get(i).flags&Context.BIND_AUTO_CREATE) ! =0) {
                return true; }}}return false;
}
Copy the code

This method internally iterates through all bind connections to the current service and returns true if any connection exists and the flags used to call bindService contain the BIND_AUTO_CREATE flag, otherwise returns false.

conclusion

Let’s look at a specific scenario for how to destroy a service:

  1. Just use thestartServiceTo start the service. In this scenario, you just callstopServiceThe service can be destroyed normally
  2. Just use thebindServiceIn the scenario of starting a service, you only need to call the correspondingunbindServiceYou can,
  3. At the same timestartServiceandbindServiceTo shut down the service in this scenario, the first call is calledstopServiceSecondly, you also need to ensure that you use it beforeBIND_AUTO_CREATEClient unbinding for binding (unbindService).

2. Why bindServcie is called multiple times while onBind only fires once

There is a realStartServiceLocked method in the Service startup process, which is called to continue the Service startup process after the Service process has been started. RealStartServiceLocked internal calls a method called requestServiceBindingsLocked bind request processing. Repaste the code for this method:

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);
        / / this method inside will pass across processes call ApplicationThread. ScheduleBindService
        // Call the service. onBind method back and forth
        if(! requestServiceBindingLocked(r, ibr, execInFg,false)) {
            break; }}}Copy the code

You can see that there is a for loop here, which indicates that it is possible for service.onbind to be called back multiple times. The question then becomes when can servicerecord.Bindings store multiple values? Put the bindings field operation occurs only in retrieveAppBindingLocked method, this method is in the bind process ActiveServices. BindServiceLocked method is invoked. Paste the code

public AppBindRecord retrieveAppBindingLocked(Intent Intent,// An Intent ProcessRecord app used by the client to initiate a bind request) {// Record the client process
    Intent.FilterComparison filter = new Intent.FilterComparison(intent);
    IntentBindRecord i = bindings.get(filter);
    if (i == null) {
        i = new IntentBindRecord(this, filter);
        bindings.put(filter, i);
    }
    AppBindRecord a = i.apps.get(app);
    if(a ! =null) {
        return a;
    }
    a = new AppBindRecord(this, i, app);
    i.apps.put(app, a);
    return a;
}
Copy the code

As you can see, the method wraps the Intent into a FilterComparison object as a key, retrieves it in bindings, and creates a value if there is no corresponding value. Also look at the FilterComparison. Equals method, because bindings will only hold multiple values if different FilterComparison instances are created.

//Intent$FilterComparison.java
public boolean equals(Object obj) {
    if (obj instanceof FilterComparison) {
        Intent other = ((FilterComparison) obj).mIntent;
        return mIntent.filterEquals(other);
    }
    return false;
}

//Intent.java
public boolean filterEquals(Intent other) {
    if (other == null) {
        return false;
    }
    if(! Objects.equals(this.mAction, other.mAction)) return false;
    if(! Objects.equals(this.mData, other.mData)) return false;
    if(! Objects.equals(this.mType, other.mType)) return false;
    if(! Objects.equals(this.mPackage, other.mPackage)) return false;
    if(! Objects.equals(this.mComponent, other.mComponent)) return false;
    if(! Objects.equals(this.mCategories, other.mCategories)) return false;

    return true;
}
Copy the code

As you can see, FilterComparison comparisons are closely related to intEnts. When any of the fields in the Intent’s mAction, mData, mType, mPackage, mComponent, and mCategories change, two different FilterComparison instances are created.

conclusion

When calling a bindService, changing some values inside the Intent can trigger service.onbind multiple times.

analyse

With that conclusion in mind, let’s revisit the problem of bindService using the same Intent multiple times. We usually construct intents in the following way

Intent intent = new Intent(activity, DemoService.class);

//Intent.java
public Intent(Context packageContext, Class
        cls) {
    mComponent = new ComponentName(packageContext, cls);
}
Copy the code

Initializing the Intent this way ends up saving the constructor’s entry as mComponent.

After entering the BIND process for the first time, calling retrieveAppBindingLocked will definitely generate a new IntentBindRecord for Bindings. At this time if the service is started, it will immediately enter requestServiceBindingLocked method

private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
            boolean execInFg, boolean rebind) throws TransactionTooLargeException {
    / /...
    //requested is false
    if((! i.requested || rebind) && i.apps.size() >0) {
        try {
            / /...
            r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                    r.app.repProcState);
            if(! rebind) {// Requested is set to true after onBind is triggered
                i.requested = true;
            }
            i.hasBound = true;
            i.doRebind = false;
        } catch (TransactionTooLargeException e) {
            / /...
        } catch (RemoteException e) {
            / /...}}return true;
}
Copy the code

If bind is requested with the same Intent, then the second request will already be true and service. onBind will not be triggered.