Android plug-in architecture - Analysis of Activity startup process

  1. summary
    Android plug-in architecture. At present, there are many third-party frameworks. In the early years, I used the DL framework. The source code of this framework is relatively simple, mainly using static agents. If we want to write a plug-in architecture framework, the problems to be solved will be divided into several aspects: class loading, resource and layout loading, broadcast management mode, Activity loading and life cycle management, Service plug-in, ContentProvider plug-in, etc. anyway, load an app that is not running to the main program, There are basically so many problems to be solved. If they can be solved one by one, plug-in can be realized.
    In the sub project part of the connotation section, we will implement several, and then introduce a 360 open source framework DroidPlugin, which has the same principle. Later, we will implement them one by one. What will be implemented in this phase? If we need to start the plug-in APP, we need to start the activities inside. These activities will not be configured in the AndroidManifest.xml of the main project in advance. Starting an unregistered Activity will certainly report an error. Can we find a way to bypass the system detection so that the activities not configured in AndroidManifest.xml can still be started?
    In fact, when looking at the source code, we often emphasize that we must take ideas with us. To solve such a problem, we must clearly know the specific process of the system starting Activity. Of course, we can directly understand why the error is reported. Here we still go through the starting process once, which is also convenient for any problems encountered in future development.
  2. Activity startup process source code analysis
public void startActivity(Intent intent, @Nullable Bundle options) {
    if (options != null) {
        startActivityForResult(intent, -1, options);
    } else {
        // Note we want to go through this call for compatibility with
        // applications that may have overridden the method.
        startActivityForResult(intent, -1);

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);
        if (ar != null) {
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
        if (requestCode >= 0) {
            // If this start is requesting a result, we can avoid making
            // the activity visible until the result is received.  Setting
            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
            // activity hidden during this time, to avoid flickering.
            // This can only be done when a result is requested because
            // that guarantees we will get information back when the
            // activity is finished, no matter what happens to it.
            mStartedActivity = true;

        // TODO Consider clearing/flushing other event sources and events for child windows.
    } else {
        if (options != null) {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        } else {
            // Note we want to go through this method for compatibility with
            // existing applications that may have overridden it.
            mParent.startActivityFromChild(this, intent, requestCode);

Go to the execStartActivity method in Instrumentation

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    if (am.match(who, null, intent)) {
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
        try {
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        return null;
    // The test results are normal
    public static void checkStartActivityResult(int res, Object intent) {
        if (res >= ActivityManager.START_SUCCESS) {

        switch (res) {
            case ActivityManager.START_INTENT_NOT_RESOLVED:
            case ActivityManager.START_CLASS_NOT_FOUND:
                // Various errors will be reported here. The unconfigured errors in AndroidManifest.xml appear here
                if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                    throw new ActivityNotFoundException(
                            "Unable to find explicit activity class "
                            + ((Intent)intent).getComponent().toShortString()
                            + "; have you declared this activity in your AndroidManifest.xml?");
                throw new ActivityNotFoundException(
                        "No Activity found to handle " + intent);
            case ActivityManager.START_PERMISSION_DENIED:
                throw new SecurityException("Not allowed to start activity "
                        + intent);
            case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                throw new AndroidRuntimeException(
                        "FORWARD_RESULT_FLAG used while also requesting a result");
            case ActivityManager.START_NOT_ACTIVITY:
                throw new IllegalArgumentException(
                        "PendingIntent is not an activity");
            case ActivityManager.START_NOT_VOICE_COMPATIBLE:
                throw new SecurityException(
                        "Starting under voice control not allowed for: " + intent);
            case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:
                throw new IllegalStateException(
                        "Session calling startVoiceActivity does not match active session");
            case ActivityManager.START_VOICE_HIDDEN_SESSION:
                throw new IllegalStateException(
                        "Cannot start voice activity on a hidden session");
            case ActivityManager.START_CANCELED:
                throw new AndroidRuntimeException("Activity could not be started for "
                        + intent);
                throw new AndroidRuntimeException("Unknown error code "
                        + res + " when starting " + intent);

ActivityManagerNative.getDefault().startActivity is also an inter process communication. If you do not understand IPC, please move to the communication between Android processes - the principle and source code of IPC (mechanism) Binder, and come to the startActivity method in ActivityManagerService:

    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
                resultWho, requestCode, startFlags, profilerInfo, bOptions,

    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                userId, false, ALLOW_FULL_ONLY, "startActivity", null);
        // TODO: Switch to user app stacks here.
        return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, bOptions, false, userId, null, null);

    final int startActivityMayWait(IApplicationThread caller, int callingUid,
            String callingPackage, Intent intent, String resolvedType,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config,
            Bundle bOptions, boolean ignoreTargetSecurity, int userId,
            IActivityContainer iContainer, TaskRecord inTask) {
            // Packagemanagerservice ------ > scan app and register components

            ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
            // Collect information about the target of the Intent.
            ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);

            rInfo = mSupervisor.resolveIntent(intent, null /*resolvedType*/, userId);

            int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
                    aInfo, rInfo, voiceSession, voiceInteractor,
                    resultTo, resultWho, requestCode, callingPid,
                    callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,
                    options, ignoreTargetSecurity, componentSpecified, outRecord, container,

     final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
            String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
            String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
            ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
            ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
            TaskRecord inTask) {
            // Verify intent, Class, Permission, etc
            // Save the Record of the Activity to be started
            err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, startFlags,
                    true, options, inTask);
            return err;

      private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
            // Check the launchMode and start Flag of the Activity to be started
            // Configure the task according to the launcheMode and Flag
            final boolean dontStart = top != null && mStartActivity.resultTo == null
                && top.realActivity.equals(mStartActivity.realActivity)
                && top.userId == mStartActivity.userId
                && != null && != null
                && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                || mLaunchSingleTop || mLaunchSingleTask);
            // For example, SingleTop
            if (dontStart) {
                    mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);

               // Don't use mStartActivity.task to show the toast. We're not starting a new activity
               // but reusing 'top'. Fields in mStartActivity may not be fully initialized.
                    top.task, preferredLaunchStackId, topStack.mStackId);
               return START_DELIVERED_TO_TOP;

            mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);
            if (mDoResume) {
                  mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,

get into ActvityStack Medium startActivityLocked()method

      // Task stack history stack configuration
      final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition,
            ActivityOptions options) {
            if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
                // Add stack top management stack
                insertTaskAtTop(rTask, r);
                // Tube display
            if (!newTask) {
                // Not a new Task
                addConfigOverride(r, task);
get into ActivityStack of resumeTopActivityInnerLocked()method

      private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
           // Find the first activity that is not finishing.
           final ActivityRecord next = topRunningActivityLocked();
           // The activity may be waiting for stop, but that is no longer
           // appropriate for it.
           next.sleeping = false;

          if (mResumedActivity != null) {
              if (DEBUG_STATES) Slog.d(TAG_STATES,
                    "resumeTopActivityLocked: Pausing " + mResumedActivity);
              pausing |= startPausingLocked(userLeaving, false, next, dontWaitForPause);

      final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping,
            ActivityRecord resuming, boolean dontWait) {
            if ( != null && != null) {
            if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Enqueueing pending pause: " + prev);
            try {
                        prev.userId, System.identityHashCode(prev),
                mService.updateUsageStats(prev, false);
                // Pause Activity
      , prev.finishing,
                        userLeaving, prev.configChangeFlags, dontWait);

            completePauseLocked(false, resuming);

Enter the schedulePauseActivity() method of ApplicationThread

      public final void schedulePauseActivity(IBinder token, boolean finished,
                boolean userLeaving, int configChanges, boolean dontReport) {
                    finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
                    (userLeaving ? USER_LEAVING : 0) | (dontReport ? DONT_REPORT : 0),

      public void handleMessage(Message msg) {
            switch (msg.what) {
            case PAUSE_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                    SomeArgs args = (SomeArgs) msg.obj;
                    handlePauseActivity((IBinder) args.arg1, false,
                            (args.argi1 & USER_LEAVING) != 0, args.argi2,
                            (args.argi1 & DONT_REPORT) != 0, args.argi3);
           } break;

       private void handlePauseActivity(IBinder token, boolean finished,
            boolean userLeaving, int configChanges, boolean dontReport, int seq) {
            performPauseActivity(token, finished, r.isPreHoneycomb(), "handlePauseActivity");
            // Tell the activity manager we have paused.
            if (!dontReport) {
                try {
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();

       final Bundle performPauseActivity(IBinder token, boolean finished,
            boolean saveState, String reason) {
            ActivityClientRecord r = mActivities.get(token);
            return r != null ? performPauseActivity(r, finished, saveState, reason) : null;

       final Bundle performPauseActivity(ActivityClientRecord r, boolean finished,
            boolean saveState, String reason) {
            // ...
            performPauseActivityIfNeeded(r, reason);

       private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) {
            // ...

Enter the callActivityOnPause() method of Instrumentation

    public void callActivityOnPause(Activity activity) {
get into Activity of performPause()method

final void performPause() {
        mDoReportFullyDrawn = false;
        mCalled = false;
        // Call onPause() to pause the method
        mResumed = false;
        if (!mCalled && getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.GINGERBREAD) {
            throw new SuperNotCalledException(
                    "Activity " + mComponent.toShortString() +
                    " did not call through to super.onPause()");
        mResumed = false;

Finally, the onPause method of the Activity is called back. Haha, it's still very simple. We finally found the onPause method in the Activity life cycle. That is, when we start an Activity, the onPause method of the Activity at the top of the stack is executed first. We have already memorized these life cycles of Activity. Then look down and return to ActivityManagerNative.getDefault().activityPaused(token) in the handlePauseActivity() method of our ApplicationThread; Enter the activityPaused() method in ActivityManagerService

     public final void activityPaused(IBinder token) {
        final long origId = Binder.clearCallingIdentity();
        synchronized(this) {
            ActivityStack stack = ActivityRecord.getStackLocked(token);
            if (stack != null) {
                stack.activityPausedLocked(token, false);
get into ActivityStack Medium activityPausedLocked()method

    final void activityPausedLocked(IBinder token, boolean timeout){
        completePauseLocked(true, null);
    private void completePauseLocked(boolean resumeNext, ActivityRecord resuming) {
        ActivityRecord prev = mPausingActivity;
        if (resumeNext) {
            final ActivityStack topStack = mStackSupervisor.getFocusedStack();
            mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);

Enter the resumeFocusedStackTopActivityLocked() method in the ActivityStackSupervisor

    boolean resumeFocusedStackTopActivityLocked(
        ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
        targetStack.resumeTopActivityUncheckedLocked(target, targetOptions);

Enter the resumetoperactivityuncheckedlocked() method in the ActivityStack

    boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
        result = resumeTopActivityInnerLocked(prev, options);

    private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        // We are already familiar with this method
        mStackSupervisor.startSpecificActivityLocked(next, true, true);
get into ActivityStackSupervisor Medium startSpecificActivityLocked()method

    void startSpecificActivityLocked(ActivityRecord r,
            boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        ProcessRecord app = mService.getProcessRecordLocked(r.processName,
      , true);


        if (app != null && app.thread != null) {
            try {
                if (( == 0
                        || !"android".equals( {
                    // Don't add this if it is a platform component that is marked
                    // to run in multiple processes, because this is actually
                    // part of the framework so doesn't make sense to track as a
                    // separate apk in the process.
                // You really want to start the Activity
                realStartActivityLocked(r, app, andResume, checkConfig);
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);

            // If a dead object exception was thrown -- fall through to
            // restart the application.

        mService.startProcessLocked(r.processName,, true, 0,
                "activity", r.intent.getComponent(), false, false, true);

    final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
            boolean andResume, boolean checkConfig) throws RemoteException {
            // scheduleLaunchActivity start
            app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                    System.identityHashCode(r),, new Configuration(mService.mConfiguration),
                    new Configuration(task.mOverrideConfig), r.compat, r.launchedFromPackage,
                    task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                    newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);

Enter the scheduleLaunchActivity() method in ApplicationThread

   public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;

            sendMessage(H.LAUNCH_ACTIVITY, r);

   public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        mSomeActivitiesChanged = true;

        if (r.profilerInfo != null) {

        // Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);

        if (localLOGV) Slog.v(
            TAG, "Handling launch of " + r);

        // Initialize before creating the activity

        Activity a = performLaunchActivity(r, customIntent);

        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

            if (!r.activity.mFinished && r.startsNotResumed) {
                // The activity manager actually wants this one to start out paused, because it
                // needs to be visible but isn't in the foreground. We accomplish this by going
                // through the normal startup (because activities expect to go through onResume()
                // the first time they run, before their window is displayed), and then pausing it.
                // However, in this case we do -not- need to do the full pause cycle (of freezing
                // and such) because the activity manager assumes it can just retain the current
                // state it has.
                performPauseActivityIfNeeded(r, reason);

                // We need to keep around the original state, in case we need to be created again.
                // But we only do this for pre-Honeycomb apps, which always save their state when
                // pausing, so we can not have them save their state when restarting from a paused
                // state. For HC and later, we want to (and can) let the state be saved as the
                // normal part of stopping the activity.
                if (r.isPreHoneycomb()) {
                    r.state = oldState;
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
  1. Activity launch process summary
    At the beginning, it is estimated that it will take a day or two to receive the goods. In fact, looking at the source code is either to solve the problem or to learn. Otherwise, don't easily enter this muddy water. There are too many codes and no thoughts will be confused. First, you can roughly go through the process, and then grasp the details, such as how to allocate the stack, how to deal with the startup mode, how to create the Activity, how to call the life cycle, how to pass the parameters of the serialized object, and so on
    After reading the source code, we have to admire Google engineers. The extensibility of the code written is really good. If you change the ordinary people's version and iterate several times, the code can't be changed. You have to re structure and re write. This is also the way we learn. The source code is actually the best book. There are several ideas here. The idea of C/S architecture is service idea, modular idea, hierarchical idea and so on

Finally, let's take a look at the functions of several main classes in the startup process:

ActivityManagerService component communication system core management class (ActivityManagerNative) IPC communication
The ActivityStackSupervisor manages the Activity task stack of the whole mobile phone
Activitystack (task stack)
PackageManagerService is mainly responsible for managing the apk of the system. Whether it is the system apk(/system/app) or we installed it manually, all apks of the system are managed by it.
ActivityThread the entry of an activity is the onCreate method, and the entry of an application on Android is ActivityThread. Like ordinary Java classes, there is a main method. It is used to control and manage the operation of the main thread of an application process, including managing and processing activities, broadcasts and other operation requests sent by the activity manager
ActivityManagerService and ActivityStack are in the same process, while ApplicationThread and ActivityThread are in another process. Among them, ActivityManagerService is responsible for managing the life cycle of activities. ActivityManagerService also uses ActivityStack to put all activities on a stack in the order of last in first out; For each application, there is an ActivityThread to represent the main process of the application, and each ActivityThread contains an ApplicationThread instance, which is a Binder object responsible for communicating with other processes.

What's the use of reading the source code so painfully? Then, combined with the dynamic code design pattern and the Activity startup process, we can try to intercept the Activity startup process, so that the Activity that is not registered in AndroidMainfest.xml can start without error. In this way, we have even stepped out of the first step of the plug-in architecture. If you don't understand the Activity startup process, it will be a failure.

Related video
Let you thoroughly master Android plug-in and conquer the interview!

Tags: Android

Posted on Fri, 03 Dec 2021 15:36:26 -0500 by SerpentSword