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:
- Just use the
startService
To start the service. In this scenario, you just callstopService
The service can be destroyed normally - Just use the
bindService
In the scenario of starting a service, you only need to call the correspondingunbindService
You can, - At the same time
startService
andbindService
To shut down the service in this scenario, the first call is calledstopService
Secondly, you also need to ensure that you use it beforeBIND_AUTO_CREATE
Client 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.