背景

对于先有项目,没有集成热更新功能,但是集成了滴滴的插件化。唯一的缺陷就是无法在不更新宿主的情况下对宿主进行更新。

解决方案

还好在打开宿主时候的splash页和main页进行了加载了一个备用插件,可以在这个备用插件中进行操作。
splash中的代码,在首页加载push插件,原计划此插件是针对不同的手机厂商集成不同的第三方推送,现在正好可修复宿主。

Class cl = plugin.getClassLoader().loadClass("***.huozhu.plugin_push.PushDispatch");
                           Constructor ct = cl.getDeclaredConstructor(new Class[]{Context.class});
                           AppIntent appIntent = (AppIntent) ct.newInstance(new Object[]{context});
                           Intent intent = new Intent();
                           intent.putExtra("from", "splash");
                           appIntent.startAppActivity(intent);

main中的代码

LogUtils.e("plugin--->" + plugin.getPackageName());
                Class cl = plugin.getClassLoader().loadClass("***.huozhu.plugin_push.PushDispatch");
                Constructor ct = cl.getDeclaredConstructor(new Class[]{Context.class});
                AppIntent appIntent = (AppIntent) ct.newInstance(new Object[]{context});
                Intent intent = new Intent();
                intent.putExtra("from", "main");
                appIntent.startAppActivity(intent);

解决过程

目前的需求是修改主页中菜单的数据,要求新添加一行数据

push中的处理
push中的处理
其中extra是处理推送的情况,小米推送(极光)点击无法弹出界面,所以需要自己重写通知。from用于区分来源。比如这次要修改主页的一行数据

先看下宿主代码,找到切入点:

main代码
main代码
宿主中 在这里对数据进行添加,是一个网络请求。考虑会有延时。所以有两个方案,一个是延时后添加一条,一个是hook网络请求,重写添加全部。可行性的话,1很简单,2过于繁琐。针对1方案,再考虑切入点。可以针对menuModels进行数据添加,也可对adapter进行数据添加。考虑到对集合添加后还需要adapter刷新,所以直接对adapter进行添加数据。

代码

 private void setMenu() {
    try {
        Class menuModelClz = contextHost.getClassLoader().loadClass("****.huozhu.ep.model.MenuModel");
        Constructor constructor = menuModelClz.getConstructor(new Class[]{int.class, boolean.class, String.class, int.class});
        Object instance = constructor.newInstance(R.drawable.host_main_menu_driver_group, false, "我的承运人", 7);
        Field field = AppUtils.activities.get(0).getClass().getDeclaredField("adapter");
        LogUtils.e("setMenu  field   " + field.getClass().toString());
        field.setAccessible(true);
        BaseQuickAdapter adapter = (BaseQuickAdapter) field.get(AppUtils.activities.get(0));
        if (adapter == null) {
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    setMenu();
                }
            }, 2000);
        }
        adapter.addData(2, instance);

    } catch (Exception e) {
        LogUtils.e("setMenu Error    " + e.toString());
    }
}

这样就对宿主进行了数据的添加。

其他修改

再有一个支付弹窗,直接使用的是宿主中的方法。所以如果要该支付这块,要更新宿主。那岂不是很麻烦。所以反射就又用到了。功能修改是 点击最后一行的时候由“下单”改为“去支付”。
现有的操作是对这个弹窗视图进行覆写,然后重写其中的点击事件的代码。所有的控件都能顺利拿到,唯独这个点击事件。所有通过反射,当弹窗显示的时候,对这个“下单”的按钮进行hook。

代码:

  private void hookOnClickListener(View view) {
    try {
        // 得到 View 的 ListenerInfo 对象
        //得到getListenerInfo方法
        Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
        getListenerInfo.setAccessible(true);
        //得到ListenerInfo变量
        Object listenerInfo = getListenerInfo.invoke(view);
        // 得到 原始的 OnClickListener 对象
        //获取指定类
        Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
        //获取类中的变量
        Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
        mOnClickListener.setAccessible(true);
        View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
        // 用自定义的 OnClickListener 替换原始的 OnClickListener
        View.OnClickListener hookedOnClickListener = new HookedOnClickListener(originOnClickListener);
        mOnClickListener.set(listenerInfo, hookedOnClickListener);
    } catch (Exception e) {
    }
}

class HookedOnClickListener implements View.OnClickListener {
    private View.OnClickListener origin;

    HookedOnClickListener(View.OnClickListener origin) {
        this.origin = origin;
    }

    @Override
    public void onClick(View v) {
        if (origin != null) {
            origin.onClick(v);
        }
        payBtn.setText("去支付");
    }
}

还有一种方法就是对setText进行监听,当这个控件被赋值为“下单”的时候,重新赋值为“去支付”。

后记

hook的切入点很重要。顺便对反射进行了复习,觉得法国生物学家拉马克提出的“用进废退”果然没错,经常使用就会很顺利,一不使用的就感觉自己退化了一样,跟不上了。可能导致最后的结局就是“适者生存”吧!

参考链接

Java高级特性——反射

Hook技术看这篇就够了

一文读懂 AOP | 你想要的最全面 AOP 方法探讨