正文
那接下来无疑是分析makeDexElements()方法了,因为这部分代码比较长,引用一下大神的分析:
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) { // 1.创建Element集合 ArrayList<Element> elements = new ArrayList<Element>(); // 2.遍历所有dex文件(也可能是jar、apk或zip文件) for (File file : files) { ZipFile zip = null; DexFile dex = null; String name = file.getName(); ... // 如果是dex文件 if (name.endsWith(DEX_SUFFIX)) { dex = loadDexFile(file, optimizedDirectory); // 如果是apk、jar、zip文件(这部分在不同的Android版本中,处理方式有细微差别) } else { zip = file; dex = loadDexFile(file, optimizedDirectory); } ... // 3.将dex文件或压缩文件包装成Element对象,并添加到Element集合中 if ((zip != null) || (dex != null)) { elements.add(new Element(file, false, zip, dex)); } } // 4.将Element集合转成Element数组返回 return elements.toArray(new Element[elements.size()]);}
总体来说,DexPathList的构造函数是将一个个的目标(可能是dex、apk、jar、zip , 这些类型在一开始时就定义好了)封装成一个个Element对象,最后添加到Element集合中。
其实,Android的类加载器(不管是PathClassLoader,还是DexClassLoader),它们最后只认dex文件,而loadDexFile()是加载dex文件的核心方法,可以从jar、apk、zip中提取出dex,但这里先不分析了,因为第1个目标已经完成,等到后面再来分析吧。
2->findClass方法
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;}
在DexPathList的构造函数中已经初始化了dexElements,所以这个方法就很好理解了,只是对Element数组进行遍历,一旦找到类名与name相同的类时,就直接返回这个class,找不到则返回null。
为什么是调用DexFile的loadClassBinaryName()方法来加载class?这是因为一个Element对象对应一个dex文件,而一个dex文件则包含多个class。也就是说Element数组中存放的是一个个的dex文件,而不是class文件!!!这可以从Element这个类的源码和dex文件的内部结构看出。
2.热修复的实现方法
加载class会使用BaseDexClassLoader,在加载时,会遍历文件下的element,并从element中获取dex文件方案 ,class文件在dex里面 , 找到dex的方法是遍历数组 , 那么热修复的原理, 就是将改好bug的dex文件放进集合的头部, 这样遍历时会首先遍历修复好的dex并找到修复好的类 . 这样 , 我们就能在没有发布新版本的情况下 , 修改现有的bug。虽然我们无法改变现有的dex文件,但是遍历的顺序是从前往后的,在旧dex中的目标class是没有机会上场的。
3.手撸一个热修复Demo
在了解了大致的热修复过程之后,我们要准备好以下几个东西:
带有bug的apk,并且可以获取到dex文件来修复已修复bug的dex文件因为修复工作是需要隐秘的进行的 , 毕竟有bug也不是什么光彩的事儿 , 所以我吧dex的插入操作放在Splash界面中. 在Splash时先检测有没有dex文件, 如果有则进行插入 , 否则直接进入MainActivity.
1->写一个有bug的程序
哇, 是不是第一次见到这么爽的需求~首先在MainActivty中写一个bug出来:
public class BugTest { public void getBug(Context context) { //模拟一个bug int i = 10; int a = 0; Toast.makeText(context, "Hello,Minuit:" + i / a, Toast.LENGTH_SHORT).show(); }}public class MainActivity extends AppCompatActivity { Button btnFix; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); new BugTest().getBug(MainActivity.this); } private void init() { btnFix = findViewById(R.id.btn_fix); }}
运行这段代码必然会报错的 , 但是我们要首先吧这段代码装到手机上 , 方便之后的修复.