专栏名称: 刘望舒
腾讯云最具价值专家
目录
相关文章推荐
鸿洋  ·  全新导航库 Jetpack ... ·  昨天  
复利大王  ·  三名日本女技师来北京打工被抓 ·  昨天  
复利大王  ·  保时捷跌下神坛 ·  2 天前  
复利大王  ·  高盛新人Banker上班,用鼠标被上司骂破防 ·  2 天前  
51好读  ›  专栏  ›  刘望舒

手动实现Android热修复

刘望舒  · 掘金  · android  · 2018-07-12 10:32

正文

请到「今天看啥」查看全文


那接下来无疑是分析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);    }}

运行这段代码必然会报错的 , 但是我们要首先吧这段代码装到手机上 , 方便之后的修复.







请到「今天看啥」查看全文