Hot fix
At present, the domestic Android hot repair technology has been developed. It can be said that a hundred flowers bloom. From the implementation method, it can be roughly classified into:
- Native layer implementation
- Java layer implementation
Someone has briefly analyzed the Andfix
implementation principle of Alibaba's open source (based on the Native layer) before, so I won't say more about it here. You can search for it.
This article briefly analyzes the Java layer to implement the hot repair logic, and simply implements the code hot repair Demo, taking Tinker as an example (of course it Tinker
is supported 代码修复,资源修复,so修复
, and interested friends move to the official website by themselves~)
First sort out the ideas:
Java class compilation process
It is the process of compiling the java class into a .class file through javac, and then compiling into a .dex file by dx.bat, without going into details, simply draw a picture~
Introduction to ClassLoader
The java.lang.ClassLoader class in Android is also different from the java.lang.ClassLoader in Java. The ClassLoader type in Android can also be divided into system ClassLoader and custom ClassLoader. The system ClassLoader includes 3 types:
BootClassLoader
, Android system will use BootClassLoader to pre-load commonly used classes when starting up. Unlike Bootstrap ClassLoader in Java, it is not implemented by C/C++ code, but by Java. BootClassLoader is an internal class of ClassLoader.PathClassLoader
, The full name is dalvik/system.PathClassLoader, you can load the installed Apk, which is the apk file under /data/app/package, or you can load the nativeLibrary under /vendor/lib, /system/lib.DexClassLoader
, The full name is dalvik/system.DexClassLoader, you can load an uninstalled apk file. PathClassLoader and DexClasLoader are both inherited from dalviksystem.BaseDexClassLoader, and their class loading logic is all written in BaseDexClassLoader. The following figure shows the inheritance system in ClassLoader in Android. Among them, SecureClassLoader and UrlClassLoader are class loaders in Java and cannot be used in Android.
.dex file loading
It is known from the source code that the .dex
file is BaseDexClassLoader类(ClassLoader的子类)
loaded through, there is a member variable in this class DexPathList对象
, and there is an array in this object that stores the DexElement
object, that is, the file loaded from the .dex
file, the entry point is here
For projects, the general project will be subcontracted (when the number of methods is greater than 64k, and when the number of methods is greater than 65535, the subcontracting strategy provided by Google), if Java code is used to implement hot repair, subcontracting must be done, because it is necessary to ensure that the main The package has no bugs, and the sub-package simply means that the packaged apk generally has multiple .dex
files
Such as: classes.dex,classes2.dex etc...
So for example classes2.dex
, if a method of one of our classes is abnormal, we can create a repair package (repaired classes2.dex
file), and then classes2.dex
copy the repaired file to the private directory through a custom class loader , and then jump in the queue to 系统ClassLoader
of dexPathList对象
the dexElement
array, so that the system priority load 修复后的classes2.dex
the file, in order to achieve the purpose of hot fixes, this implementation must perform a repair logic to restart the app in order to achieve the effect ~
After understanding this information, the general idea is there. We need to load and analyze the repaired .dex file, and then jump the old installation and packaging .dex file to do the jump operation, which is equivalent to cheating the Android system, which is roughly as follows:
Implementation principle
Thinking about, we need to fix a bug of .dex file, to jump the queue BaseDexClassLoader类
in DexPathList对象
the DexElement
array, and sorting to front, so that the system is loaded into .dex file after we fix the bug will not have to load dex files, complete Jump in the queue (instrumentation), there will be a knowledge of the class loading mechanism. This article will not introduce it in detail. A separate summary will be written later~ The general implementation steps are as follows:
Demo implementation
1. Basic configuration-main package configuration
Configure subcontracting. The purpose of configuring subcontracting is mainly to package the apk that will have multiple .dex files. In the actual project application, ensure that the main package does not have bugs. When loading the .dex file in the demo, the main package file is also excluded. classes.dex
, as follows: Create BaseApplication
, BaseActivity
, MainActivity
placed in the main bag, which MainActivity
is mainly to sub footprint, only a sub-click to jump in SecondActivity
the logical directory app build.gradle
open sub-support, the android
→ defaultConfig
increase configuration, wherein multiDex-config.txt
is arranged Keep class files in the main package
//开启分包
multiDexEnabled true
//分包的配置,将配置文件中的放置在主包
multiDexKeepFile file("multiDex-config.txt")
Add sub-package dependencies:
//multidex分包依赖
implementation 'com.android.support:multidex:1.0.3'
Application opens subcontracting:
public class BaseApplication extends MultiDexApplication {
@Override
public void onCreate() {
super.onCreate();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//安装分包配置
MultiDex.install(this);
}
}
2. Subcontracting configuration
The subcontracting creates an SecondActivity类
entry to simulate anomalies and repair anomalies, and a Calculate
simulated anomaly, 10/0
the operations done, after the repair is10/1
Note: The repaired
classes2.dex
file can be obtained by directly decompressing directly by buildapk, or bydx.bat
executing the command under build-tools
Simply paste the code after SecondActivity
clicking the fix
button:
private void update() {
//将下载的修复包,复制到私有目录,解压从.dex文件中取到对应的.class文件
//从sd卡取修复包
File sourceFile = new File(Environment.getExternalStorageDirectory(), Constants.DEX_NAME);
//目标文件
File targetFile = new File(getDir(Constants.DEX_DIR, Context.MODE_PRIVATE).getAbsolutePath() + File.separator + Constants.DEX_NAME);
if (targetFile.exists()) {
targetFile.delete();
Log.e("update","删除原有dex文件(已使用的)");
}
//将SD卡中的修复包copy到私有目录
FileUtils.copyFile(sourceFile,targetFile);
Log.e("update","copy完成");
FixDexUtils.loadDexFile(this);
}
3、FixModule
Create a new Module to handle the related logic of hot repair
There are only five files, the core file code is there FixDexUtils
, the others are tool classes, and there FixDexUtils
is a code that defines several constants .
public class FixDexUtils {
//修复文件可能有多个
private static HashSet<File> loadedDex = new HashSet<>();
//不建议这么写,demo演示用
static {
loadedDex.clear();
}
public static void loadDexFile(Context context) {
//获取私有目录
File fileDir = context.getDir(Constants.DEX_DIR, Context.MODE_PRIVATE);
//遍历筛选私有目录中的.dex文件
File[] listFiles = fileDir.listFiles();
for (int i = 0; i < listFiles.length; i++) {
//文件名以.dex结尾,且不是主包.dex文件
if (listFiles[i].getName().endsWith(Constants.DEX_SUFFIX) && !"classes.dex".equalsIgnoreCase(listFiles[i].getName())) {
loadedDex.add(listFiles[i]);
}
}
//创建自定义的类加载器
createDexClassLoader(context ,fileDir);
}
/**
* @param context
* @param fileDir
* 创建自己的类加载器,加载私有目录的.dex文件,上面已经将修复包中的dex文件copy到私有目录
*/
private static void createDexClassLoader(Context context, File fileDir) {
//解压目录
String optimizedDir = fileDir.getAbsolutePath()+File.separator+"opt_dex";
File fileOpt = new File(optimizedDir);
if (!fileOpt.exists()) {
fileOpt.mkdirs();
}
for (File dex : loadedDex) {
//创建自己的类加载器,临时的
DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(), optimizedDir, null, context.getClassLoader());
//有一个修复文件,就插装一次
hotFix(classLoader,context);
}
}
private static void hotFix(DexClassLoader classLoader, Context context) {
try {
//获取系统的PathClassLoader类加载器
PathClassLoader pathClassLoader = (PathClassLoader)context.getClassLoader();
//获取自己的dexElements数组
Object myElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(classLoader));
//获取系统的dexElements数组
Object systemElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(pathClassLoader));
//合并数组,并排序,生成一个新的数组
Object dexElements=ArrayUtils.combineArray(myElements,systemElements);
//通过反射获取系统的pathList属性
Object systemPathList = ReflectUtils.getPathList(pathClassLoader);
//通过反射,将合并后新的dexElements赋值给系统的pathList
ReflectUtils.setFieldValue(systemPathList,"dexElements",dexElements);
}catch (Exception e){
e.printStackTrace();
}
}
}
The main work is: the above flowchart is to first obtain the .dex
file collection that needs to be hot repaired through operations such as traversal, decompression, etc. , traverse the collection, create a temporary one each time DexClassLoader
, and then perform the repair steps. The division is six steps:
The final effect is shown in the figure (the mobile phone used in the Demo is a Huawei 8.0 mobile phone):
Note: In order to make the rendering more intuitive, the app has been restarted once.
Note: The hot repair implemented in this way must restart the App to achieve the repair. This is also determined by the class loading mechanism. After the repair is shown in the following figure, open the loading execution again The repairedclasses.dex
file isBaseApplication
called the repair method in
At last
Here I also share an Android learning PDF+architecture video+interview document+source notes , advanced architecture technology advanced mind map, Android development interview special materials, advanced advanced architecture materials collected and organized by several big guys .
If you have a need, you can point it to receive
If you like this article, you might as well give me a thumbs-up, leave a message in the comment area or forward and support it~