根据类加载机制探究项目插件化过程(二)

上一篇我们根据类加载的机制,在主应用中调用了插件中的方法。这次呢我们挑战下是否能够在主应用中调用到插件的页面,我们知道,要想调用一个页面必须要先声明这个页面,那我们能不能不声明这个页面调用到他呢,下面我们做下尝试。

在插件中新建一个简单的页面,然后我们用assembleDebug命令将插件打成apk包,然后呢我们还放到sdcard目录下,在主应用的点击事件中增加跳转应用页面代码如下:

 Intent intent = new Intent();
 intent.setComponent(new ComponentName("plugindemo.demo.com.plugintest","plugindemo.demo.com.plugintest.PluginMainActivity"));
 startActivity(intent);

setComponent第一个参数是插件的包名,第二个参数是插件主Activity名。

点击页面按钮发现异常报错了,如下:

这个异常的意思是提示我们未在AndroidMainfest.xml中声明我们要跳转的页面PluginMainActivity,平时我们做的应用各个页面都要在此声明,否则无法访问,那这个问题我们怎么解决呢?

我们想要在主应用中访问另外一个App的页面,没有办法直接访问,必须要告诉AMS,让AMS帮我们打开,比如我要访问PluginMainActivity,告诉AMS我知道PluginMainActivity的地址路径和名称,你帮我打开吧,然后AMS经过一系列骚操作,最终帮你打开了你想要的页面,问题来了,那么我能不能编个假名字骗过AMS,然后我在假装就是假名字的本人,让AMS打开呢,下面我们先复习下Activity的启动流程,看看AMS做了啥骚操作帮你打开页面的,代码就不放了太多了,可以跟着我的时序图走一遍源码:

既然提示我们未声明要跳转的页面,那我们就给他声明一个,比如说我给他声明了一个PluginActivity的页面,具体如下:

 <activity android:name=".PluginActivity" />

我们要用定义的这个PluginActivity来替换我们要访问的插件页面PluginMainActivity

我们要想骗过AMS,必须在访问AMS之前做操作,下面我们模拟下,直接上代码了,注释都写的很清楚了:

        try {
            //2.1获取到ActivityManager这个类
            Class<?> clazz = Class.forName("android.app.ActivityManager");
            //2.2从ActivityManager这个类中获取到IActivityManagerSingleton私有属性(
            // (IActivityManager)ActivityManager.getService()
            // =====>(Singleton<IActivityManager>)IActivityManagerSingleton.get()
            // =========>public abstract class Singleton<T>--->public final T get()----->return mInstance----->private T mInstance
            // )
            Field singletonField = clazz.getDeclaredField("IActivityManagerSingleton");
            //2.3设置属性的作用域
            singletonField.setAccessible(true);//作用域
            //2.4获取到原来的对象singleton
            Object singleton = singletonField.get(null);

            //1.1获取Singleton类
            Class<?> singletonClass = Class.forName("android.util.Singleton");
            //1.2从Singleton类中获取mInstance私有属性
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            //1.3设置作用域
            mInstanceField.setAccessible(true);
            //2.5从原来的singleton对象中获取mInstance对象
            final Object mInstance = mInstanceField.get(singleton);

            Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
            //1.先获取到proxyInstance这个对象
            // IActivityManager 的Class 对象 第一步先获取到proxyIn这个对象 第二步反射替换这个对象
            Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                    new Class[]{iActivityManagerClass}, new InvocationHandler() {

                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if ("startActivity".equals(method.getName())) {
                                int index = 0;
                                // 将插件的Intent 替换为 代理的Intent
                                for (int i = 0; i < args.length; i++) {
                                    if (args[i] instanceof Intent) {
                                        index = i;
                                        break;
                                    }
                                }
                                // 获取到插件的intent
                                Intent intent = (Intent) args[index];
                                // 替换成代理的Intent
                                Intent proxyIntent = new Intent();
                                proxyIntent.setClassName("plugindemo.com.plugindemoone",
                                        PluginActivity.class.getName());
                                // 保存插件的Intent
                                proxyIntent.putExtra(TARGET_INTENT, intent);

                                args[index] = proxyIntent;
                            }

                            // IActivityManager 对象 --- 通过反射
                            return method.invoke(mInstance, args);
                        }
                    });

            // IActivityManager对象 = proxyInstance
            // new IActivityManager();// 这个是系统的  == new IActivityManagerProxy();

//            找到singleton中的get方法的mInstance
            //2.用proxyInstance对象替换原来的对象singleton达到替换效果,在这之前必须获取到原来的singleton
            mInstanceField.set(singleton, proxyInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }

然后我们看下运行效果

看到这个提示了吧,我们主应用是访问的啥,是PluginMainActivity,现在我们成功的将要访问的页面通过偷梁换柱,改成了PluginActivity,为啥要换成这个页面呢,因为我们在xml中注册了这个页面,他是合法的,可以通过AMS的验证,到这里我们就成功了一半了,我们的目的是在通过AMS的验证之后,在想办法将PluginActivity换成PluginMainActivity,继续上第二步代码:

try {
            //获取ActivityThread类
            Class<?> clazz = Class.forName("android.app.ActivityThread");
            //从ActivityThread类clazz中获取sCurrentActivityThread属性成员(private static volatile ActivityThread sCurrentActivityThread;)
            Field sCurrentActivityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            //拿到activityThread对象
            Object activityThread = sCurrentActivityThreadField.get(null);
            //从ActivityThread类clazz中拿到mh属性成员(final H mH = new H();)
            Field mHField = clazz.getDeclaredField("mH");
            //
            mHField.setAccessible(true);
            //拿到mH对象
            Object mH = mHField.get(activityThread);

            //拿到Handler类
            Class<?> handlerClass = Class.forName("android.os.Handler");
            //从handlerClass中获取mCallback属性成员(final Callback mCallback;)
            Field mCallbackField = handlerClass.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);

            // Handler 对象
            // 创建一个 Callback 替换系统的 Callback 对象
            //new 一个mCallback等于空发送handlerMessage(msg)替换系统的callbank对象 Handler的dispatchMessage方法
            mCallbackField.set(mH, new Handler.Callback() {

                @Override
                public boolean handleMessage(@NonNull Message msg) {
                    // 替换Intent
                    switch (msg.what) {
                        //100值的说明:1.sendMessage(H.LAUNCH_ACTIVITY, r);
                        // 2.public static final int LAUNCH_ACTIVITY = 100(private class H extends Handler);
                        case 100://
                            // ActivityClientRecord == msg.obj
                            try {
                                // 获取PluginActivity的intent
                                Field intentField = msg.obj.getClass().getDeclaredField("intent");
                                intentField.setAccessible(true);
                                Intent proxyIntent = (Intent) intentField.get(msg.obj);

                                //获取插件的Intent
                                Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
                                // 判断调用的是否是插件的,如果不是插件的,intent就会为空
                                if (intent != null) {
                                    intentField.set(msg.obj, intent);
                                }
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            break;
                    }
                    return false;
                }
            });

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

代码跑起来,我们在看下运行效果图:

老铁们,6不6,要是6就关注下公众号点赞转发走起来吧。

Python网络爬虫与推荐算法新闻推荐平台:网络爬虫:通过Python实现新浪新闻的爬取,可爬取新闻页面上的标题、文本、图片、视频链接(保留排版) 推荐算法:权重衰减+标签推荐+区域推荐+热点推荐.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

博主逸尘

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

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

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

打赏作者

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

抵扣说明:

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

余额充值