使用Tinker时一般会用@DefaultLifeCycle注解,由注解处理器生成一个App类如
import com.tencent.tinker.loader.app.TinkerApplication;
/**
*
* Generated application for tinker life cycle
*
*/
public class TestApp extends TinkerApplication {
public TestApp() {
super(7, "com.xxx.xx.xxx.ApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
}
}
该App继承TinkerApplication,TinkerApplication又继承Application,目的是把原本在application里执行的逻辑,放到代理中,使得这些逻辑可以通过补丁的方式动态修改(如果直接用Application是无法修改的,因为已经加载了),因为在TinkerApplication的onBaseContextAttached中先执行打补丁操作:
private void onBaseContextAttached(Context base) {
....
loadTinker(); //加载补丁
ensureDelegate(); //创建Application代理
applicationLike.onBaseContextAttached(base);
//reset save mode
....
}
private void loadTinker() {
try {
//reflect tinker loader, because loaderClass may be define by user!
//loaderClassName如果没有自定义的话,默认是com.tencent.tinker.loader.TinkerLoader
Class> tinkerLoadClass = Class.forName(loaderClassName, false, TinkerApplication.class.getClassLoader());
//获取tryLoad()方法
Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
Constructor> constructor = tinkerLoadClass.getConstructor();
//调用tryLoad()
tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
} catch (Throwable e) {
//has exception, put exception error code
tinkerResultIntent = new Intent();
ShareIntentUtil.setIntentReturnCode(tinkerResultIntent, ShareConstants.ERROR_LOAD_PATCH_UNKNOWN_EXCEPTION);
tinkerResultIntent.putExtra(INTENT_PATCH_EXCEPTION, e);
}
}
当Application回调onBaseContextAttached之后,会先执行loadTinker()方法,loadTinker通过反射的方式获取到实际执行加载dex的类,并且通过tryLoad方法去执行,加载类默认是TinkerLoader。
public Intent tryLoad(TinkerApplication app) {
.....
//加载补丁文件
this.tryLoadPatchFilesInternal(app, resultIntent);
.....
}
private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
.....
//一系列校验
//now we can load patch jar
if (isEnabledForDex) {
//加载dex
loadTinkerResources = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA);
}
.....
}
tryLoadPatchFilesInternal方法一路看下来会发现有大量的校验,最后校验成功会调TinkerDexLoader.loadTinkerJars(),从方法名看,终于加载dex了。
/**
* Load tinker JARs and add them to
* the Application ClassLoader.
*
* @param application The application.
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA) {
if (loadDexList.isEmpty() && classNDexInfo.isEmpty()) {
Log.w(TAG, "there is no dex to load");
return true;
}
......
String dexPath = directory + "/" + DEX_PATH + "/";
ArrayList legalFiles = new ArrayList<>();
for (ShareDexDiffPatchInfo info : loadDexList) {
//for dalvik, ignore art support dex
if (isJustArtSupportDex(info)) {
continue;
}
String path = dexPath + info.realName;
File file = new File(path);
......
legalFiles.add(file);
}
......
loadTinkerJars首先回判断loadDexList和classNDexInfo是否为空,这两个什么时候赋值呢,继续分析发现在TinkerDexLoader.checkComplete()中进行了赋值,而tryLoadPatchFilesInternal进行的一些列校验中,校验完毕会执行一个回调即TinkerDexLoader.checkComplete()。
private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {
if (isEnabledForDex) {
//tinker/patch.info/patch-641e634c/dex
boolean dexCheck = TinkerDexLoader.checkComplete(patchVersionDirectory, securityCheck, oatDex, resultIntent);
if (!dexCheck) {
//file not found, do not load patch
Log.w(TAG, "tryLoadPatchFiles:dex check fail");
return;
}
}
在分析checkComplete()方法之前,先看一下补丁长什么样:
public static boolean checkComplete(String directory, ShareSecurityCheck securityCheck, String oatDir, Intent intentResult) {
//DEX_MEAT_FILE = assets/dex_meta.txt
String meta = securityCheck.getMetaContentMap().get(DEX_MEAT_FILE);
//not found dex
if (meta == null) {
return true;
}
loadDexList.clear();
classNDexInfo.clear();
ArrayList allDexInfo = new ArrayList<>();
//解析assets/dex_meta.txt 获取所有dex文件
ShareDexDiffPatchInfo.parseDexDiffPatchInfo(meta, allDexInfo);
if (allDexInfo.isEmpty()) {
return true;
}
HashMap dexes = new HashMap<>();
ShareDexDiffPatchInfo testInfo = null;
//校验并添加dex进loadDexList
for (ShareDexDiffPatchInfo info : allDexInfo) {
.....
loadDexList.add(info);
}
}
}
public static void parseDexDiffPatchInfo(String meta, ArrayList dexList) {
if (meta == null || meta.length() == 0) {
return;
}
String[] lines = meta.split("\n");
for (final String line : lines) {
if (line == null || line.length() <= 0) {
continue;
}
//分割“,”拿到信息,assets/dex_meta.txt里每个dex都有8个值
final String[] kv = line.split(",", 8);
if (kv == null || kv.length < 8) {
continue;
}
// key
final String name = kv[0].trim();
final String path = kv[1].trim();
final String destMd5InDvm = kv[2].trim();
final String destMd5InArt = kv[3].trim();
final String dexDiffMd5 = kv[4].trim();
final String oldDexCrc = kv[5].trim();
final String newDexCrc = kv[6].trim();
final String dexMode = kv[7].trim();
ShareDexDiffPatchInfo dexInfo = new ShareDexDiffPatchInfo(name, path, destMd5InDvm, destMd5InArt,
dexDiffMd5, oldDexCrc, newDexCrc, dexMode);
dexList.add(dexInfo);
}
}
checkComplete()做的事情就是解析补丁文件的dex_meta.txt 文本,拿到补丁里所有dex的相关信息封装成ShareDexDiffPatchInfo,解析方法是ShareDexDiffPatchInfo.parseDexDiffPatchInfo(),里面通过分割","拿到dex的信息,如名字、路径、MD5等。这里已经知道了loadDexList的值从哪里来,继续分析tryLoadPatchFilesInternal()
public static boolean loadTinkerJars(final TinkerApplication application, String directory, String oatDir, Intent intentResult, boolean isSystemOTA) {
.....
//校验dex文件MD5
for (ShareDexDiffPatchInfo info : loadDexList) {
//for dalvik, ignore art support dex
if (isJustArtSupportDex(info)) {
continue;
}
String path = dexPath + info.realName;
File file = new File(path);
......
legalFiles.add(file);
}
//接下来是systemOTA的处理,看不懂,直接跳过
......
try {
//执行dex 合并
SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
} catch (Throwable e) {
Log.e(TAG, "install dexes failed");
// e.printStackTrace();
intentResult.putExtra(ShareIntentUtil.INTENT_PATCH_EXCEPTION, e);
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION);
return false;
}
loadTinkerJars()主要是对loadDexList进行MD5校验,校验完毕后开始执行dex合并SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles);
public static void installDexes(Application application, PathClassLoader loader, File dexOptDir, List files)
throws Throwable {
Log.i(TAG, "installDexes dexOptDir: " + dexOptDir.getAbsolutePath() + ", dex size:" + files.size());
if (!files.isEmpty()) {
//对dex文件进行排序
files = createSortedAdditionalPathEntries(files);
ClassLoader classLoader = loader;
//兼容24以上的版本,避免出现dex file register with multiple classloader异常
if (Build.VERSION.SDK_INT >= 24 && !checkIsProtectedApp(files)) {
classLoader = AndroidNClassLoader.inject(loader, application);
}
//because in dalvik, if inner class is not the same classloader with it wrapper class.
//it won't fail at dex2opt
if (Build.VERSION.SDK_INT >= 23) {
V23.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 19) {
V19.install(classLoader, files, dexOptDir);
} else if (Build.VERSION.SDK_INT >= 14) {
V14.install(classLoader, files, dexOptDir);
} else {
V4.install(classLoader, files, dexOptDir);
}
//install done
sPatchDexCount = files.size();
Log.i(TAG, "after loaded classloader: " + classLoader + ", dex size:" + sPatchDexCount);
if (!checkDexInstall(classLoader)) {
//reset patch dex
SystemClassLoaderAdder.uninstallPatchDex(classLoader);
throw new TinkerRuntimeException(ShareConstants.CHECK_DEX_INSTALL_FAIL);
}
}
}
这里是真正实行合并的地方,兼容不同的android版本,分析V23.install(classLoader, files, dexOptDir)
private static final class V23 {
private static void install(ClassLoader loader, List additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IOException {
/* The patched class loader is expected to be a descendant of
* dalvik.system.BaseDexClassLoader. We modify its
* dalvik.system.DexPathList pathList field to append additional DEX
* file entries.
*/
//反射拿到pathList
Field pathListField = ShareReflectUtil.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ArrayList suppressedExceptions = new ArrayList();
//反射执行makePathElements把补丁的dex文件转成Elements
//把补丁的Elements添加进dexPathList的dexElements里(dexElement是一个Element数组)
ShareReflectUtil.expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList,
new ArrayList(additionalClassPathEntries), optimizedDirectory,
suppressedExceptions));
if (suppressedExceptions.size() > 0) {
for (IOException e : suppressedExceptions) {
Log.w(TAG, "Exception in makePathElement", e);
throw e;
}
}
}
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makePathElements}.
*/
private static Object[] makePathElements(
Object dexPathList, ArrayList files, File optimizedDirectory,
ArrayList suppressedExceptions)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makePathElements;
try {
makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
List.class);
} catch (NoSuchMethodException e) {
Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
try {
makePathElements = ShareReflectUtil.findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
} catch (NoSuchMethodException e1) {
Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
try {
Log.e(TAG, "NoSuchMethodException: try use v19 instead");
return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions);
} catch (NoSuchMethodException e2) {
Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
throw e2;
}
}
}
return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
}
}
//把补丁的Elements添加进dexPathList的dexElements里
public static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
Field jlrField = findField(instance, fieldName);
//反射拿到dexPathList里dexElements的实例
Object[] original = (Object[]) jlrField.get(instance);
//创建一个长度为原始dexElements长度和补丁dexElements长度的新的dexElements
Object[] combined = (Object[]) Array.newInstance(original.getClass().getComponentType(), original.length + extraElements.length);
//最重要的一步
//先把补丁的dexElements拷贝进来
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
//再把原始的dexElements拷贝进来
System.arraycopy(original, 0, combined, extraElements.length, original.length);
//反射重新给dexElements赋值
jlrField.set(instance, combined);
}
看到这里就会发现,这部分代码和MultiDex里的代码很相似,都是用反射对pathList里的Elements数组dexElements进行操作,不同之处在于Tinker把补丁的Elements放到最前面,原始apk的Elements放到后面,这样在类加载时,就会优先找到补丁的类,达到补丁的效果。放上MultiDex的部分源码对比一下
public final class MultiDex {
private static void expandFieldArray(Object instance, String fieldName,
Object[] extraElements) throws NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field jlrField = findField(instance, fieldName);
Object[] original = (Object[]) jlrField.get(instance);
Object[] combined = (Object[]) Array.newInstance(
original.getClass().getComponentType(), original.length + extraElements.length);
//不同点:
//先把原始的dexElements拷贝进来
System.arraycopy(original, 0, combined, 0, original.length);
//再把其他的dexElements拷贝进来
System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
jlrField.set(instance, combined);
}
}
最后,这只是把dex加载的核心部分抽取出来,忽略了很多东西,比如校验、补丁dex合并成完整的dex等(因为打出来的补丁是跟原始APK的差分包)。