背景
对于先有项目,没有集成热更新功能,但是集成了滴滴的插件化。唯一的缺陷就是无法在不更新宿主的情况下对宿主进行更新。
解决方案
还好在打开宿主时候的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);
解决过程
目前的需求是修改主页中菜单的数据,要求新添加一行数据
其中extra是处理推送的情况,小米推送(极光)点击无法弹出界面,所以需要自己重写通知。from用于区分来源。比如这次要修改主页的一行数据先看下宿主代码,找到切入点:
宿主中 在这里对数据进行添加,是一个网络请求。考虑会有延时。所以有两个方案,一个是延时后添加一条,一个是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的切入点很重要。顺便对反射进行了复习,觉得法国生物学家拉马克提出的“用进废退”果然没错,经常使用就会很顺利,一不使用的就感觉自己退化了一样,跟不上了。可能导致最后的结局就是“适者生存”吧!