Influence of initialization timing of virtualapk plug-in framework on starting plug-in Activity

Problem background

It is stated in the virtualapk framework document that initialization needs to be carried out in the onCreate method in the Application. It is no problem to use it according to the steps in the document. It is also possible to start the plug-in Activity from the host
Due to business needs, the use of virtualApk in the project does not necessarily initialize in the onCreate method in the Application. Therefore, there is such a situation here:
Initialize Virtualapk in the current Activity of the host and start the plug-in Activity in the current Activity of the host. At this time, the startup fails and an exception is thrown

System.err: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.vplugin/com.example.vplugin.PluginActivity}; have you declared this activity in your AndroidManifest.xml?
System.err:     at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2016)
System.err:     at android.app.Instrumentation.execStartActivity(Instrumentation.java:1673)
System.err:     at android.app.Activity.startActivityForResult(Activity.java:4689)
System.err:     at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:767)
System.err:     at android.app.Activity.startActivityForResult(Activity.java:4647)
System.err:     at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:754)
System.err:     at android.app.Activity.startActivity(Activity.java:5008)
System.err:     at android.app.Activity.startActivity(Activity.java:4976)
System.err:     at com.example.vhost.MainActivity$onCreate$1.onClick(MainActivity.kt:95)
System.err:     at android.view.View.performClick(View.java:7352)
System.err:     at android.widget.TextView.performClick(TextView.java:14177)
System.err:     at android.view.View.performClickInternal(View.java:7318)
System.err:     at android.view.View.access$3200(View.java:846)
System.err:     at android.view.View$PerformClick.run(View.java:27801)
System.err:     at android.os.Handler.handleCallback(Handler.java:873)
System.err:     at android.os.Handler.dispatchMessage(Handler.java:99)
System.err:     at android.os.Looper.loop(Looper.java:214)
System.err:     at android.app.ActivityThread.main(ActivityThread.java:7045)
System.err:     at java.lang.reflect.Method.invoke(Native Method)
System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:964)

VirtualApk intercepts the startup process of the Activity through the Instrumentation in the hook application ActivityThread to support the plug-in Activity. From the above exception information, the plug-in Activity started in the host Activity this time is not caused by VirtualApk.

Cause finding

The Activity startActivity process is finally processed in the startActivityForResult of the Activity

Activity.java

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            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;
            }

            cancelInputsAndStartExitTransition(options);
            // 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);
            }
        }
    }

You can see that there is an M Instrumentation in the Activity that executes execStartActivity. Based on the above problems, the reference of M Instrumentation is not the Instrumentation after hook of VirtualApk framework
Mminstrumentation is assigned in the attach method of Activity

Activity.java

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        attachBaseContext(context);
		......
        mInstrumentation = instr;
        ......
    }

Where is the attach method of Activity called
performLaunchActivity in the ActivityThread will call the attach method of the Activity and pass the MI instrumentation of the ActivityThread to the parameters in the attach.

Let's sort out the process of problems:
1. Host Activity start
2. Host Activity initializes the virtualapk framework
3. Host Activity loads the plug-in and tries to start the plug-in Activity
From the above process, the MI Instrumentation held by the host Activity when it is started is the Instrumentation without virtualapk hook, which also leads to the failure of subsequent plug-in activities. The startActivity process of Activity class is not processed by virtualapk

terms of settlement

1. Ensure that Virtualapk is initialized first

As far as possible, perform the initialization of virtualapk in the onCreate method of Application

2. Use context Startactivity method

Context. Implementation of startactivity and activity Startactivity is slightly different, as follows
The implementation of Context class is the implementation of startActivity of ContextImpl

 @Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in. A bug was existed between N and O-MR1 which allowed this to work. We
        // maintain this for backwards compatibility.
        final int targetSdkVersion = getApplicationInfo().targetSdkVersion;

        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && (targetSdkVersion < Build.VERSION_CODES.N
                        || targetSdkVersion >= Build.VERSION_CODES.P)
                && (options == null
                        || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                            + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                            + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

You can see that in ContextImpl, the Instrumentation object currently held by ActivityThread is obtained in real time to execute the execStartActivity method This means that this problem also occurs when context is used Startactivity to start the plug-in takes effect

3. Reflect and modify the mminstrumentation of the host Activity

Before starting the plug-in Activity, obtain the value of the host Activity mimstrumentation through reflection and compare it with the Instrumentation value after virtualapk hook. If it is inconsistent, modify the value of Activity mimstrumentation to the Instrumentation value after hook. The code example is as follows:

public static void checkActivityInstrumentation(Activity activity) {
        try {
			
			//The tool class of reflection is in the VirtualApk framework
			
            //Gets the mminstrumentation object of the activity
            Object mInstrumentation =  Reflector.on(activity.getClass())
                    .field("mInstrumentation")
                    .bind(activity)
                    .get<Object>()

            //Get the instrumentation after virtualapk hook
            Object vaInstrumentation = PluginManager.getInstance(activity).getInstrumentation();

            if (mInstrumentation == null || vaInstrumentation == null) {
                return;
            }

            //In case of inconsistency, replace the MI instrumentation held by activity with vaInstrumentation
            if (mInstrumentation != vaInstrumentation) {
                Reflector.on(activity.getClass())
                    .field("mInstrumentation")
                    .bind(activity)
                    .set(vaInstrumentation)
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

As of Android 11, the miminstrumentataion attribute of activity class is still in the greylist of unrestricted interface, which can still be used through reflection at present

Tags: Android

Posted by Sianide on Tue, 17 May 2022 18:11:09 +0300