Android热修复之 - 打补丁原来如此简单
来源:互联网 发布:换手率炒股软件 编辑:程序博客网 时间:2024/06/05 05:15
1.概述
今天我们来看一看纯java代码打补丁的方式会是怎样,纯Java代码是什么意思?因为上一期讲到阿里开源的热补丁里面涉及到NDK,会是会用但要自己去写NDK很多人估计不考谱,今天我们就用一种最简单的方式去实现,灵感来自腾讯提供的解决方案Tinker,但是我们自己的实现方式与它又不相同。上一周要大家去看类的加载机制也不知道大家看得怎么样了,某些估计连BaseDexClassLoader的源码都找不到,这里提供一个在线阅读网站http://androidxref.com。
视频讲解:http://pan.baidu.com/s/1dE4UsbZ
相关文章:
2017Android进阶之路与你同行
Android热修复之 - 收集崩溃信息上传至服务器
Android热修复之 - 阿里开源的热补丁
Android热修复之 - 打补丁原来如此简单
2.源码阅读
2.1 Activity启动流程
为很么要读Activity的流程呢?因为到后面我们要讲插件开发那也是个蒙B的坎,了解了解也好,但这里我就不介绍那么详细,后面插件开发再说,我只想知道Activity是怎么创建的呢?我贴点源码出来:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // ........省略代码 Activity activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); // ........省略代码 } /** * Perform instantiation of the process's {@link Activity} object. The * default implementation provides the normal system behavior. * * @param cl The ClassLoader with which to instantiate the object. * @param className The name of the class implementing the Activity * object. * @param intent The Intent object that specified the activity class being * instantiated. * * @return The newly instantiated Activity object. */ public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { // 利用ClassLoader通过名字加载类然后通过反射创建对象 return (Activity)cl.loadClass(className).newInstance(); }
2.2 ClassLoader源码解析
Activity的ClassLoader我们仔细看源码是 PathClassLoader extends BaseDexClassLoader extends ClassLoader 而loadClass这个方法在ClassLoader 中:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); // this is the defining class loader; record the stats } return c; }
BaseDexClassLoader部分源码:
public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; /** * Constructs an instance. * * @param dexPath the list of jar/apk files containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param optimizedDirectory directory where optimized dex files * should be written; may be {@code null} * @param libraryPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the parent class loader */ public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; }}
这部分代码请务必看懂很简单,如果看不懂周末可以看看视频,从源码得知,当我们需要加载一个class时,实际是从pathList中去找的,而pathList则是DexPathList的一个实体。
DexPathList部分源码:
/*package*/ final class DexPathList { private static final String DEX_SUFFIX = ".dex"; private static final String JAR_SUFFIX = ".jar"; private static final String ZIP_SUFFIX = ".zip"; private static final String APK_SUFFIX = ".apk"; /** class definition context */ private final ClassLoader definingContext; /** * List of dex/resource (class path) elements. * Should be called pathElements, but the Facebook app uses reflection * to modify 'dexElements' (http://b/7726934). */ private final Element[] dexElements; /** * Finds the named class in one of the dex files pointed at by * this instance. This will find the one in the earliest listed * path element. If the class is found but has not yet been * defined, then this method will define it in the defining * context that this instance was constructed with. * * @param name of class to find * @param suppressed exceptions encountered whilst finding the class * @return the named class or {@code null} if the class is not * found in any of the dex files */ public Class findClass(String name, List<Throwable> suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; }}
从这段源码可以看出,dexElements是用来保存dex的数组,而每个dex文件其实就是DexFile对象。遍历dexElements,然后通过DexFile去加载class文件,加载成功就返回,否则返回null,看到这里应该基本知道我们想干啥了,我打算在dexElements上面做手脚,而且解释也说了Should be called pathElements, but the Facebook app uses reflection 我想问问跟Facebook啥子关系,不管了它说可以用reflection反射。
我们打算采用dex分包,从服务上获取到fix.dex后采用反射机制往正在运行的ClassLoader中的PathList中的dexElements中去插入我们fix.dex中的dexElements并且把它插入到正在运行的ClassLoader最前面,这样我们for循环找class类的时候就会找我们的fix.dex中的class了,原来有Bug的calss就不会被遍历到了。
3.Dex分包问题
如果你是用的Eclipse做开发,不过现在应该很少了吧,Eclipse分包比较蛋疼需要去写脚本不过好就好在这一快的资料比较多。
Android Studio我们可以直接配置,但是网上的资料都是用来解决方法数超过65536,问题是我们现在没有超过按照网上提供的配置根本就不会分包,当然我们还有很多方法如获取到class类自己用命令制作dex包,但是我想问一下就算是知道哪一个class报错了修改混淆后牵连的东西会太多估计也行不同。
我们其实还是可以用Android Studio自带的我们去官网找最新的,百度搜索提供的分包方案比较老了所以行不通,且看我如何配置:
dexOptions {//dex配置 javaMaxHeapSize "4g" preDexLibraries = false def listFile = project.rootDir.absolutePath+'/app/maindexlist.txt' additionalParameters = [//dex参数详见 dx --help '--multi-dex',//多分包 '--set-max-idx-number=60000',//每个包内方法数上限 '--main-dex-list='+listFile,//打包进主classes.dex的文件列表 '--minimal-main-dex'//使上一句生效 ] }
在build.gradle中加入以上配置,我们再在maindexlist.txt中保存我们主dex的类即可运行,解压apk如果可以看到有两个dex,代表这一步已经成功了,如果想保险一点可以反编译dex看看主classes.dex里面到底是不是只要我们配置的下面这三个类。
com/hc/multidexdemo/MainActivity.classcom/hc/multidexdemo/BuildConfig.classcom/hc/multidexdemo/BaseApplication.class
4.合并补丁Dex包
假如我们某个类出现了异常闪退的情况,那么我们修改完成重新打包获取classes2.dex作为我们的补丁包fix.dex放在我们的服务器上面,我们客户端访问服务器下载fix.dex进行合并即可修复。
/** * 合并注入 * @param context * @throws Exception */ private static void injectDexElements(Context context) throws Exception { ClassLoader pathClassLoader = context.getClassLoader(); File outDexFile = new File(context.getDir("odex", Context.MODE_PRIVATE).getAbsolutePath() + File.separator + "out_dex"); if (!outDexFile.exists()) { outDexFile.mkdirs(); } // 合并成一个数组 Object applicationDexElement = getDexElementByClassLoader(pathClassLoader); for (File dexFile : mFixDex) { ClassLoader classLoader = new DexClassLoader(dexFile.getAbsolutePath(),// dexPath outDexFile.getAbsolutePath(),// optimizedDirectory null, pathClassLoader ); // 获取这个classLoader中的Element Object classElement = getDexElementByClassLoader(classLoader); Log.e("TAG", classElement.toString()); applicationDexElement = combineArray(classElement, applicationDexElement); } // 注入到pathClassLoader中 injectDexElements(pathClassLoader, applicationDexElement); } /** * 把dexElement注入到已运行classLoader中 * @param classLoader * @param dexElement * @throws Exception */ private static void injectDexElements(ClassLoader classLoader, Object dexElement) throws Exception { Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); Field pathListField = classLoaderClass.getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathList = pathListField.get(classLoader); Class<?> pathListClass = pathList.getClass(); Field dexElementsField = pathListClass.getDeclaredField("dexElements"); dexElementsField.setAccessible(true); dexElementsField.set(pathList, dexElement); } /** * 合并两个dexElements数组 * * @param arrayLhs * @param arrayRhs * @return */ private static Object combineArray(Object arrayLhs, Object arrayRhs) { Class<?> localClass = arrayLhs.getClass().getComponentType(); int i = Array.getLength(arrayLhs); int j = i + Array.getLength(arrayRhs); Object result = Array.newInstance(localClass, j); for (int k = 0; k < j; ++k) { if (k < i) { Array.set(result, k, Array.get(arrayLhs, k)); } else { Array.set(result, k, Array.get(arrayRhs, k - i)); } } return result; } /** * 获取classLoader中的DexElement * @param classLoader ClassLoader */ public static Object getDexElementByClassLoader(ClassLoader classLoader) throws Exception { Class<?> classLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader"); Field pathListField = classLoaderClass.getDeclaredField("pathList"); pathListField.setAccessible(true); Object pathList = pathListField.get(classLoader); Class<?> pathListClass = pathList.getClass(); Field dexElementsField = pathListClass.getDeclaredField("dexElements"); dexElementsField.setAccessible(true); Object dexElements = dexElementsField.get(pathList); return dexElements; }
就这么几个方法即可完成修复其实挺简单的,大家也可以去看看腾讯提供的修复方案,但是我一看到要防止打标记就开始蒙B了,后面几篇我们又要回归设计模式的讲解了,视频讲解需要等每周末晚上八点。
视频讲解:http://pan.baidu.com/s/1dE4UsbZ
相关文章:
2017Android进阶之路与你同行
Android热修复之 - 收集崩溃信息上传至服务器
Android热修复之 - 阿里开源的热补丁
Android热修复之 - 打补丁原来如此简单
- Android热修复之 - 打补丁原来如此简单
- Android打补丁 热修复(HotFix)小结
- (4.2.32.6)android热修复之Andfix方式:Andfix的Hook方式打补丁原理
- android实现热更新,打补丁,进行差异文件修复
- Android之热修复
- Android 热修复之AndFix
- Android热修复之AndFix
- Android进阶之热修复
- Android热修复之AndFix
- Android热修复之Tinker
- android热修复之AndFix
- Android优化之热修复
- 热修复之JSPatch简单使用
- Android 热修复其实很简单
- Android 热修复其实很简单
- Android 热修复其实很简单
- Android 热修复其实很简单
- Android热修复方案--sophix简单使用
- 编译优化与语法糖
- 数据结构::STL库里map和set的用法简介
- Unity5.5.0f3中".bytes"文件出现"Unrecognized assets cannot be included in AssetBundles"问题
- InitializingBean
- ffmpeg中的时间
- Android热修复之 - 打补丁原来如此简单
- 生产者消费者模式(Producer-Consumer)
- Java基础点总结
- 添加百度自动推送工具代码以提高收录可能
- go 协程与主线程强占运行
- WEB入门.五 页面设计简介
- 【Java每日一题】20170216
- Go 之旅二: 流程控制语句
- 数据结构实例<二>(斐波那契数列)入门