Service startup process -- Based on Android 11

Posted by dionsweet on Thu, 10 Feb 2022 13:32:37 +0100

Service startup process -- Based on Android 11

Recently, when reviewing the relevant knowledge points of Android system, we all know that Android has four components: Activity, Service, Broadcast Receive and Content Provider. Compared with other knowledge points, the best way to review the four components is to follow the source code again. When it comes to looking at the source code, we may have a headache, because once we get into it, Will be trapped in the ocean of code, at a loss, in fact, the most taboo in analyzing the source code is to dig deep as soon as you come up. This is difficult to come out. It not only takes time, but also can not extract useful information. The best way is to follow the main line process first, get through the main line first, and then knock on the side of the branch line if there is time.

OK, let's analyze it according to the way we just talked about. Today we mainly analyze the service startup process. First, we know that there are two ways to start a service: startService and bindService, that is, the startup and binding of a service. Next, let's analyze the startup of a service.

startService

First, let's start with context Starting from the method of startservice (), let's take a look at its source code:

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

startService () is an abstract method, which is implemented in contextimpl In Java, enter contextimpl Java:

@Override
public ComponentName startService(Intent service) {
    warnIfCallingFromSystemProcess();
    return startServiceCommon(service, false, mUser);
}

It will go directly to the method of startServiceCommon,

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(), getAttributionTag(), 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();
    }
}

From the startServiceCommon method, it is easy to locate the key code:

ComponentName cn = ActivityManager.getService().startService(
        mMainThread.getApplicationThread(), service,
        service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
        getOpPackageName(), getAttributionTag(), user.getIdentifier());

Via activitymanager Getservice () will go to AMS and start the service in AMS.

Next, enter AMS to find out:

@Override
public ComponentName startService(IApplicationThread caller, Intent service,
        String resolvedType, boolean requireForeground, String callingPackage,
        String callingFeatureId, int userId)
        throws TransactionTooLargeException {
        
  ...
  
        try {
            res = mServices.startServiceLocked(caller, service,
                    resolvedType, callingPid, callingUid,
                    requireForeground, callingPackage, callingFeatureId, userId);
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
        return res;
    }
}

In this method, startServiceLocked will be called through ActiveServices, and then see startServiceLocked:

ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, boolean fgRequired, String callingPackage,
        @Nullable String callingFeatureId, final int userId,
        boolean allowBackgroundActivityStarts) throws TransactionTooLargeException {
    if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
            + " type=" + resolvedType + " args=" + service.getExtras());

    final boolean callerFg;
    if (caller != null) {
        // The process information of the app is recorded here
        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
        
...
       
    ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
        
...
    
    return cmp;
}

Finally, call startServiceInnerLocked. Continue to look at this method:

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;
}

Continued to call bringUpServiceLocked,

private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg, boolean whileRestarting, boolean permissionsReviewRequired)
        throws TransactionTooLargeException {
    ...

    // Service is now being launched, its package can't be stopped.
    try {
        AppGlobals.getPackageManager().setPackageStoppedState(
                r.packageName, false, r.userId);
    } catch (RemoteException e) {
    } catch (IllegalArgumentException e) {
        Slog.w(TAG, "Failed trying to unstop package "
                + r.packageName + ": " + e);
    }

    final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
    final String procName = r.processName;
    HostingRecord hostingRecord = new HostingRecord("service", r.instanceName);
    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 the service process exists
        if (app != null && app.thread != null) {
            try {
                app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode, 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.shortInstanceName, e);
            }
        }
    } else {
        app = r.isolatedProc;
        if (WebViewZygote.isMultiprocessEnabled()
                && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
            hostingRecord = HostingRecord.byWebviewZygote(r.instanceName);
        }
        if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
            hostingRecord = HostingRecord.byAppZygote(r.instanceName, r.definingPackageName,
                    r.definingUid);
        }
    }

    // If this process does not exist
    if (app == null && !permissionsReviewRequired) {
        // Start running thread
        if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
                hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, 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;
}

This method does two things:

1. If the Service process already exists, call realStartServiceLocked directly.

2. If the Service process does not exist, execute the startProcessLocked method to create the process. After layers of calls, it will eventually go to

In realStartServiceLocked.

private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
    
    ...
        
    try {
        
	...
        
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
                app.getReportedProcState());
        r.postNotification();
        created = true;
    }
}

This method internally calls app thread. Schedulecreateservice, and app Thread is an iaapplicationthread type. Its implementation is an internal class ApplicationThread of ActivityThread, which just implements iaapplicationthread Stub, in the ApplicationThread class, find the corresponding calling method:

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);
}

It can be seen that a message is sent to the Handler, which is the internal class H of ActivityThread

public void handleMessage(Message msg) {
    switch (msg.what) {
       ...
        
        case CREATE_SERVICE:
            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        ("serviceCreate: " + String.valueOf(msg.obj)));
            }
            handleCreateService((CreateServiceData)msg.obj);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            break;
            
		... 
    }
    
}

Finally, the handleCreateService method is called:

private void handleCreateService(CreateServiceData data) {

    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
		//Create context of service
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        //Create Application
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        //Get class loader
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        //Load service instance
        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
        // Service resources must be initialized with the same loaders as the application
        // context.
        context.getResources().addLoaders(
                app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

        context.setOuterContext(service);
        //Initialize service
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        //Call onCreate method of service
        service.onCreate();
        mServices.put(data.token, service);
        try {
            //Inform AMS through serviceDoneExecuting that the service has been started and completed
            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);
        }
    }
}

Let's see what this method does:

1. First, create a context

2. Create an Application and get the class loader

3. Load the service instance, initialize the service, and call back to notify the developer that the service creation is complete.

4. Finally inform AMS that the service is started.

Here, the service has been started.

Summary:

The whole startService process, from the perspective of process

  • proccessA process uses Binder form to send data to system_ The server process initiates a startService request
  • system_ After receiving the request, the server process sends a request to create a process to the zygote process
  • The zygote process fork s out the new process and creates the main method of the ActivityThread of the new process
  • The new process is sent to the system through Binder_ The server process initiates an attachApplication request
  • system_ After receiving the request, the server process sends a scheduleCreateService request to the new process through Binder after a series of preparations
  • After receiving the request, the new process sends create through the Handler_ Service message
  • The main thread receives a message, creates a Service, and calls back onCreate

bindService

Next, we analyze the binding of the service:

Coincidentally, it starts with context Take bindservice as the starting point for analysis. See the source code:

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

It also returns bindService, which is implemented in the context implementation class contextimpl In Java,

@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
    warnIfCallingFromSystemProcess();
    return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), null,
            getUser());
}

bindServiceCommon is called. Let's see:

private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, String instanceName, Handler handler, Executor executor, UserHandle user) {
    
   ...
       
    try {
       ...
        int res = ActivityManager.getService().bindIsolatedService(
            mMainThread.getApplicationThread(), getActivityToken(), service,
            service.resolveTypeIfNeeded(getContentResolver()),
            sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
       ...
    }
}

Via activitymanager Getservice() to enter AMS, call bindIsolatedService method through AMS, and enter this method in AMS as follows:

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);
    }
}

bindServiceLocked will be called through ActiveServices to continue the analysis:

int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
        String resolvedType, final IServiceConnection connection, int flags,
        String instanceName, String callingPackage, final int userId)
        throws TransactionTooLargeException {
    ...
   //Record process information
    final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
    
    ...
    //Activity information
    ActivityServiceConnectionsHolder<ConnectionRecord> activity = null;
    if (token != null) {
        activity = mAm.mAtmInternal.getServiceConnectionsHolder(token);
        if (activity == null) {
            Slog.w(TAG, "Binding with unknown activity: " + token);
            return 0;
        }
    }

   ...
    try {
        ...
            
        //If bind is set_ AUTO_ Create flag, start the bringUpServiceLocked method,
        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) {
            // Service is already running, so we can immediately
            // publish the connection.
            try {
                c.conn.connected(s.name, b.intent.binder, false);
            } catch (Exception e) {
                Slog.w(TAG, "Failure sending service " + s.shortInstanceName
                        + " to connection " + c.conn.asBinder()
                        + " (in " + c.binding.client.processName + ")", e);
            }

            // 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) {
                requestServiceBindingLocked(s, b.intent, callerFg, true);
            }
        } else if (!b.intent.requested) {
            requestServiceBindingLocked(s, b.intent, callerFg, false);
        }

        maybeLogBindCrossProfileService(userId, callingPackage, callerApp.info.uid);

        getServiceMapLocked(s.userId).ensureNotStartingBackgroundLocked(s);

    } finally {
        Binder.restoreCallingIdentity(origId);
    }

    return 1;
}

This method will first obtain the process and Activity information, and then obtain the relationship between the Service and the application. [Note: if there is a connection to set BIND_AUTO_CREATE, it will not destroy the Service and return directly. Start the bringUpServiceLocked method. Refer to Service startup later] and then call the requestServiceBindingLocked method.

private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
        boolean execInFg, boolean rebind) throws TransactionTooLargeException {
   ...
        try {
            bumpServiceExecutingLocked(r, execInFg, "bind");
            r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
            r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
                    r.app.getReportedProcState());
            if (!rebind) {
                i.requested = true;
            }
            i.hasBound = true;
            i.doRebind = false;
        } 
    return true;
}

Here's the app Thread is the same as service startup. It is an iaapplicationthread type. Its implementation is the internal class ApplicationThread of ActivityThread, in which ApplicationThread inherits iaapplicationthread Stub

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);
}

In this method, send the message to the Handler through sendMessage, and process the message through handleMessage

public void handleMessage(Message msg) {
    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;
        
	... 
	}

}

Finally, handleBindService was called,

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);
                }
            } 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);
            }
        }
    }
}

mService is a Map with the relationship between token and Server. First get the Service to be bound from it, and then judge whether the Service is bound for the first time. If yes, call back onBind method and call publishService method of AMS; Otherwise, call back onRebind and call the serviceDoneExecuting method of AMS.

Let's first look at the publishService method:

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);
    }
}

Call publishServiceLocked through ActiveServices:

void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
    final long origId = Binder.clearCallingIdentity();
    try {
        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r
                + " " + intent + ": " + service);
        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;
                ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
                for (int conni = connections.size() - 1; conni >= 0; conni--) {
                    ArrayList<ConnectionRecord> clist = connections.valueAt(conni);
                    for (int i=0; i<clist.size(); i++) {
                        ConnectionRecord c = clist.get(i);
                        if (!filter.equals(c.binding.intent.intent)) {
                            if (DEBUG_SERVICE) Slog.v(
                                    TAG_SERVICE, "Not publishing to: " + c);
                            if (DEBUG_SERVICE) Slog.v(
                                    TAG_SERVICE, "Bound intent: " + c.binding.intent.intent);
                            if (DEBUG_SERVICE) Slog.v(
                                    TAG_SERVICE, "Published intent: " + intent);
                            continue;
                        }
                        if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Publishing to: " + c);
                        try {
                            c.conn.connected(r.name, service, false);
                        } catch (Exception e) {
                            Slog.w(TAG, "Failure sending service " + r.shortInstanceName
                                  + " to connection " + c.conn.asBinder()
                                  + " (in " + c.binding.client.processName + ")", e);
                        }
                    }
                }
            }

            serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false);
        }
    } finally {
        Binder.restoreCallingIdentity(origId);
    }
}

Finally, we come to servicedone executing locked. Let's take a look at c.conn.connected,

final IServiceConnection conn;  // The client connection.

IServiceConnection is an AIDL interface, specifically implemented as ServiceDispatcher Innerconnection, where ServiceDispatcher is the internal class of LoadedApk.

static final class ServiceDispatcher {

   ...

    private static class InnerConnection extends IServiceConnection.Stub {
        @UnsupportedAppUsage
        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);
            }
        }
    }

The connected method of ServiceDispatcher class was called

private final Handler mActivityThread;
private final Executor mActivityExecutor;

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

In this method, a Runnable is posted 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;
}

doConnected through thread pool

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 viable service, it is now connected.
    if (service != null) {
        mConnection.onServiceConnected(name, service);
    } else {
        // The binding machinery worked, but the remote returned null from onBind().
        mConnection.onNullBinding(name);
    }
}

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

Summary:

  • Context notification AMS
  • AMS notification ActivityThread
  • If bind is set_ AUTO_ Create, start the service directly, otherwise continue binding
  • ActivityThread binding Service
  • If it is the first callback of reservice, it will call the method of reservice bind bind bind, and if it is the first callback of amservice, it will directly call the method of reservice bind bind bind

So far, the analysis of the Service startup and binding process has been completed. Generally speaking, the process is not very complex. You can understand it by carefully following the source code. If there is something wrong in the article, you are welcome to leave a message in the message area for discussion and common learning and progress. If you think my article can help you, please also give me a love and attention.

Topics: Android Design Pattern