上几篇内容介绍了Java的ClassLoader和相关的知识点,总的来说 · Java加载class逻辑是双亲委托模式 · 对于不在class path中的class,可以通过自定义class loader来加载 · 同一个class的不同对象是否可以转换还要看是否在同一个class loader里
明白了这几点之后,我们就可以自己来实现一个热修复框架了。
现在技术圈的热修复可以分为几种套路, · 生成差分包 patch.dex,启动时通过反射把dex放到classloader的Element[]前部 · 生成差分包 patch.dex,将patch和原dex合并,启动时加载合并后的新dex · 通过native进行运行时的方法级替换
以上三种套路,代表框架可以参考 · Nuwa,RocooFix,手Q团队提出 · Tinker,微信团队提出 · AndFix,阿里百川
这几种都是成熟的框架,可以直接使用。其实在明白了ClassLoader的原理后,我们自己也可以造自己的轮子,这样对热修复的原理能有个深刻的认识。
"插件化开发"已经是老生常谈了,但其实说起来很简单,简单的举例,把项目中不同的功能通过接口来隔离,就算是组件化开发了。
举个非常简单的例子,对于不同功能或者渠道我们需要展示不同的Toast内容,这样可以把内容获取做成接口暴露给app,运行时动态的加载不同的class来显示不同的toast 最终完成的代码接口如下图
Alt text
我们先定义一个接口给app和组件/插件,或者说 component比较没有歧义
package com.phoenix.hotswitch;
public interface CustomInterface {
public String getText();
}
这个接口一式两份,在主工程和插件工程里都有,然后我们在插件中需要实现实现这个接口
package com.phoenix.hotswitch;
public class CustomImpl implements CustomInterface {
private static final String EXT_STRING = "hello from the other side";
@Override
public String getText() {
return EXT_STRING;
}
}
这样就完成了插件的代码了,我们要把它打成jar包,一般jar包里的是class文件,Android的dalvik/ant是不能直接加载的,我们需要用sdk下的platform-tools的dex工具再把jar包打一次。
主工程需要有一个类,这个类用自定义的ClassLoader来加载插件,然后通过反射获取插件的实现类,通过上一步我们定义好的接口来调用实现类。 姑且把这个类叫 ToastFactory,它有几个接口,分别是
public class ToastFactory {
public void showCustomToast(Type type){...}
private String getMsgFromInernal(){...}
private String getMsgFromExt(){...}
public void loadClass() {...}
}
loadClass()做了这么件事情, 加载外部class,然后实例化给mExt,调用的时候就可以通过之前定义好的接口来使用了。
public void loadClass() {
String target = extractJarFromAsset();
if(TextUtils.isEmpty(target)) {
return;
}
DexClassLoader loader = new DexClassLoader(target, mContext.getExternalCacheDir().getAbsolutePath(), null, mContext.getClassLoader());
try {
Class extImpl = loader.loadClass("com.phoenix.hotswitch.CustomImpl");
mExt = (CustomInterface) extImpl.newInstance();
Toast.makeText(mContext, "ext class loaded success", Toast.LENGTH_SHORT).show();
} catch (Exception exp) {
exp.printStackTrace();
}
}
在没有 loadClass 之前,点 external toast会展示默认的toast,点了 loadClass 之后会把插件加载进来,再展示外部Toast的时候,就会展示组件里的Toast了。
这里为什么要用接口的原因是为了让模块之间去耦合,避免模块间耦合度高,而在编程的过程中不小心导致的一些问题,比如ClassCastException。
然后我们就可以在主工程中任意一个地方通过ToastFactory来使用组件的功能了。完成的效果如下面的gif图所示。
这种app架构有个好处,就是各个功能之间绝对独立,在开发的时候可以各个小组分别开发,最终以jar包的形式给主工程通过约定好的接口使用。 而这种方式还可以实现在线的功能更新,只需要下发不同的jar包,在统一的接口约束下线上的app可以实现不同的功能。 我把这个demo的代码上传到了GitHub上,有兴趣的可以下载下来研究源码。