摘要:也是较容易出现问题的一个模块,对层实现热修复,对项目的稳定性是十分有利的。需求在不修改层和层的基础上,实现对层的热修复。通过不停的更换所加载的文件,但调用方法一致,来达到热修复的目的。综上,本方案应该是可以在项目中实际应用的一个热修复方案。
本文为作者原创,转载请注明出处由来
现在开发android项目大部分都已经由mvc转移到了mvp,关于mvp是什么大致也不必多说了,无非三个层:
m:model层,一般封装对数据的操作,增删改查,接口访问等等。
v:view层,也就是视图层,视图层不主动做什么,只是根据某些事件作出对应的视图展示。
p:Presenter层,也就是逻辑层。
图解(之前在别的博文看到的,觉得比较好就直接拿来用了):
在mvp模式中,model层和view层不再有直接交互,而把相应的工作交给了“中间人”Presenter层来处理。
因此在我们的项目中,Presenter层可以说是一个改动比较频繁,逻辑比较复杂的一个模块。也是较容易出现问题的一个模块,对Presenter层实现热修复,对项目的稳定性是十分有利的。
总结一下:
项目背景:mvp的项目。
需求:在不修改model层和view层的基础上,实现对Presenter层的热修复。
构想核心思想就是Presenter层只写接口,然后使用java的classloader机制加载Presenter层的实现类来产生对象然后赋值给接口指针调用。通过不停的更换classloader所加载的文件,但调用方法一致,来达到热修复的目的。
下面是我画的一个整体的结构图。
既然大体思路都有了,那么咱们就来尝试一下能不能行得通吧。做一个案例,功能非常简单,界面上一个按钮,点击按钮吐司一个字符串,这个字符串通过。
1.创建项目除了一路next之外我这里想说的实际上是项目module结构:
app里面主要写项目的相关界面。
motorlib里面主要写presenter接口和热修复、classloadler等相关的代码,另外model层也可以在里面写,其实它们可以写在app里面,但这样组件化的话,更有利于解耦。
motorhot这里就是写presenter的具体实现了,另外model层也可以在里面写,那样的话对于后台接口返回的数据格式变更等也可以进行修复了。
再设置一下这三个module之间的引用关系。
.appbuild.gradle
dependencies { ... //只关心接口,不关心实现,因此只引用motorlib implementation project(":motorlib") }
.motorhotbuild.gradle
dependencies { ... //需要集成motorlib定义的接口,因此需要引用 implementation project(":motorlib") }
.motorlibbuild.gradle
dependencies { ... //因为只定义接口,所以都不需要引用 }2.persenter接口
在motorlib中创建一个java接口,AppParsenter,里面只有一个方法:
public interface AppParsenter { String getStr(); }3.实现接口persenter接口
在motorlib中创建一个java类,实现AppParsenter:
public class AppParsenterImpl implements AppParsenter { @Override public String getStr() { return "hello word !"; } }4.热修复文件打包
在android studio中的右侧,打开Gradle一栏,然后点击(也可以直接运行gradlew命令gradlew motorhot:assembleRelease):
打包后的jar包文件存放在.motorhotuildintermediatesundles
eleaseclasses.jar
拿到打包好的classes.jar,然后使用android sdk提供的dx.bat将jar包转换为dex:
CD ..androidsdkuild-tools20.0.0 dx --dex --output=..hotfix.dex ..classes.jar
这样,用于热修复的.dex文件就打包完成了。
5.ObjectFactory因为dex的加载离不开DexClassLoader,因此我在这里先对DexClassLoader进行了一下封装:
public class Motor { private Context context; private MotorListener listener; private static volatile Motor motor; private DexClassLoader mClassLoader; private Motor() { } public static Motor get() { if (motor == null) { synchronized (Motor.class) { if (motor == null) { motor = new Motor(); } } } return motor; } public static void init(Context context, MotorListener listener) { get(); motor.context = context; motor.listener = listener; //加载dex motor.initClassLoader(); listener.initFnish(); //todo 网络检查dex更新 } private void initClassLoader() { String dexDir = context.getCacheDir().getAbsolutePath() + "/dex/"; File dir = new File(dexDir); if (!dir.exists()) { dir.mkdirs(); } File dexFile = new File(dir, "mydex.dex"); if (!(dexFile.exists() && dexFile.isFile() && dexFile.length() > 0)) { copyFileFromAssets(context, "mydex.dex", dexFile.getAbsolutePath()); } mClassLoader = new DexClassLoader( dexFile.getAbsolutePath(), context.getFilesDir().getAbsolutePath() , null, context.getClassLoader()); } public boolean copyFileFromAssets(Context context, String assetName, String path) { boolean bRet = false; try { InputStream is = context.getAssets().open(assetName); File file = new File(path); file.createNewFile(); FileOutputStream fos = new FileOutputStream(file); byte[] temp = new byte[64]; int i = 0; while ((i = is.read(temp)) > 0) { fos.write(temp, 0, i); } fos.close(); is.close(); bRet = true; } catch (IOException e) { e.printStackTrace(); } return bRet; } public DexClassLoader getClassLoader() { return mClassLoader; } public void setmClassLoader(DexClassLoader mClassLoader) { this.mClassLoader = mClassLoader; } public Context getContext() { return context; } public void setContext(Context context) { this.context = context; } public interface MotorListener { void initFnish(); void initError(Throwable throwable); } }
单例,首先从assets中把.dex文件拷贝到android的沙盒目录(android加载dex的时候有限制,必须是在沙盒目录中才能加载),然后构建好了mClassLoader就完成了。
方便起见,热修复文件就不从网络下载了,因此直接把打包好的.dex拷贝到项目的assets文件夹中。
在motorlib中创建ObjectFactory,用来通过classloader生产对象:
public class ObjectFactory { public static Object make(String type) { try { Classap = (Class ) Motor.get() .getClassLoader().loadClass("com.example.motordex.AppParsenterImpl"); AppParsenter o = ap.newInstance(); return o; } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } return null; } }
到了这里就可以注意到了,反射构建对象的时候ap.newInstance();没法传参数,因此对于Parsenter的实现类中,必须有一个无参的构造方法。
6.运行app中新建一个activity,设置一个按钮然后添加点击事件:
layout/activity_main.xml
com.example.miqt.dexmvppdemo.MainActivity
public class MainActivity extends AppCompatActivity { AppParsenter ap; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Motor.init(this, new Motor.MotorListener() { @Override public void initFnish() { ap = (AppParsenter) ObjectFactory.make(MainActivity.class.getName()); } @Override public void initError(Throwable throwable) { } }); } public void getStr(View view) { String str = ap.getStr(); Toast.makeText(this, str, Toast.LENGTH_SHORT).show(); } }
运行结果:
修改motorhot中的AppParsenterImpl,模拟修复了一个bug:
public class AppParsenterImpl implements AppParsenter { @Override public String getStr() { return "fix a bug!"; } }
重复4步骤,打包运行:
事实证明这种构想完全可以实现,并且可以在不使用其他框架,达到热修复的目的,并且不仅仅是presenter层,在其他的层应用这种方式,也是可以的。
但实际上从上面的实践,也可以发现一些问题:
dex文件线上下载或拷贝过程中如果损坏,则可能引起程序崩溃。不过我们可以使用对dex文件取hash码然后对比下载后的文件,如果不一致则证明出错,重新下载拷贝。解决这个问题。
dex暴露在用户手机沙盒目录中,而android的沙盒目录在root之后是可以直接访问的,因此有可能被人拿到dex反编译,修改逻辑搞破坏。并且因为反射的影响,motorhot中的类文件是不可以进行混淆的,因为混淆之后就会报找不到类的异常。不过关于这个也是有解决办法的,我想到的那就是对热修复文件加密,在loader之前再在内存中解密。这样别人没法知道加密规则,也就没法解密了。
双亲委托机制,在classloader加载外部dex之前会先检查本地是否已经存在同名的类,如果有则优先加载本地已经存在的类,因此在实际使用中我们最好还要禁用双亲委托机制。
综上,本方案应该是可以在项目中实际应用的一个热修复方案。
完整代码:https://github.com/miqt/MVPHo...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/76756.html
摘要:理解内存模型对多线程编程无疑是有好处的。干货高级动画高级动画进阶,矢量动画。 这是最好的Android相关原创知识体系(100+篇) 知识体系从2016年开始构建,所有的文章都是围绕着这个知识体系来写,目前共收入了100多篇原创文章,其中有一部分未收入的文章在我的新书《Android进阶之光》中。最重要的是,这个知识体系仍旧在成长中。 Android 下拉刷新库,这一个就够了! 新鲜出...
摘要:当然,目前看来,的势头是盖过的。平台的插件化框架也是存在多种方案,各有优劣。常见的携程的,的,的以及等。另外,插件化也是解决问题的一大利器。 在 2016 年学 Android 是一种什么样的体验? @author ASCE1885的 Github 简书 微博 CSDN 知乎本文由于潜在的商业目的,不开放全文转载许可,谢谢! showImg(/img/remote/146000000...
阅读 1643·2021-08-30 09:45
阅读 1710·2019-08-30 15:54
阅读 1155·2019-08-30 14:02
阅读 1888·2019-08-29 16:21
阅读 1557·2019-08-29 13:47
阅读 3124·2019-08-29 12:27
阅读 676·2019-08-29 11:01
阅读 2640·2019-08-26 14:04