virtualapk插件化框架初始化时机对启动插件Activity的影响

问题背景

virtualapk框架文档上是有写明需要在Application中的onCreate方法中进行初始化工作的,按照文档上的步骤来使用是没有问题的,从宿主中启动插件Activity也是可以正常运行.
由于业务的需要,在项目中使用virtualApk不一定会在Application中的onCreate方法去初始化,所以这边出现了这样的一个情况:
在宿主当前Activity中初始化Virtualapk,并且在宿主当前Activity中启动插件Activity,这个时候启动失败,抛出异常


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是通过hook应用ActivityThread中的Instrumentation来拦截Activity的启动流程实现对插件Activity的支持,从上述异常信息来看,这次在宿主Activity启动插件Activity并没有经过VirtualApk所导致。

原因查找

Activity startActivity流程 最终是在Activity的startActivityForResult处理

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

可以看到Activity里面有个mInstrumentation执行了execStartActivity,基于上述问题的情况,此时mInstrumentation的引用并不是VirtualApk框架hook后的Instrumentation.
mInstrumentation是在Activity的attach方法中进行赋值的

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

那么Activity的attach方法又是在哪里调用的呢
在ActivityThread中的performLaunchActivity会调用Activity的attach方法,并将ActivityThread的mInstrumentation传给attach中的参数。

让我们整理下出现问题的流程:
1.宿主Activity启动
2.宿主Activity初始化virtualapk框架
3.宿主Activity加载插件并且尝试启动插件Activity
从上面流程来看,宿主Activity启动时所持有的mInstrumentation是未经过virtualapk hook后的Instrumentation,这也导致了后续启动插件Activity失败的原因,在Activity类的startActivity流程未经过virtualapk的处理

解决办法

1.确保Virtualapk先初始化

尽可能在Application的onCreate方法中执行virtualapk的初始化

2.使用Context.StartActivity方法

Context.StartActivity的实现和Activity.StartActivity略微有不同,以下为
Context的实现类ContextImpl的startActivity的实现

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

可以看到在ContextImpl中,是实时拿到ActivityThread当前持有的Instrumentation对象来执行execStartActivity方法的.这也就意味着在出现问题的这种情况下 使用Context.startActivity来启动插件是生效的

3.反射修改宿主Activity的mInstrumentation

在启动插件Activity前,通过反射获取宿主Activity mInstrumentation的值,将其与virtualapk hook后的Instrumentation值作比较,如果不一致,则将Activity mIntrumentation的值修改为hooke后的Instrumentation,代码示例如下:

public static void checkActivityInstrumentation(Activity activity) {
        try {
			
			//反射的工具类是VirtualApk框架里面的
			
            //获取activity的mInstrumentation对象
            Object mInstrumentation =  Reflector.on(activity.getClass())
                    .field("mInstrumentation")
                    .bind(activity)
                    .get<Object>()

            //获取virtualapk hook后的instrumentation
            Object vaInstrumentation = PluginManager.getInstance(activity).getInstrumentation();

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

            //不一致则将 actviity持有的mInstrumentation换成vaInstrumentation
            if (mInstrumentation != vaInstrumentation) {
                Reflector.on(activity.getClass())
                    .field("mInstrumentation")
                    .bind(activity)
                    .set(vaInstrumentation)
            }

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

    }

截止到Android 11,Activity类的mInstrumentataion属性还处在非限制接口的greylist中,目前还是可以通过反射去使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值