热修复/热更新技术原理

1 热修复技术出现的背景

要说到热修复的出现,主要还是因为在中国Android应用没有一个统一的应用商店,每个手机厂商基本都会定制自己的一款手机应用市场,不像国外的统一都使用Google Play。

中国用户下载app都在不同的应用市场下载,没有统一的应用市场导致的问题就是,如果应用出现bug需要紧急修复,按正常的发布流程,需要从bug的修改、发布应用到各个应用市场、提示用户下载安装修复这几个流程。

在流程上看,有一些弊端:

  • 需要重新发布版本代价太大(可能只是修改小bug)

  • 用户下载安装覆盖成本太高

  • bug修复不及时导致给用户的体验差

出现了以上问题,所以在中国热修复技术就产生了。

2 热修复技术简介

什么是热修复?热修复简单来说就是一种补丁方案,我们只需要通过将补丁文件打包为 .dex 推送给用户,结合类加载机制在应用重启的时候就可以修复好出现的bug。

优势上是显而易见的:

  • 无需重新发布版本,实时高效修复

  • 用户无感知,无需下载安装覆盖应用,代价小

  • 修复成功率比较高

目前在市面上热修复技术有阿里巴巴的 AndFixDexposed,腾讯QQ空间的超级补丁和微信的 Tinker 等。

这些热修复技术框架的使用我不会在这里讲,而是要了解热修复技术的底层实现原理。

3 插件化

在知道热修复之前,你或许有听说过插件化。那什么是插件化?

3.1 什么是插件化

如果你有关注一些比较大型的app,比如支付宝、美团等,可以发现app的一些入口是一个完全不同的功能,这些功能可能是主项目发布后在后期动态加入进来的,而这种将其他apk集成到另一个apk的技术就是插件化。插件化的核心就是动态部署。

要注意的是,下面说的插件化方式也不是官方提供也不提倡的,和热修复一样在中国比较合适,要和Android提供的 Android App Bundles 区分。

3.2 插件化例子

现在我们实现一个功能:在我们的apk中通过插件化集成另一个apk,应用启动的时候让集成进来的apk生效(因为是demo,所以我们直接就在项目中创建另一个apk)。

  • 创建一个插件名为 plugin(注意这里我们是选择 Phone & Tablet Module 模拟一个插件apk),apk里面写一个类:

在这里插入图片描述

package com.example.plugin;

public class PluginClass {

    public String plugin() {
        return "I'm a plugin!";
    }
}

  • 生成apk,将这个apk放到主项目的 assets 目录下(当然,实际的项目可能会从网络下载或其他方式获取插件apk):

在这里插入图片描述

  • 将apk加载进来:

那么你可能会有疑问了:外部apk我们怎么将它作为插件加入到我们主项目中来呢?我们知道android的apk打包后都有一个或多个dex文件,而dex文件里面是我们java文件的 .class 字节码文件:

在这里插入图片描述

既然dex文件是我们一些 .class 字节码文件,那么同样的也需要类加载器来加载,就是 DexClassLoader,我们可以通过它来获取加载我们的apk中的那个 PluginClass 类。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        File pluginApk = new File(getCacheDir() + "/plugin.apk");
        if (!pluginApk.exists()) {
            try(Source source = Okio.source(getAssets().open("plugin-debug.apk"));
                BufferedSink sink = Okio.buffer(Okio.sink(pluginApk))) {
                sink.writeAll(source);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        DexClassLoader dexClassLoader = new DexClassLoader(pluginApk.getPath(), getCacheDir().getPath(), null, null);
        String pluginPrint = "";
        try {
            Class<?> clazz = dexClassLoader.loadClass("com.example.plugin.PluginClass");
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            Object pluginObj = constructor.newInstance();
            Method pluginMethod = clazz.getDeclaredMethod("plugin");
            pluginPrint = (String) pluginMethod.invoke(pluginObj);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        ((TextView) findViewById(R.id.tv_plugin_test)).setText(pluginPrint);
    }
}

上面的代码非常简单,就是读取我们 assets 目录的apk文件到本地,然后使用 DexClassLoader 将apk加载后使用反射调用。

3.3 新增界面、资源的插件

既然上面是通过反射才能够拿到数据展示,那么如果我的插件apk可能有界面Activity的怎么办?直接使用 Intent 跳转?肯定是行不通的,运行时会提示清单文件没有注册Activity。那要怎么做?可以提供代理Activity让它转发处理到插件apk的界面:

// 伪代码
public class ProxyActivity extends AppCompatActivity {
	Object pluginActivity = DexClassLoader.loadClass("xxx");

	@Override
	protected void onCreate(@Nullable Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		pluginActivity.onCreate(savedInstanceState);
	}

	@Override
	protected void onStart() {
		super.onStart();
		pluginActivity.onStart();
	}
	...
}

不过上面的方式也会导致要写很多的代理类。

还有另外一种方式就是通过欺骗系统,在系统检查完清单文件发现Activity注册后,在启动清单文件注册的Activity之前替换为我们要启动的Activity。不过这种方式说不准往后会被官方屏蔽也说不定,只使用于当前。

而新增在插件apk的资源又要怎么获取?需要重写 getResources() 扩展:

@Override
public Resources getResources() {
	return new Resources(createAssetManager("插件apk本地目录"), 
		super.getResources().getDisplayMetrics(), 
		super.getResources().getConfiguration());
}

private AssetManager createAssetManager(String dexPath) {
	try {
		// addAssetPath()是AssetManager的方法
		AssetManager am = AssetManager.class.newInstance();
		Method addAssetPath = am.getClass().getMethod("addAssetPath", String.class);
		addAssetPath.invoke(am, dexPath);
		return sm;
	} catch (Exception e) {
		e.printStackTrace();
	}
	return null;
}

4 热修复技术

热修复技术主要是处理我们上线发布的版本需要紧急对bug进行修复,可能只是一些小改动,如果使用上面说的插件化的方式就不行了,插件化会替换掉所有的内容,这显然不符合我们预期,我们只想要替换掉修复bug更改的一个或几个java文件。

4.1 类加载机制

因为热修复技术需要了解类加载机制和反射相关原理和使用,所以首先还是需要有一个基本了解。在之前我写了一篇文章可以作为参考:

类加载机制和反射

4.2 PathClassLoader和DexClassLoader

在Android中主要涉及有几个类加载器:

  • PathClassLoader

  • DexClassLoader

  • BaseDexClassLoader

PathClassLoaderDexClassLoader 都是 BaseDexClassLoader 的子类:

public class PathClassLoader extends BaseDexClassLoader {
	public PathClassLoader(String dexPath, ClassLoader parent) {
		super(dexPath, null, null, parent);
	}

	public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
		super(dexPath, null, librarySearchPath, parent);
	}
}

public class DexClassLoader extends BaseDexClassLoader {
	public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
		super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
	}
}

根据上面的源码可以发现,PathClassLoaderDexClassLoader 的区别在于,DexClassLoader 多了一个 optimizedDirectory 参数,optimizedDirectory 可以是apk外部的文件目录路径。

这产生的具体区别就是,DexClassLoader 可用于加载指定路径下的 .dex 文件,能支持动态插件化加载、热修复;PathClassLoader 就只能用于加载已经安装到系统中的apk文件中的 .dex 文件,不能从外部加载,也就不支持动态插件化加载、热修复。

4.3 热修复技术的原理

4.3.1 findClass()

上面分析了 PathClassLoaderDexClassLoader 的区别,发现它们都是 BaseDexClassLoader 的子类,具体的热修复原理分析也是在这里说明。

热修复干预类加载机制是在 ClassLoader.findClass() 处理:

BaseDexClassLoader.java

public class BaseDexClassLoader {
	private DexPathList pathList;

	@Override
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		...
		Class c = pathList.findClass(name, suppressedExceptions);
		...
	}
}

DexPathList.java

public class DexPathList {
	private Element[] dexElements;

	public Class<?> findClass(String name, List<Throwable> suppressed) {
		for (Element element : dexElements) {
			Class<?> clazz = element.findClass(name, definingContext, suppressed);
			if (clazz != null) {
				return clazz;
			}
			...
			return null;
		}
	}	
}

Element.java

public class Element {
	public Class<?> findClass(String name, ClassLoader definingContext, List<Throwable> suppressed) {
		return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null;
	}
}


DexFile.java

public final  class DexFile {
	public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
		return defineClass(name, loader, mCookie, this, suppressed);
	}

	private static class defineClass(String name, ClassLoader loader, Object cookie, DexFile dexFile, List<Throwable> suppressed) {
		Class result = null;
		try {
			// native method
			result = defineClassNative(name, loader, cookie, dexFile);
		} catch (NoClassDefFoundError e) {
			...
		} catch (ClassNotFoundException e) {
			...
		}
		return result;
	}
}

上面的流程先简单梳理一下:

BaseDexClassLoader.findClass() -> DexPathList.findClass() -> Element.findClass() -> DexFile.loadClassBinaryName() -> DexFile.defineClass()

但是有几个点我们没有搞明白:DexPathList 是什么?dexElements 是什么?dexFile 又是什么?接下来一步步分析。

4.3.2 DexPathList、dexElement、dexFile

首先看下 DexPathList 是什么时候被初始化的:

public class BaseDexClassLoader {
	private DexPathList pathList;

	public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
		this(dexPath, librarySearchPath, parent, null, false);	
	}

	public BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, boolean isTrusted) {
		super(parent);
		...
		// DexPathList在ClassLoader创建的时候被初始化
		this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
		...
	}
}

可以发现,DexPathList 是在 BaseDexClassLoader 创建的时候初始化,进去 DexPathList

public class DexPathList {
	private Element[] dexElements;

	DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
		...
		this.definingContext = definingContext;
		// dexElements在DexPathList初始化的时候完成加载dex文件
		this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedException, definingContext, isTrusted);
	}

	private static List<File> splitDexPath(String path) {
		return splitPaths(path, false);
	}

	private static List<File> splitPaths(String searchPath, boolean directoryOnly) {
		List<File> result = new ArrayList<>();
		
		if (searchPath != null) {
			// File.pathSeparator在不同的系统是不一样的
			// Windows系统:File.pathSeparator=";"
			// Unix、Linux系统:File.pathSeparator=":"
			// 这是系统环境变量Path分割拼接的路径
			
			// 这里的操作就是将我们的apk的dex文件目录做拆分
			// new DexClassLoader(path, ...)
			// 也就是path可以是单个文件,也可以是一个目录,根据不同的操作系统对目录路径拆分
			for (String path : searchPath.split(File.pathSeparator)) {
				if (directoryOnly) {
					try {
						StructStat sb = Libcore.os.stat(path);
						if (!S_ISDIR(sb.st_mode)) {
							continue;
						}
					} catch (ErrnoException ignored) {
						continue;
					}
				}
				result.add(new File(path));
			}
		}
		return result;
	}

	// 加载dex文件存储到dexElements数组中
	private static Element[] makeDexElements(List<File> fiels, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
		Element[] elements = new Element[files.size()];
		int elementsPos = 0;
		
		for (File file : files) {
			if (file.isDirectory()) {
				elements[elementsPos++] = new Element(file);
			} else if (file.isFile()) {
				String name = file.getName();

				DexFile dex = null;
				// DEX_SUFFIX = ".dex"
				// 如果是dex文件就加载,并且装进Element存到dexElements
				if (name.endsWith(DEX_SUFFIX)) {
					try {
						dex = loadDexFile(file, optimizedDirectory, loader, elements);
						if (dex != null) {
							elements[elementsPos++] = new Element(dex, null);
						}
					} catch (IOException suppressed) {
						...
					}
				} else {
					try {
						// 不是dex文件同样也处理加载
						dex = loadDexFile(file, optimizedDirectory, loader, elements);
					} catch (IOException suppressed) {
						...
					}

					// 无论是否为dex文件都装载进Element存到dexElements
					if (dex == null) {
						elements[elementsPos++] = new Element(file);
					} else {
						elements[elementsPos++] = new Element(dex, file);
					}
				}
			}
		}
		...
	}

	private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException {
		if (optimizedDirectory == null) {
			return new DexFile(file, loader, elements);
		} else {
			String optimizedPath = optimizedPathFor(file, optimizedDirectory);
			return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
		}
	}

	// 这个方法就是处理,如果不是dex文件,就帮你的文件加上后缀.dex
	private static String optimizedPathFor(File path, File optimizedDirectory) {
		String fileName = path.getName();
		if (!fileName.endsWith(DEX_SUFFIX)) {
			int lastDot = fileName.lastIndexOf(".");
			if (lastDot < 0) {
				fileName += DEX_SUFFIX;
			} else {
				StringBuilder sb = new StringBuilder(lastDot + 4);
				sb.append(fileName, 0, lastDot);
				sb.append(DEX_SUFFIX);
				fileName = sb.toString();
			}
		}
		return fileName;
	}
}

通过上面的源码分析,dexElements 其实就是我们存放的 .dex 文件的数组,只不过将 .dex 封装进Element。它同样在ClassLoader创建出来的时候,将我们的文件通过 dexFile 加载出来。

4.4 分析干预类加载

经过上面的分析我们知道,既然主要的 .dex 加载完成的文件是存放在 dexElements 数组中,那么它就是一个切入点。

有两种方式可以干预:

  • 全量替换 dexElements 数组

  • 将我们修改的类文件先转成dex文件,然后自己封装放进Element,再将这个Element插入到 dexElements 数组前面

我们用一个例子来干预类加载达到热修复:提供两个按钮,点击 hotfix 按钮替换 dexElements ,点击 show text 按钮显示热修复后的内容。

4.4.1 全量替换dexElement数组

// 未热修复前的类
public class HotfixClass {

    public String hotfix() {
        return "I'm a original!";
    }
}

// 热修复后的类
public class HotfixClass {

    public String hotfix() {
        return "I'm a hotfix!";
    }
}

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView tvText = findViewById(R.id.tv_text);
        Button btnShowText = findViewById(R.id.btn_show_text);
        Button btnHotFix = findViewById(R.id.btn_hotfix);

        btnShowText.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                HotfixClass hotfixClass = new HotfixClass();
                tvText.setText(hotfixClass.hotfix());
            }
        });

        btnHotFix.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                File hotfixFile = new File(getCacheDir() + "/hotfix.apk");
                try(Source source = Okio.source(getAssets().open("app-debug.apk"));
                    BufferedSink sink = Okio.buffer(Okio.sink(hotfixFile))) {
                    sink.writeAll(source);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                try {
                    ClassLoader classLoader = getClassLoader();
                    Class<BaseDexClassLoader> loaderClass = BaseDexClassLoader.class;

                    // 获取ClassLoader的DexPathList pathList成员变量
                    Field pathListFiled = loaderClass.getDeclaredField("pathList");
                    pathListFiled.setAccessible(true);
                    Object pathList = pathListFiled.get(classLoader);

                    // 获取DexPathList的Element[] dexElements成员变量
                    Class<?> pathListClass = pathList.getClass();
                    Field dexElementsField = pathListClass.getDeclaredField("dexElements");
                    dexElementsField.setAccessible(true);

                    // 创建我们的类加载器,加载出我们热修复需要的dexElements数组,替换旧的ClassLoader的dexElements
                    DexClassLoader hotfixClassLoader = new DexClassLoader(hotfixFile.getPath(), getCacheDir().getPath(), null, null);
                    Object newPathList = pathListFiled.get(hotfixClassLoader);
                    Object newDexElements = dexElementsField.get(newPathList);

                    dexElementsField.set(pathList, newDexElements);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

上面代码操作步骤如下:

  • 通过反射获取 BaseDexClassLoader 里面的 pathList 成员变量

  • 再通过 pathList 反射获取 dexElements 成员变量

  • 自己创建一个ClassLoader加载我们自己的补丁文件生成 dexElements

  • 替换旧的 dexElements

4.4.2 存在的问题

看起来没问题也运行正常,但实际上有一些弊端:

  • 如果把程序kill掉,重新启动apk后直接点击 show text 热更新失效了(因为类加载机制这时候加载的是你没修改的那个类)

  • 我为了修复这个bug,替换了整个apk而不是只替换要修复的 HotfixClass

  • 热更新要生效,需要重启apk(要让热更新生效只能如此)

4.4.2.1 解决程序kill掉后热修复失效问题

第二个问题是因为没有及时的将我们热修复的补丁文件加到 dexElements 中,可以将它提前到 Application.attachBaseContext() 执行:

public class HotfixApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        File hotfixFile = new File(getCacheDir() + "/hotfix.apk");
        if (!hotfixFile.exists()) {
            return;
        }
        
        try {
            ClassLoader classLoader = getClassLoader();
            Class<BaseDexClassLoader> loaderClass = BaseDexClassLoader.class;

            // 获取ClassLoader的DexPathList pathList成员变量
            Field pathListFiled = loaderClass.getDeclaredField("pathList");
            pathListFiled.setAccessible(true);
            Object pathList = pathListFiled.get(classLoader);

            // 获取DexPathList的Element[] dexElements成员变量
            Class<?> pathListClass = pathList.getClass();
            Field dexElementsField = pathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);

            // 创建我们的类加载器,加载出我们热修复需要的dexElements数组,替换旧的ClassLoader的dexElements
            DexClassLoader hotfixClassLoader = new DexClassLoader(hotfixFile.getPath(), getCacheDir().getPath(), null, null);
            Object newPathList = pathListFiled.get(hotfixClassLoader);
            Object newDexElements = dexElementsField.get(newPathList);

            dexElementsField.set(pathList, newDexElements);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
4.4.2.2 解决替换整个apk而不是替换修复的类文件问题

第一个问题的解决方案就是我们只将我们要修改的类编译为 .dex 文件,然后插入到 dexElements 前面。

.class 文件编译为 .dex 需要使用到 d8 工具,这个工具存放在我们的sdk build-tools/xxx版本/d8

  • HotfixClass.java 使用命令 javac HotfixClass.java 编译为 HotfixClass.class

  • HotfixClass.class 转成 .dex 文件,执行命令:

// Windows
d8.bat HotfixClass.class
// Unix、Linux
./d8 HotfixClass.class

输出:classes.dex
  • 使用 classes.dex 作为热修复的补丁文件

因为修改的是一个 .dex 文件,如果还是使用上面的方案去全量替换 dexElements 将会导致应用崩溃,因为你是把整个apk替换称这个只有一个补丁 .dex 文件。

所以也就需要第二种热修复方案。

4.4.3 修改文件转成dex文件封装为Element插入到dexElement数组前面

public class HotfixApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        File hotfixFile = new File(getCacheDir() + "/hotfix.dex");
        if (!hotfixFile.exists()) {
            return;
        }

        try {
            ClassLoader classLoader = getClassLoader();
            Class<BaseDexClassLoader> loaderClass = BaseDexClassLoader.class;

            // 获取ClassLoader的DexPathList pathList成员变量
            Field pathListFiled = loaderClass.getDeclaredField("pathList");
            pathListFiled.setAccessible(true);
            Object pathList = pathListFiled.get(classLoader);

            // 获取DexPathList的Element[] dexElements成员变量
            Class<?> pathListClass = pathList.getClass();
            Field dexElementsField = pathListClass.getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);
            Object dexElements = dexElementsField.get(pathList);

            // 创建我们的类加载器,加载出我们热修复需要的dexElements数组
            DexClassLoader hotfixClassLoader = new DexClassLoader(hotfixFile.getPath(), getCacheDir().getPath(), null, null);
            Object newPathList = pathListFiled.get(hotfixClassLoader);
            Object newDexElements = dexElementsField.get(newPathList);
            
            int oldLength = Array.getLength(dexElements);
            int newLength = Array.getLength(newDexElements);
            Object concatDexElements = Array.newInstance(dexElements.getClass().getComponentType(), oldLength + newLength);
            for (int i = 0; i < newLength; i++) {
                Array.set(concatDexElements, i, Array.get(newDexElements, i));
            }
            for (int i = 0; i < oldLength; i++) {
                Array.set(concatDexElements, newLength + i, Array.get(dexElements, i));
            }
            dexElementsField.set(pathList, concatDexElements);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

通过将一个或多个需要修复的类文件打包成 .dex 文件的方式,在实际的项目开发当中要使用到热修复,我们就可以将我们要修改的补丁文件打成 .dex 文件,通过网络的方式推给apk,让apk在下次启动的时候生效。

需要注意的是,我们自己热修复的 .dex 文件封装的Element要插入 dexElements 数组前面而不是后面。

首先第一个原因就是 dexElements 是顺序遍历循环的:

public class DexPathList {
	private Element[] dexElements;

	public Class<?> findClass(String name, List<Throwable> suppressed) {
		// 顺序循环遍历
		for (Element element : dexElements) {
			Class<?> clazz = element.findClass(name, definingContext, suppressed);
			if (clazz != null) {
				return clazz;
			}
			...
			return null;
		}
	}	
}

第二个原因是因为类加载机制在首次加载到这个类后,下一次获取会去查找缓存那个之前已加载过的类:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
        // 第二次及往后查找缓存获取c != null,直接就会返回
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // 第一次加载会遍历到dexElements
                c = findClass(name);
            }
        }
        return c;
}

5 总结

5.1 热修复原理的简单说明

简单来说,热修复的原理就是:

  • ClassLoader的dex文件替换

  • 直接修改字节码

5.2 初始化流程和从dex文件查找类流程

初始化流程:

创建 BaseDexClassLoader -> 创建 DexPathList -> DexPathList.splitDexPath() 获取dex文件 -> DexPathList.makeDexElements() 加载dex文件封装为Element存储到 dexElements 数组

从dex文件查找类流程:

BaseDexClassLoader.findClass() -> DexPathList.findClass() -> 遍历 dexElements 调用 Element.findClass() -> DexFile.loadClassBinaryName() -> DexFile.defineClass()

5.3 插件化和热修复的区别

区别有两点:

  • 插件化的内容在原App中没有,而热修复是在原App中的内容做改动

  • 插件化在代码中有固定的入口,而热修复则可能改变任何一个位置的代码

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值