Today we’ll look at the Service binding principle and fill in the holes I dug in the Service Startup Process.
BindService: bindService: bindService: bindService: bindService
Workflow of bindService
Take a look at the overall sequence diagram:
- The application makes a bindService call to AMS
- If Service is not started, start the Service
- If AMS does not hold a Binder handle to Service
- AMS first requests a Binder handle from the Service
- The Service publishes its own Binder handle to AMS upon receiving the request
- AMS will call back Binder handles to the application
- An application with a Binder handle can make a Binder call to a Service
Now that we understand the flow of bindService, let’s look at how the relevant lifecycle callbacks work in the bindService flow
ServiceConnection
The absolute protagonist of this article, bindService complete method is as follows:
public abstract boolean bindService(@RequiresPermission Intent service,
@NonNull ServiceConnection conn, @BindServiceFlags int flags);
Copy the code
We need to pass in a non-empty ServiceConnection to listen for connection status when we bind the Service
public interface ServiceConnection {
// When connecting, AMS will eventually call the Binder handle (IBinder) back and forth through this connection.
void onServiceConnected(ComponentName name, IBinder service);
void onServiceDisconnected(ComponentName name);
}
Copy the code
Follow the code call to bindService:
Context.bindService(...)
-> ContextImpl.bindService(...)
-> ContextImpl.bindServiceCommon(...)
private boolean bindServiceCommon(Intent service, ServiceConnection conn, ...) {
// ServiceConnection is a normal interface that cannot be used for IPC
IServiceConnection sd; // Encapsulate a Binder object and pass it to AMSsd = mPackageInfo.getServiceDispatcher(conn, ...) ;// Make a request to AMSActivityManager.getService().bindService(... , service, sd,...) }Copy the code
Let’s look at the relationship between IServiceConnection and ServiceConnection
// mPackageInfo.getServiceDispatcher : LoadApk.java
public final IServiceConnection getServiceDispatcher(ServiceConnection c, Context context,...) {
ServiceDispatcher sd = null;
// Get the ServiceDispatcher cache for the context
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);
if(map ! =null) {
// Fetch the cache according to ServiceConnection
// IServiceConnection corresponds to (context, ServiceConnection)
// The same connection of the same context corresponds to the same IServiceConnection
sd = map.get(c);
}
if (sd == null) {
sd = new ServiceDispatcher(c, context, handler, flags);
// Cache
if (map == null) {
map = new ArrayMap<>();
mServices.put(context, map);
}
map.put(c, sd);
}
// Finally return IServiceConnection
return sd.getIServiceConnection();
}
// sd.getIServiceConnection(); Here IServiceConnection is initialized in the sd constructor
ServiceDispatcher(ServiceConnection conn,...) {
mIServiceConnection = new InnerConnection(this);
mConnection = conn; // Holds a reference to ServiceConnection
}
private static class InnerConnection extends IServiceConnection.Stub {
final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;
InnerConnection(LoadedApk.ServiceDispatcher sd) {
mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
}
public void connected(ComponentName name, IBinder service, boolean dead) {
LoadedApk.ServiceDispatcher sd = mDispatcher.get();
if(sd ! =null) { // The AMS call ends up heresd.connected(name, service, dead); }}}Copy the code
In sd. Connected directly or indirectly invoked the ServiceDispatcher. DoConnected:
public void doConnected(ComponentName name, IBinder service, boolean dead) {
ServiceDispatcher.ConnectionInfo old;
ServiceDispatcher.ConnectionInfo info;
// Fetch the old Service Binder object according to name
old = mActiveConnections.get(name);
if(old ! =null && old.binder == service) {
// Huh, already have this one. Oh well!
return;// Return to avoid repeating onServiceConnected calls
}
if(service ! =null) {
// A new service is being connected... set it all up.
info = new ConnectionInfo();
info.binder = service;
// Listen for the death of the service. If the service hangs, the onServiceDisconnected method will be called after receiving a callback
info.deathMonitor = new DeathMonitor(name, service);
service.linkToDeath(info.deathMonitor, 0);
mActiveConnections.put(name, info);
} else {
// The named service is being disconnected... clean up.
mActiveConnections.remove(name);
}
if(old ! =null) {
old.binder.unlinkToDeath(old.deathMonitor, 0);
}
// If there was an old service, it is now disconnected.
if(old ! =null) {
// Service binders do not change. = null
// The IBinder service normally passed in when this method is called is empty, triggering the disconnected callback
mConnection.onServiceDisconnected(name);
}
// If there is a new viable service, it is now connected.
if(service ! =null) { mConnection.onServiceConnected(name, service); }}Copy the code
In the previous section, we looked at the ServiceConnection callback, noting that onServiceDisconnected will only callback if the IBinder Service fails. OnServiceDisconnected will not be called back when we actively call unBind.
AMS bindService processing
Without further ado, let’s look at the code:
ContextImpl.bindServiceCommon(...)
-> ActivityManager.getService().bindService(...)
-> AMS.bindService(...)
-> ActiveServices.bindServiceLocked(...)
int bindServiceLocked(IApplicationThread caller,...) {
if((flags&Context.BIND_AUTO_CREATE) ! =0) {
// This method, as mentioned in the previous article, starts the Service when needed
bringUpServiceLocked(s,...)
}
ServiceRecord s = res.record;
AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
if(s.app ! =null && b.intent.received) {
// Service is already running, so we can immediately
// publish the connection. (Service is ready, directly callback onServiceConnected)
c.conn.connected(s.name, b.intent.binder, false);
// If this is the first app connected back to this binding,
// and the service had previously asked to be told when
// rebound, then do so.
if (b.intent.apps.size() == 1 && b.intent.doRebind) {
/ / callback service. OnRebind ()
requestServiceBindingLocked(s, b.intent, callerFg, true);
}
// If doRebind is false or apps.size > 1, nothing is done
} else if(! b.intent.requested) {// If AMS does not request a Binder object, it requests a Binder object from service
requestServiceBindingLocked(s, b.intent, callerFg, false); }}Copy the code
AMS requests Binder objects from the Service
To get straight to the above method:
private final boolean requestServiceBindingLocked(ServiceRecord r, ... .boolean rebind) {
if (r.app == null || r.app.thread == null) {
// If service is not currently running, can't yet bind.
return false;
}
if((! i.requested || rebind) && i.apps.size() >0) {
// Bind to Service (onBind/onRebind)r.app.thread.scheduleBindService(r, rebind, ...) ;if(! rebind) { i.requested =true;
}
/** hasBound: Set when we still need to tell the service all clients are unbound. */
OnUnbind is called only after scheduleBindService is unbound
i.hasBound = true;
i.doRebind = false;
}
return true;
}
Copy the code
The application of the Service receives a request:
// ActivityThread.java
private void handleBindService(BindServiceData data) {
Service s = mServices.get(data.token);
if(! data.rebind) {// Get Binder objects through Service and publish them to AMS
IBinder binder = s.onBind(data.intent);
ActivityManager.getService().publishService(
data.token, data.intent, binder);
} else {
/ / onRebind callbacks.onRebind(data.intent); }}Copy the code
Binder objects for Services are published to AMS
ActivityManagerService.publishService()
-> ActiveServices.publishServiceLocked()
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
Intent.FilterComparison filter = new Intent.FilterComparison(intent);
IntentBindRecord b = r.bindings.get(filter);
if(b ! =null && !b.received) {
b.binder = service;
b.requested = true;// The tag requests a Binder object
b.received = true;// The tag has received the Binder object
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);
if(! filter.equals(c.binding.intent.intent)) {continue;
}
// Find all connection records that match the intent
// Distribute Service binders to them so that the application receives the callback
c.conn.connected(r.name, service, false); }}}}Copy the code
At this point, the complete bindService process and code details that we listed at the beginning have been combed out.
unBindService
Having said the process of binding, let’s add the process of unbinding:
// ActiveServices.java
boolean unbindServiceLocked(IServiceConnection connection) {
IBinder binder = connection.asBinder();
// Retrieve the list of IServiceConnection
ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);
while (clist.size() > 0) { // Iterate, remove
ConnectionRecord r = clist.get(0);
removeConnectionLocked(r, null.null); // Remove operation
if (clist.size() > 0 && clist.get(0) == r) {
// In case it didn't get removed above, do it now.
Slog.wtf(TAG, "Connection " + r + " not removed for binder " + binder);
clist.remove(0);
}
return true;
}
void removeConnectionLocked(ConnectionRecord c,...){...if(s.app ! =null&& s.app.thread ! =null && b.intent.apps.size() == 0
&& b.intent.hasBound) {
// The onUnbind method does not need to be called in the future
b.intent.hasBound = false;
// The service is still there, and the process it belongs to is still there. No process binds the service with the intent
b.intent.doRebind = false; // Call onRebind the next time the intent is bound
// Tell the service to call back onUnbinds.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent()); }}Copy the code
Service calls onUnBind:
// ActivityThread.java
public final void scheduleUnbindService(IBinder token, Intent intent) {
BindServiceData s = new BindServiceData();
s.token = token;
s.intent = intent;
// Send a message to the main thread to execute unbind
sendMessage(H.UNBIND_SERVICE, s);
}
private void handleUnbindService(BindServiceData data) {
Service s = mServices.get(data.token);
boolean doRebind = s.onUnbind(data.intent);
if (doRebind) {
// If doRebind is true, AMS needs to be notified againActivityManager.getService().unbindFinished(doRebind); }}// ActiveServices.java
void unbindFinishedLocked(ServiceRecord r, Intent intent, boolean doRebind){
Intent.FilterComparison filter = new Intent.FilterComparison(intent);
IntentBindRecord b = r.bindings.get(filter);
if (b.apps.size() > 0 && !inDestroying) {
// Applications have already bound since the last
// unbind, so just rebind right here.. }else {
// Note to tell the service the next time there is
// a new client.
b.doRebind = true; // The next time you bind a service with this intent, you will receive an onRebind callback}}Copy the code
Ok, so now we’re done with unBind, and we’ve also looked at how onRebind’s callback condition (doRebind = true) is assigned
Google official documentation error?
Through the above analysis, we can draw some conclusions:
- OnBind is called when the same intent requests a Binder object from a Service for the first time. The Binder object and the request state are cached in AMS
- OnRebind only calls onRebind when the same intent is used to initiate the binding
- After bindService, onUnbind will only be called if onBind/onRebind is called
Let’s take a look at a private message from a friend:
After our previous analysis, we should also know that the friend said exactly right.
The Service, rounding used in figure from Google document: developer.android.com/guide/compo… If you want to force a patch to understand it, it should look like this (ignoring the conflict between the scarlet enforced condition and the previous process node) :
We can also take a look at the picture provided by this student, what a talented treasure reader, remember to give a thumbs-up if you think the best drawing:
The last
After reading this article, you should be familiar with the overall flow of bindService and the related lifecycle calls.
This article source code based on Android – 28