安卓插件化课程-第七篇:解决插件中activity跳转的问题

序:

1.本文是安卓插件化课程的第七篇,完整课程链接参见下面链接:

安卓插件化课程-序章_分享+记录-CSDN博客前言:目前安卓领域,插件化十分的流行,本以为这一类的文章会有很多,但是百度一搜,基本上讲的插件化都是皮毛,没有涉及到核心。所以就想写一系列的文章来一步一步深入的讲解插件化,通过实现逐渐增加难度的需求,最终实现插件化方案。章节:插件化系列文章主要包含以下几个篇章:1.加载插件apk中的类2.启动插件apk中的activity3.使用插桩的方式启动apk中的activity4.使用插件apk中的资源5.使用插件apk中的so文件6.插件化加载移动apk。7.插件化目前所遇到的https://blog.csdn.net/rzleilei/article/details/122453035?spm=1001.2014.3001.5502icon-default.png?t=M0H8https://blog.csdn.net/rzleilei/article/details/122453035?spm=1001.2014.3001.5502

2.主要内容

        前面几篇我们讲了,我们宿主启动插件Activity,使用的是启动HostActivity的方式。在宿主中给启动插件的Activity,我们可以改为启动HostActivity的方式,但是插件中怎么办呢?插件中的启动方式是startActivity已经是固定不变的了。

所以在插件中使用老的方式启动,仍然会遇到activity not found的问题,而本文就是解决这个问题。       

一:原理简述


1.如何Hook

具体的流程前面有说过,这里就不详细介绍了。我们只要知道启动activity时,最终都是通过Instrumentation的execStarActivity的方法去启动的。既然前面我们已经hook了Instrumentation,那么我们是不是可以重写这个方法呢?

重写了这个方法,然后本来要跳转AActivity的,我强行改为跳转HostActivity并附带参数,这样就可以走前几章我们的hook流程了。

二:代码编写


1.重写execStarActivity方法

在重写方法里面我们要做两件事:

首先,要判断下,即将要加载的activity,是不是插件中的?因为只有插件中的才需要被代理。

第二,我们要把改完之后的数据,传递回Instrumentation的execStarActivity方法,让后续流程继续执行。

2.如何判断是否代理?

第一个问题,我的想法是这样的,如果是插件中的activity,那么一定是自定义classLoader加载的,所以只要通过自定义classLoader去尝试加载一下这个类,如果存在,那么就进行代理。不存在,则不需要代理。

代码如下:

//判断是否拦截,拦截的标准是插件的classLoader中是否存在该类
        try {
            Object classLoader = ObjectCache.getInstance().getParams(ClassLoader);
            if (classLoader instanceof ClassLoader) {
                ComponentName component = intent.getComponent();
                String className = component.getClassName();
                Class<?> aClass = ((ClassLoader) classLoader).loadClass(className);//不抛异常就是正常的
                intent.setClass(target, HostActivity.class);
                intent.putExtra(MyInstrumentation.ClassName, className);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

3.如何通知回父类方法?

本来是很简单的,只要super.execStartActivity 就可以了,但是很不幸,execStartActivity方法是UnsupportedAppUsage类型的,所以是无法正常调用的。我们需要通过反射去调用父类的方法。

因为我们调用的是父类中的方法,所以这时我们就需要用MethodHandle出马了。通过它来调用父类中的方法

MethodHandle h1;//通过methodHandle调用父类的同名方法


 @RequiresApi(api = Build.VERSION_CODES.O)
    @SuppressLint("SoonBlockedPrivateApi")
    public MyInstrumentation() {
        try {
            MethodHandles.Lookup lookup = MethodHandles.lookup();
            h1 = lookup.findSpecial(Instrumentation.class, "execStartActivity", MethodType.methodType(ActivityResult.class, Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class), MyInstrumentation.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 //替换这个方法
    @RequiresApi(api = Build.VERSION_CODES.O)
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        Log.i("lxltest", "");

        //判断是否拦截,拦截的标准是插件的classLoader中是否存在该类
        try {
            Object classLoader = ObjectCache.getInstance().getParams(ClassLoader);
            if (classLoader instanceof ClassLoader) {
                ComponentName component = intent.getComponent();
                String className = component.getClassName();
                Class<?> aClass = ((ClassLoader) classLoader).loadClass(className);//不抛异常就是正常的
                intent.setClass(target, HostActivity.class);
                intent.putExtra(MyInstrumentation.ClassName, className);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Object invoke = null;
        try {
            invoke = h1.invoke(this, who, contextThread, token, target, intent, requestCode, options);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return (ActivityResult) invoke;
    }

4.插件项目中写一个跳转,然后编译插件APK,拷贝

比如我这里写了一个MainActivity跳转Plugin5Activity的。

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_main);
        findViewById(R.id.text1).setOnClickListener(v -> {
            PluginJni pluginJni = new PluginJni();
            String s = pluginJni.pluginSpliceString("hello", "world");
            ((TextView) findViewById(R.id.result)).setText(s);
        });

        findViewById(R.id.text2).setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, Plugin5Activity.class);
            startActivity(intent);
        });
    }
}

5.验证效果。

在宿主中,首先我们启动插件中的MainActivity。这时候显示两个按钮。

 

我们点击按钮2,这时候我们发现正常跳转了Plugin5Activity,说明我们实验成功。

当然,这里我们发现了一个问题,第二个按钮显示的文本是不对的。这个我们下一章再讲如何去解决。

 

 

三:要点总结

四。代码地址:


项目地址:

GitHub - aa5279aa/android_all_demo: 一直觉得研究各种技术,一个个demo的下载运行太费劲了,为什么不能有一个所有新技术的融合体demo呢?项目为此而生

插件项目位置:android_all_demo/DemoClient/appplugin at master · aa5279aa/android_all_demo · GitHub

调用类位置:android_all_demo/DynamicFragment.kt at master · aa5279aa/android_all_demo · GitHub

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值