一、Tinker核心架构总览

1.1 架构设计目标与核心价值

Tinker作为Android热修复框架,核心目标是在不重启App的情况下实现代码、资源和So库的动态更新。其架构设计围绕高效差分合成稳定加载机制系统兼容性展开,核心价值体现在:

  • 最小化补丁包体积:通过bsdiff/bspatch算法生成差分补丁
  • 多维度修复支持:涵盖Dex、资源文件、So库的修复
  • 低侵入性:对原有App工程的代码改动极小
  • 高兼容性:适配Android多版本、多厂商系统差异

1.2 整体架构分层模型

Tinker架构可分为基础层核心处理层应用接口层

  1. 基础层:提供底层支撑,包括文件系统操作、进程管理、ClassLoader适配等。
  2. 核心处理层:实现补丁包的核心处理逻辑,如差分合成、加载重定向、资源修复等。
  3. 应用接口层:暴露对外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方法实现类加载逻辑@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {// 尝试从补丁Dex中加载类return super.findClass(name);} catch (ClassNotFoundException e) {// 若失败,委托给父ClassLoaderreturn 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);// 设置新的ClassLoadersetClassLoader(context, tinkerClassLoader);}// 设置应用的ClassLoaderprivate 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));}@Overrideprotected 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";@Overridepublic 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() {// 实现回调方法@Overridepublic void onCreate() {// 初始化完成后的处理}@Overridepublic 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());}
}