Secrets of the Android system (two)-Service startup process

Secrets of the Android system (two)-Service startup process

Preamble

Preface

We know that Service has two ways to use it: startup and binding. This article explains them separately according to the two situations (the source code analysis is longer, comrades who don't talk about martial arts can directly pull it and see the summary at the end)

Service start

The service startup process is relatively simple, and there is no major change in each version. The code in this article is taken from 8.1, but it is also applicable to versions 8-11

The calling process from ContextImpl to AMS


What we call startService actually calls the startService method of ContextWrapper
frameworks/base/core/java/android/content/ContextWrapper.java

Context mBase; @Override public ComponentName startService(Intent service) { return mBase.startService(service); } Copy code

The specific implementation of Context is ContextImpl

frameworks/base/core/java/android/app/ContextImpl.java

@Override public ComponentName startService(Intent service) { warnIfCallingFromSystemProcess(); return startServiceCommon(service, false, mUser); } Copy code
private ComponentName startServiceCommon(Intent service, boolean requireForeground, UserHandle user) { try { validateServiceIntent(service); service.prepareToLeaveProcess(this); ComponentName cn = ActivityManager.getService().startService( mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded( getContentResolver()), requireForeground, getOpPackageName(), user.getIdentifier()); if (cn != null) { if (cn.getPackageName().equals("!")) { throw new SecurityException( "Not allowed to start service "+ service + "without permission" + cn.getClassName()); } else if (cn.getPackageName().equals("!!")) { throw new SecurityException( "Unable to start service "+ service + ": "+ cn.getClassName()); } else if (cn.getPackageName().equals("?")) { throw new IllegalStateException( "Not allowed to start service "+ service + ":" + cn.getClassName()); } } return cn; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } Copy code

According to the Android system secret (1)-Activity startup process (on) section we already know that ActivityManager.getService() is actually AMS

AMS notify ActivityThread


frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

final ActiveServices mServices; @Override public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType, boolean requireForeground, String callingPackage, int userId) throws TransactionTooLargeException { ... synchronized(this) { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); ComponentName res; try { res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid, requireForeground, callingPackage, userId); } finally { Binder.restoreCallingIdentity(origId); } return res; } } Copy code

frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage, final int userId) throws TransactionTooLargeException { ... ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting); return cmp; } Copy code
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r, boolean callerFg, boolean addToStarting) throws TransactionTooLargeException { ... String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false, false); if (error != null) { return new ComponentName("!!", error); } ... return r.name; } Copy code
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting, boolean permissionsReviewRequired) throws TransactionTooLargeException { //Send Service parameters if (r.app != null && r.app.thread != null) { sendServiceArgsLocked(r, execInFg, false); return null; } ... final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; final String procName = r.processName; String hostingType = "service"; ProcessRecord app; if (!isolated) { app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false); if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + "app=" + app); if (app != null && app.thread != null) { try { app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats); //Start Service realStartServiceLocked(r, app, execInFg); return null; } catch (TransactionTooLargeException e) { throw e; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting service "+ r.shortName, e); } //If a dead object exception was thrown - fall through to //restart the application. } } else { //If this service runs in an isolated process, then each time ... } //Create if the Service process does not exist if (app == null && !permissionsReviewRequired) { if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, hostingType, r.name, false, isolated, false)) == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + "for service" + r.intent.getIntent() + ": process is bad"; Slog.w(TAG, msg); bringDownServiceLocked(r); return msg; } if (isolated) { r.isolatedProc = app; } } ... return null; } Copy code

The bringUpServiceLocked method will get the process where the Service is located, if it exists, start the Service through realStartServiceLocked, otherwise create the process first and then start the Service

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException { ... boolean created = false; try { ... 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) { ... } finally { ... } ... } Copy code

The app.thread here refers to IApplicationThread, and its implementation is the internal class ApplicationThread of ActivityThread, where ApplicationThread inherits IApplicationThread.Stub

ActivityThread starts Service


frameworks/base/core/java/android/app/ActivityThread.java#ApplicationThread

public final void scheduleCreateService(IBinder token, ServiceInfo info, CompatibilityInfo compatInfo, int processState) { updateProcessState(processState, false); CreateServiceData s = new CreateServiceData(); s.token = token; s.info = info; s.compatInfo = compatInfo; sendMessage(H.CREATE_SERVICE, s); } Copy code

Then send the CREATE_SERVICE command to mH and receive it in the handleMessage of H

frameworks/base/core/java/android/app/ActivityThread.java

public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: "+ codeToString(msg.what)); switch (msg.what) { case CREATE_SERVICE: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: "+ String.valueOf(msg.obj))); handleCreateService((CreateServiceData)msg.obj); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; } } Copy code
private void handleCreateService(CreateServiceData data) { //If we are getting ready to gc after going to the background, well //we are back active so skip it. unscheduleGcIdler(); //Get package information LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo, data.compatInfo); Service service = null; try { //Get the class loader java.lang.ClassLoader cl = packageInfo.getClassLoader(); //Create Service instance service = (Service) cl.loadClass(data.info.name).newInstance(); } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { throw new RuntimeException( "Unable to instantiate service "+ data.info.name + ": "+ e.toString(), e); } } try { if (localLOGV) Slog.v(TAG, "Creating service "+ data.info.name); //Create context ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); //Create or get Application Application app = packageInfo.makeApplication(false, mInstrumentation); //Initialize Service service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); //The callback informs the developer that the service is created service.onCreate(); mServices.put(data.token, service); try { //Notify AMS ActivityManager.getService().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } catch (Exception e) { if (!mInstrumentation.onException(service, e)) { throw new RuntimeException( "Unable to create service "+ data.info.name + ": "+ e.toString(), e); } } } Copy code

This step mainly does the following things:

  • Get package information
  • Get the class loader
  • Create Service instance
  • Create context
  • Create or get Application
  • Initialize Service
  • Callback to notify the developer that the service is created
  • Notify AMS

Service binding

The calling process from ContextImpl to AMS


What we call startService actually calls the startService method of ContextWrapper
frameworks/base/core/java/android/content/ContextWrapper.java

Context mBase; @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { return mBase.bindService(service, conn, flags); } Copy code

The specific implementation of Context is ContextImpl

frameworks/base/core/java/android/app/ContextImpl.java

@Override public boolean bindService(Intent service, ServiceConnection conn, int flags) { warnIfCallingFromSystemProcess(); return bindServiceCommon(service, conn, flags, mMainThread.getHandler(), Process.myUserHandle()); } Copy code
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, Handler handler, UserHandle user) { IServiceConnection sd; ... if (mPackageInfo != null) { sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags); } else { throw new RuntimeException("Not supported in system context"); } ... validateServiceIntent(service); try { ... int res = ActivityManager.getService().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), sd, flags, getOpPackageName(), user.getIdentifier()); if (res <0) { throw new SecurityException( "Not allowed to bind to service "+ service); } return res != 0; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } Copy code

AMS notify ActivityThread


frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

final ActiveServices mServices; public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String callingPackage, int userId) throws TransactionTooLargeException { ... synchronized(this) { return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, flags, callingPackage, userId); } } Copy code

(After Android 10, the bindIsolatedService method will be jumped first)

public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String callingPackage, int userId) throws TransactionTooLargeException { return bindIsolatedService(caller, token, service, resolvedType, connection, flags, null, callingPackage, userId); } public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags, String instanceName, String callingPackage, int userId) throws TransactionTooLargeException { ... synchronized(this) { return mServices.bindServiceLocked(caller, token, service, resolvedType, connection, flags, instanceName, callingPackage, userId); } } Copy code

frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service, String resolvedType, final IServiceConnection connection, int flags, String callingPackage, final int userId) throws TransactionTooLargeException { ... //Record process information final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); ... //Activity information ActivityRecord activity = null; if (token != null) { activity = ActivityRecord.isInStackLocked(token); if (activity == null) { Slog.w(TAG, "Binding with unknown activity: "+ token); return 0; } } ... try { ... //The relationship between Service and application AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent); IBinder binder = connection.asBinder(); ArrayList<ConnectionRecord> clist = s.connections.get(binder); ... //If the BIND_AUTO_CREATE flag is set, start the bringUpServiceLocked method, refer to Service start later if ((flags&Context.BIND_AUTO_CREATE) != 0) { s.lastActivity = SystemClock.uptimeMillis(); if (bringUpServiceLocked(s, service.getFlags(), callerFg, false, permissionsReviewRequired) != null) { return 0; } } .... if (s.app != null && b.intent.received) { try { c.conn.connected(s.name, b.intent.binder, false); } catch (Exception e) { ... } if (b.intent.apps.size() == 1 && b.intent.doRebind) { requestServiceBindingLocked(s, b.intent, callerFg, true); } } else if (!b.intent.requested) { requestServiceBindingLocked(s, b.intent, callerFg, false); } getServiceMapLocked(s.userId).ensureNotStartingBackgroundLocked(s); } finally { Binder.restoreCallingIdentity(origId); } return 1; } Copy code

This method will first obtain information about the process and Activity, and then obtain the relationship between the Service and the application.
(Note that if there is a connection with BIND_AUTO_CREATE set, the service is returned without destroying the service, and the bringUpServiceLocked method is started. Refer to Service start
later ) . Call the requestServiceBindingLocked method

private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i, boolean execInFg, boolean rebind) throws TransactionTooLargeException { ... if ((!i.requested || rebind) && i.apps.size()> 0) { try { bumpServiceExecutingLocked(r, execInFg, "bind"); r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind, r.app.repProcState); if (!rebind) { i.requested = true; } i.hasBound = true; i.doRebind = false; } catch (TransactionTooLargeException e) { .. } catch (RemoteException e) { ... } } return true; } Copy code

The app.thread here refers to IApplicationThread, and its implementation is the internal class ApplicationThread of ActivityThread, where ApplicationThread inherits IApplicationThread.Stub

ActivityThread binding Service


frameworks/base/core/java/android/app/ActivityThread.java#ApplicationThread

public final void scheduleBindService(IBinder token, Intent intent, boolean rebind, int processState) { updateProcessState(processState, false); BindServiceData s = new BindServiceData(); s.token = token; s.intent = intent; s.rebind = rebind; if (DEBUG_SERVICE) Slog.v(TAG, "scheduleBindService token=" + token + "intent=" + intent +" uid=" + Binder.getCallingUid() + "pid=" + Binder.getCallingPid()); sendMessage(H.BIND_SERVICE, s); } Copy code

Then send the BIND_SERVICE command to mH, and receive it in H's handleMessage

frameworks/base/core/java/android/app/ActivityThread.java

public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: "+ codeToString(msg.what)); 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 code
final ArrayMap<IBinder, Service> mServices = new ArrayMap<>(); private void handleBindService(BindServiceData data) { Service s = mServices.get(data.token); if (DEBUG_SERVICE) Slog.v(TAG, "handleBindService s=" + s + "rebind=" + data.rebind); if (s != null) { try { data.intent.setExtrasClassLoader(s.getClassLoader()); data.intent.prepareToEnterProcess(); try { if (!data.rebind) { IBinder binder = s.onBind(data.intent); ActivityManager.getService().publishService( data.token, data.intent, binder); } else { s.onRebind(data.intent); ActivityManager.getService().serviceDoneExecuting( data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0); } ensureJitEnabled(); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } catch (Exception e) { if (!mInstrumentation.onException(s, e)) { throw new RuntimeException( "Unable to bind to service "+ s + "with" + data.intent + ": "+ e.toString(), e); } } } } Copy code

mService is a Map that stores the relationship between token and server. First obtain the service to be bound from it, and then determine whether the service is bound for the first time. If yes, call back the onBind method and call the publishService method of AMS; otherwise, call back onRebind and call the serviceDoneExecuting method of AMS

Let's first look at the publishService method

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public void publishService(IBinder token, Intent intent, IBinder service) { //Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors() == true) { throw new IllegalArgumentException("File descriptors passed in Intent"); } synchronized(this) { if (!(token instanceof ServiceRecord)) { throw new IllegalArgumentException("Invalid service token"); } mServices.publishServiceLocked((ServiceRecord)token, intent, service); } } Copy code

frameworks/base/services/core/java/com/android/server/am/ActiveServices.java

void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) { final long origId = Binder.clearCallingIdentity(); try { ... if (r != null) { Intent.FilterComparison filter = new Intent.FilterComparison(intent); IntentBindRecord b = r.bindings.get(filter); if (b != null && !b.received) { b.binder = service; b.requested = true; b.received = true; 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); ... try { c.conn.connected(r.name, service, false); } catch (Exception e) { ... } } } } ... serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false); } } finally { Binder.restoreCallingIdentity(origId); } } Copy code

It can be seen that the serviceDoneExecutingLocked method is also followed, let's continue to look at c.conn.connected

frameworks/base/services/core/java/com/android/server/am/ConnectionRecord.java

final IServiceConnection conn;.//The client connection copy the code

IServiceConnection is an AIDL interface, specifically implemented as ServiceDispatcher.InnerConnection, where ServiceDispatcher is an internal class of LoadedApk frameworks/base/core/java/android/app/LoadedApk.java

static final class ServiceDispatcher { ... 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) throws RemoteException { LoadedApk.ServiceDispatcher sd = mDispatcher.get(); if (sd != null) { sd.connected(name, service, dead); } } } } Copy code

Then look at the connected method of ServiceDispatcher
frameworks/base/core/java/android/app/LoadedApk.java#ServiceDispatcher

public void connected(ComponentName name, IBinder service, boolean dead) { if (mActivityThread != null) { mActivityThread.post(new RunConnection(name, service, 0, dead)); } else { doConnected(name, service, dead); } } Copy code

This method posts a Runnable to ActivityThread

private final class RunConnection implements Runnable { RunConnection(ComponentName name, IBinder service, int command, boolean dead) { mName = name; mService = service; mCommand = command; mDead = dead; } public void run() { if (mCommand == 0) { doConnected(mName, mService, mDead); } else if (mCommand == 1) { doDeath(mName, mService); } } final ComponentName mName; final IBinder mService; final int mCommand; final boolean mDead; } Copy code

Inside Runnable is the code to connect and close the connection.

public void doConnected(ComponentName name, IBinder service, boolean dead) { ServiceDispatcher.ConnectionInfo old; ServiceDispatcher.ConnectionInfo info; synchronized (this) { if (mForgotten) { //We unbound before receiving the connection; ignore //any connection received. return; } old = mActiveConnections.get(name); if (old != null && old.binder == service) { //Huh, already have this one. Oh well! return; } if (service != null) { //A new service is being connected... set it all up. info = new ConnectionInfo(); info.binder = service; info.deathMonitor = new DeathMonitor(name, service); try { service.linkToDeath(info.deathMonitor, 0); mActiveConnections.put(name, info); } catch (RemoteException e) { //This service was dead before we got it... just //don't do anything with it. mActiveConnections.remove(name); return; } } 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) { mConnection.onServiceDisconnected(name); } if (dead) { mConnection.onBindingDied(name); } //If there is a new service, it is now connected. if (service != null) { mConnection.onServiceConnected(name, service); } } Copy code

When the binding is successful, the mConnection.onServiceConnected(name, service) method will be called back

summary

Service start

  • Context informs AMS
  • AMS notify ActivityThread
  • ActivityThread starts Service

The calling process from ContextImpl to AMS

AMS notify ActivityThread

ActivityThread starts Service

Service binding

The binding process is more complicated, the simple sequence diagram is not easy to describe, and a flowchart is added

  • Context informs AMS
  • AMS notify ActivityThread
  • If BIND_AUTO_CREATE is set, start the service directly, otherwise continue to bind
  • ActivityThread binding Service
  • If it is the first binding, call the publishService method of AMS, connect to the Service, and call back onBind, otherwise directly call the serviceDoneExecutingLocked method of AMS and call back onReBind

The calling process from ContextImpl to AMS

AMS notify ActivityThread

ActivityThread binding Service


references

[1] Liu Wangshu. Android Advanced Decryption [M]: Publishing House of Electronics Industry, 2018-10