一、Tinker核心架构总览
1.1 架构设计目标与核心价值
Tinker作为Android热修复框架,核心目标是在不重启App的情况下实现代码、资源和So库的动态更新。其架构设计围绕高效差分合成、稳定加载机制和系统兼容性展开,核心价值体现在:
- 最小化补丁包体积:通过bsdiff/bspatch算法生成差分补丁
- 多维度修复支持:涵盖Dex、资源文件、So库的修复
- 低侵入性:对原有App工程的代码改动极小
- 高兼容性:适配Android多版本、多厂商系统差异
1.2 整体架构分层模型
Tinker架构可分为基础层、核心处理层和应用接口层:
- 基础层:提供底层支撑,包括文件系统操作、进程管理、ClassLoader适配等。
- 核心处理层:实现补丁包的核心处理逻辑,如差分合成、加载重定向、资源修复等。
- 应用接口层:暴露对外API,供开发者进行初始化配置、补丁加载等操作。
二、基础层:底层能力支撑
2.1 文件系统与存储管理
Tinker通过com.tencent.tinker.lib.util.TinkerIO
类实现文件操作:
public class TinkerIO {
// 从输入流读取数据到文件
public static boolean saveToFile(InputStream inputStream, File destFile) {
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(destFile);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
return true;
} catch (IOException e) {
// 捕获并记录异常
TinkerLog.e("TinkerIO", "saveToFile fail: %s", e.getMessage());
return false;
} finally {
// 关闭输入输出流
closeQuietly(inputStream);
closeQuietly(outputStream);
}
}
// 静默关闭流,避免抛出异常
public static void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
TinkerLog.e("TinkerIO", "closeQuietly fail: %s", e.getMessage());
}
}
}
}
补丁文件的存储路径管理由com.tencent.tinker.lib.tinker.TinkerInstaller
负责:
public class TinkerInstaller {
private static final String TINKER_DIR = "tinker";
private static final String PATCH_DIR = "patch";
// 获取Tinker根目录
public static File getTinkerDir(Context context) {
File cacheDir = context.getCacheDir();
return new File(cacheDir, TINKER_DIR);
}
// 获取补丁文件目录
public static File getPatchDir(Context context) {
File tinkerDir = getTinkerDir(context);
return new File(tinkerDir, PATCH_DIR);
}
}
2.2 进程管理与生命周期处理
Tinker通过com.tencent.tinker.lib.tinker.Tinker
类管理补丁加载流程与进程状态:
public class Tinker {
private static final String TAG = "Tinker";
private static Tinker tinker;
// 单例模式获取Tinker实例
public static Tinker with(Context context) {
if (tinker == null) {
tinker = new Tinker(context);
}
return tinker;
}
private Tinker(Context context) {
// 初始化Tinker配置
TinkerLoadResult loadResult = TinkerLoadResult.load(context);
if (loadResult.isSuccess()) {
// 加载成功后的处理逻辑
TinkerLog.i(TAG, "Tinker load success");
} else {
// 加载失败处理
TinkerLog.e(TAG, "Tinker load fail: %s", loadResult.getError());
}
}
}
在进程重启场景下,com.tencent.tinker.lib.tinker.TinkerLoadResult
负责记录加载状态:
public class TinkerLoadResult {
private boolean success;
private String error;
// 从配置文件加载加载结果
public static TinkerLoadResult load(Context context) {
try {
// 读取配置文件获取加载状态
File configFile = new File(TinkerInstaller.getTinkerDir(context), "tinker_config.txt");
if (!configFile.exists()) {
return new TinkerLoadResult(false, "config file not exist");
}
BufferedReader reader = new BufferedReader(new FileReader(configFile));
String line = reader.readLine();
if ("success".equals(line)) {
return new TinkerLoadResult(true, null);
} else {
return new TinkerLoadResult(false, line);
}
} catch (IOException e) {
return new TinkerLoadResult(false, e.getMessage());
}
}
private TinkerLoadResult(boolean success, String error) {
this.success = success;
this.error = error;
}
public boolean isSuccess() {
return success;
}
public String getError() {
return error;
}
}
2.3 ClassLoader适配机制
Tinker通过自定义TinkerClassLoader
实现类加载重定向:
public class TinkerClassLoader extends DexClassLoader {
private final File optimizedDirectory;
private final String libraryPath;
private final ClassLoader parent;
// 构造函数初始化参数
public TinkerClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, libraryPath, parent);
this.optimizedDirectory = optimizedDirectory;
this.libraryPath = libraryPath;
this.parent = parent;
}
// 重写findClass方法实现类加载逻辑
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 尝试从补丁Dex中加载类
return super.findClass(name);
} catch (ClassNotFoundException e) {
// 若失败,委托给父ClassLoader
return parent.loadClass(name);
}
}
}
在应用启动时,com.tencent.tinker.lib.tinker.Tinker
会替换默认的ClassLoader
:
public class Tinker {
private void replaceClassLoader(Context context) {
File patchDir = TinkerInstaller.getPatchDir(context);
File optimizedDir = new File(patchDir, "odex");
if (!optimizedDir.exists()) {
optimizedDir.mkdirs();
}
String dexPath = new File(patchDir, "patch.dex").getAbsolutePath();
ClassLoader parentClassLoader = context.getClassLoader();
// 创建TinkerClassLoader实例
ClassLoader tinkerClassLoader = new TinkerClassLoader(
dexPath, optimizedDir, null, parentClassLoader);
// 设置新的ClassLoader
setClassLoader(context, tinkerClassLoader);
}
// 设置应用的ClassLoader
private void setClassLoader(Context context, ClassLoader classLoader) {
try {
Field loaderField = Context.class.getDeclaredField("mClassLoader");
loaderField.setAccessible(true);
loaderField.set(context, classLoader);
} catch (NoSuchFieldException | IllegalAccessException e) {
TinkerLog.e("Tinker", "setClassLoader fail: %s", e.getMessage());
}
}
}
三、核心处理层:补丁处理逻辑
3.1 差分合成算法实现
Tinker采用bsdiff/bspatch算法生成和应用差分补丁:
- bsdiff:用于生成补丁文件,对比新旧Dex文件生成差异数据。
- bspatch:用于合成补丁,将差异数据应用到旧Dex文件生成新Dex。
补丁合成核心逻辑在com.tencent.tinker.lib.tinker.TinkerPatch
类中:
public class TinkerPatch {
private static final String TAG = "TinkerPatch";
// 应用补丁文件
public static boolean applyPatch(Context context, File oldDexFile, File patchFile, File outputDexFile) {
try {
// 执行bspatch命令进行合成
Process process = new ProcessBuilder("sh", "-c",
String.format("bspatch %s %s %s", oldDexFile.getAbsolutePath(),
outputDexFile.getAbsolutePath(), patchFile.getAbsolutePath()))
.redirectErrorStream(true)
.start();
int exitCode = process.waitFor();
if (exitCode == 0) {
TinkerLog.i(TAG, "Patch apply success");
return true;
} else {
TinkerLog.e(TAG, "Patch apply fail, exit code: %d", exitCode);
return false;
}
} catch (IOException | InterruptedException e) {
TinkerLog.e(TAG, "Patch apply fail: %s", e.getMessage());
return false;
}
}
}
3.2 Dex文件加载与重定向
Tinker通过DexFileFactory
类处理Dex文件的加载:
public class DexFileFactory {
private static final String TAG = "DexFileFactory";
// 加载Dex文件
public static DexFile loadDexFile(Context context, File dexFile) {
try {
// 创建DexFile实例
return DexFile.loadDex(dexFile.getAbsolutePath(),
new File(context.getCacheDir(), "tinker_odex").getAbsolutePath(), 0);
} catch (IOException e) {
TinkerLog.e(TAG, "loadDexFile fail: %s", e.getMessage());
return null;
}
}
}
在类加载过程中,TinkerClassLoader
会优先从补丁Dex中查找类:
public class TinkerClassLoader extends DexClassLoader {
private final DexFile patchDexFile;
public TinkerClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, libraryPath, parent);
this.patchDexFile = DexFileFactory.loadDexFile(null, new File(dexPath));
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 从补丁Dex中查找类
Class<?> clazz = patchDexFile.loadClass(name, this);
if (clazz != null) {
return clazz;
}
} catch (IOException | ClassNotFoundException e) {
// 忽略异常,继续委托给父ClassLoader
}
return super.findClass(name);
}
}
3.3 资源修复机制
Tinker通过ResourcePatch
类处理资源补丁:
public class ResourcePatch {
private static final String TAG = "ResourcePatch";
// 应用资源补丁
public static boolean applyResourcePatch(Context context, File patchFile) {
try {
// 解析资源补丁文件
// (实际逻辑涉及AssetManager操作)
AssetManager assetManager = context.getAssets();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, patchFile.getAbsolutePath());
// 更新资源加载上下文
Context newContext = context.createConfigurationContext(context.getResources().getConfiguration());
newContext.setAssets(assetManager);
// 替换应用的资源上下文
setActivityThreadResources(newContext);
TinkerLog.i(TAG, "Resource patch apply success");
return true;
} catch (Exception e) {
TinkerLog.e(TAG, "Resource patch apply fail: %s", e.getMessage());
return false;
}
}
// 替换ActivityThread中的Resources实例
private static void setActivityThreadResources(Context newContext) {
try {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object activityThread = sCurrentActivityThreadField.get(null);
Field mResourcesField = activityThreadClass.getDeclaredField("mResources");
mResourcesField.setAccessible(true);
Resources newResources = newContext.getResources();
mResourcesField.set(activityThread, newResources);
} catch (Exception e) {
TinkerLog.e(TAG, "setActivityThreadResources fail: %s", e.getMessage());
}
}
}
四、应用接口层:对外API设计
4.1 Tinker初始化配置
开发者通过TinkerApplication
类进行初始化:
public class TinkerApplication extends MultiDexApplication {
private static final String TAG = "TinkerApplication";
@Override
public void onCreate() {
super.onCreate();
// 初始化Tinker配置
TinkerInstaller.install(this);
Tinker tinker = Tinker.with(this);
if (tinker.isTinkerLoaded()) {
TinkerLog.i(TAG, "Tinker is already loaded");
} else {
// 加载补丁逻辑
tinker.loadPatch();
}
}
}
TinkerInstaller
类负责具体的初始化操作:
public class TinkerInstaller {
public static void install(Context context) {
// 检查Tinker是否已初始化
if (isTinkerInstalled(context)) {
return;
}
// 初始化文件目录
File tinkerDir = getTinkerDir(context);
if (!tinkerDir.exists()) {
tinkerDir.mkdirs();
}
// 注册Tinker回调
TinkerManager.setTinkerApplicationLike(new TinkerApplicationLike() {
// 实现回调方法
@Override
public void onCreate() {
// 初始化完成后的处理
}
@Override
public void onTerminate() {
// 应用终止时的处理
}
});
}
private static boolean isTinkerInstalled(Context context) {
File tinkerDir = getTinkerDir(context);
return tinkerDir.exists();
}
}
4.2 补丁加载与管理API
Tinker
类提供补丁加载接口:
public class Tinker {
// 加载补丁文件
public void loadPatch() {
File patchDir = TinkerInstaller.getPatchDir(null);
File patchFile = new File(patchDir, "patch.dex");
if (!patchFile.exists()) {
TinkerLog.e("Tinker", "Patch file not exist");
return;
}
// 执行补丁应用逻辑
if (TinkerPatch.applyPatch(null, null, patchFile, null)) {
TinkerLog.i("Tinker", "Patch load success");
// 重启应用使补丁生效
restartApplication();
} else {
TinkerLog.e("Tinker", "Patch load fail");
}
}
// 重启应用
private void restartApplication() {
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
PendingIntent restartIntent = PendingIntent.getActivity(
null, 0, intent, PendingIntent.FLAG_ONE_SHOT);
AlarmManager mgr = (AlarmManager) getSystemService(Context.ALARM_MANAGER);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, restartIntent);
android.os.Process.killProcess(android.os.Process.myPid());
}
}