在此说明,本次说讲的热修复为精简版
不涉及到字节码插桩
基于Android9源码、没有做版本适配、基于原理实现简单的热修复
正文=====正文============================正文
什么是热修复?
热:及时的
修复:修复bug
及时的修复bug,在日常使用中是非常有必要的
首先,热修复设计到类的加载,那么,我们的app是如何加载这些类的?各种类加载器又有何联系?
ClassLoader.class
这个类就是我们的类加载器,可以看到,这个类以及它的派生类在AS中都看不到实现,此刻我们应该借助源码工具(工具在这里就不多说了)来看。
重要方法
loadClass(String name, boolean resolve)利用双亲委托机制
先放图
可以看到ClassLoader.class是一个抽象类,我们可以定义自己的类加载器
但是在这里我要讲的是PathClassLoader、BaseDexClassLoader、ClassLoader、DexPathList
public class PathClassLoader extends BaseDexClassLoader {
//我们的dex路径和类加载器
37 public PathClassLoader(String dexPath, ClassLoader parent) {
38 super(dexPath, null, null, parent);
39 }
63 public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
64 super(dexPath, null, librarySearchPath, parent);
65 }
}
public class BaseDexClassLoader extends ClassLoader {
...
46 private final DexPathList pathList;
63 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
64 String librarySearchPath, ClassLoader parent) {
65 this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
66 }
//实例化PathClassLoader执行这个方法
71 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
72 String librarySearchPath, ClassLoader parent, boolean isTrusted) {
73 super(parent);
//获取pathList
74 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
75
76 if (reporter != null) {
77 reportClassLoaderChain();
78 }
79 }
@Override
130 protected Class<?> findClass(String name) throws ClassNotFoundException {
131 List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
//在pathList中找类,找的到就返回当前类,没找到就报错
132 Class c = pathList.findClass(name, suppressedExceptions);
133 if (c == null) {
134 ClassNotFoundException cnfe = new ClassNotFoundException(
135 "Didn't find class \"" + name + "\" on path: " + pathList);
136 for (Throwable t : suppressedExceptions) {
137 cnfe.addSuppressed(t);
138 }
139 throw cnfe;
140 }
141 return c;
142 }
...
}
public abstract class ClassLoader {
201 private final ClassLoader parent;
311 public Class<?> loadClass(String name) throws ClassNotFoundException {
312 return loadClass(name, false);
313 }
//双亲委托机制
359 protected Class<?> loadClass(String name, boolean resolve)
360 throws ClassNotFoundException
361 {
362 //首先、检查这个类是否被加载,如果加载直接返回
363 Class<?> c = findLoadedClass(name);
364 if (c == null) {
365 try {
//ClassLoader !=null,再执行一次loadClass
//也就是说,如果我们自定义一个Test.class,获取到的classLoader就是pathClassLoader,继续获取就是BaseDexClassLoader,继续获取就是ClassLoader,继续获取就是null
366 if (parent != null) {
367 c = parent.loadClass(name, false);
368 } else {
//c==null
369 c = findBootstrapClassOrNull(name);
370 }
371 } catch (ClassNotFoundException e) {
372 // ClassNotFoundException thrown if class not found
373 // from the non-null parent class loader
374 }
375
376 if (c == null) {
377 // If still not found, then invoke findClass in order
378 // to find the class.
379 c = findClass(name);
380 }
381 }
382 return c;
383 }
732 protected final Class<?> findLoadedClass(String name) {
733 ClassLoader loader;
734 if (this == BootClassLoader.getInstance())
735 loader = null;
736 else
737 loader = this;
//vm加载
738 return VMClassLoader.findLoadedClass(loader, name);
739 }
1344 @Override
1345 protected Class<?> findClass(String name) throws ClassNotFoundException {
//这是一个native方法,最后返回这个类
1346 return Class.classForName(name, false, null);
1347 }
}
在这里继续说明一下,因为有了双亲委托机制的存在,使得我们的类加载效率更高,也更加的安全
热修复也是基于这一点的存在来实现
final class DexPathList {
...
//存放dex的集合,dex存放我们的类
64 private Element[] dexElements;
135 DexPathList(ClassLoader definingContext, String dexPath,
136 String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
164 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
165 suppressedExceptions, definingContext, isTrusted);
191 }
319 private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
320 List<IOException> suppressedExceptions, ClassLoader loader) {
321 return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
322 }
484 public Class<?> findClass(String name, List<Throwable> suppressed) {
//loop dexElements
485 for (Element element : dexElements) {
//在element中找class
486 Class<?> clazz = element.findClass(name, definingContext, suppressed);
487 if (clazz != null) {
488 return clazz;
489 }
490 }
491
492 if (dexElementsSuppressedExceptions != null) {
493 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
494 }
495 return null;
496 }
...
}
}
接下来开始我们的热修复
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//定义一个bug
throw new NullPointerException("空异常!!!");
//将程序运行在手机上
}
}
接下来修复程序,修复完查看字节码,这里只是把bug注释了而已
public class MainActivity extends AppCompatActivity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131361820);
}
}
接下来利用dx工具(工具是什么,自己百度)打包我们的补丁包a.dex
Element[] dexElements |
---|
1、Element | 2、Element | 3、 Test.class中有bug |
---|
可以看到第3个中存在bug,修复bug之后
Element[] dexElements |
---|
1、Element Test.class已修复的 | 2、Element | 3、 Element | 4、 Test.class中有bug |
---|
当我们的类加载器去加载Test.class的时候,在第一个Element中找到了Test.class,就不会再去加载第4个Element中的Test.class了,这样就完成了修复的效果
由于考虑到数组的复制问题
在这里标识一个方法
public final class System{
//源数组
//源数组要复制的起始位置
//目标数组
//目的数组放置的起始位置
//要复制的长度
public static native void arraycopy(@RecentlyNonNull Object var0, int var1, @RecentlyNonNull Object var2, int var3, int var4);
}
为了能够看的更加清楚,我将所有代码写在了一个类中,当然,附有详细的注释
public class App extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//获取当前类加载器
ClassLoader classLoader = this.getClassLoader();
//首先获取到pathList属性
Field pathList = null;
//根据双亲委托的机制来获取对应的属性
for (Class<?> aClass = classLoader.getClass(); aClass != null; aClass = aClass.getSuperclass()) {
try {
//BaseDexClassLoader-->pathList
pathList = aClass.getDeclaredField("pathList");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
//一般来说都是私有属性,判断后设为true
if (!pathList.isAccessible()) {
pathList.setAccessible(true);
}
//拿到pathList的val
//o.getClass()=DexPathList.class
Object o = null;
try {
o = pathList.get(classLoader);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//获取DexPathList.class中dexElements属性
Field dexElementsField = null;
for (Class<?> aClass = o.getClass(); aClass != null; aClass = aClass.getSuperclass()) {
try {
//获取dexElements属性
//DexPathList -->dexElements
dexElementsField = aClass.getDeclaredField("dexElements");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
if (!dexElementsField.isAccessible()) {
//设置访问权限
dexElementsField.setAccessible(true);
}
//获取到程序中的dexElements(旧的apk中的dexs)
Object[] dexElements = null;
try {
dexElements = (Object[]) dexElementsField.get(o);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
//====================================分割线==============================================//
//接下来获取补丁包中的文件
//获取缓存目录中的a.dex(名字随意)
String filePath = getCacheDir().getPath() + "/a.dex";
//获取补丁包中的dexElements数组,在分析源码中
//DexPathList.class中的makeDexElements() 是创建了一个dexElements数组
Method makeDexElements = null;
for (Class<?> aClass = o.getClass(); aClass != null; aClass = aClass.getSuperclass()) {
try {
makeDexElements = aClass.getDeclaredMethod("makeDexElements", List.class, File.class, List.class,ClassLoader.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
if (!makeDexElements.isAccessible()) {
makeDexElements.setAccessible(true);
}
List<File> files = new ArrayList<>();
File file = new File(filePath);
if (file.exists()) {
files.add(file);
}
List<IOException> exceptions = new ArrayList<>();
//获取补丁包中的dexElements
Object[] dexElements2 = null;
try {
dexElements2 = (Object[]) makeDexElements.invoke(o, files, null, exceptions,classLoader);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
//====================================分割线==============================================//
//接下来合并dex
//创建一个新的数组
Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(),
dexElements.length + dexElements2.length);
//将补丁包的dexs放在最前面
System.arraycopy(dexElements2, 0, newElements, 0, dexElements2.length);
//接着存放就apk中的dexs
System.arraycopy(dexElements, 0, newElements, dexElements2.length, dexElements.length);
//将合并后的数据赋值给dexElements
try {
dexElementsField.set(o,newElements);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
这也是我个人的一次总结,如有问题请指出
若有不解,请留言
by bdd