转载来源:http://blog.csdn.net/l2show/article/details/53307523
之前有说到Tinker的修复原理是跟Qzone类似,这里就详细分析一下为什么这样做可以修复补丁.虽然其他Android版本的源码实现可能不一样,但是都是基于相同的原理.所以这里就以Android 6.0的源码为例介绍原理.具体每个系统版本的不同实现下面会详细说明.
首先从加载dex文件的入口开始看, /libcore/dalvik/src/main/Java/dalvik/system/DexClassLoader.java
这个类很简单,只是继承了BaseDexClassLoader
在构造方法中调用了父类的构造方法.
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
继续进入/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
类中,BaseDexClassLoader
在构造方法中创建出了一个很重要的对象pathList
,至于为什么说他重要可以看下面的findclass
方法.findclass
方法是根据类名在运行时从dex文件中找出并将其返回回来,而真正的findclass
是通过pathList
对象的方法来操作的.
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
最终在/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
类中找到findclass
方法,该方法是按顺序遍历dexElements,只要dexElement中的dex文件中包含有该class就加载出class然后直接return.所以利用findclass
这种特性把补丁包dex插入dexElements的首位,系统在findClass的时候就优先拿到补丁包中的class,从而达到修复bug的目的.
final class DexPathList {
private final Element[] dexElements;
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
校验Dex文件
讲过dex修复的原理,回到Tinker的dex补丁加载流程.在loadTinkerJars
之后,先确保之前checkComplete
时是否筛选出物理有效的dex文件以供加载,再拿到PathClassLoader供后面使用.
if (dexList.isEmpty()) {
Log.w(TAG, "there is no dex to load");
return true;
}
PathClassLoader classLoader = (PathClassLoader) TinkerDexLoader.class.getClassLoader();
if (classLoader != null) {
Log.i(TAG, "classloader: " + classLoader.toString());
} else {
Log.e(TAG, "classloader is null");
ShareIntentUtil.setIntentReturnCode(intentResult, ShareConstants.ERROR_LOAD_PATCH_VERSION_DEX_CLASSLOADER_NULL);
return false;
}
在TinkerLoader.tryLoad
时只是校验了dex_meta.txt
文件的签名信息,并没有校验所有的dex文件的合法性.如果在ApplicationLike处配置了tinkerLoadVerifyFlag为true, 则每次加载dex补丁之前都对文件做MD5,并对比dex_meta.txt
中对应的MD5信息.
for (DexDiffPatchInfo info : dexList) {
//for dalvik, ignore art support dex
if (isJustArtSupportDex(info)) {
continue
}
String path = dexPath + info.realName
File file = new File(path)
if (tinkerLoadVerifyFlag) {
long start = System.currentTimeMillis()
String checkMd5 = isArtPlatForm ? info.destMd5InArt : info.destMd5InDvm
if (!PatchFileUtil.verifyDexFileMd5(file, checkMd5)) {
//it is good to delete the mismatch file
IntentUtil.setIntentReturnCode(intentResult, Constants.ERROR_LOAD_PATCH_VERSION_DEX_MD5_MISMATCH)
intentResult.putExtra(IntentUtil.INTENT_PATCH_MISMATCH_DEX_PATH,
file.getAbsolutePath())
return false
}
Log.i(TAG, "verify dex file:" + file.getPath() + " md5, use time: " + (System.currentTimeMillis() - start))
}
legalFiles.add(file)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
OTA在特定情况下重新load Dex
在v1.7.5的版本开始有了isSystemOTA判断,只要用户是ART环境并且做了OTA升级则在加载dex补丁的时候就会先把最近一次的补丁全部DexFile.loadDex一遍.这么做的原因是有些场景做了OTA后,oat的规则可能发生变化,在这种情况下去加载上个系统版本oat过的dex就会出现问题.
if (isSystemOTA) {
parallelOTAResult = true;
parallelOTAThrowable = null;
Log.w(TAG, "systemOTA, try parallel oat dexes!!!!!");
ParallelDexOptimizer.optimizeAll(
legalFiles, optimizeDir,
new ParallelDexOptimizer.ResultCallback() {
@Override
public void onSuccess(File dexFile, File optimizedDir) {
}
@Override
public void onFailed(File dexFile, File optimizedDir, Throwable thr) {
parallelOTAResult = false;
parallelOTAThrowable = thr;
}
}
);
if (!parallelOTAResult) {
Log.e(TAG, "parallel oat dexes failed");
intentResult.putExtra(IntentUtil.INTENT_PATCH_EXCEPTION, parallelOTAThrowable);
IntentUtil.setIntentReturnCode(intentResult, Constants.ERROR_LOAD_PATCH_VERSION_PARALLEL_DEX_OPT_EXCEPTION);
return false;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
dex补丁的重置是在线程池中执行,并且利用CountDownLatch
挂起主线程,直到线程池中的task都执行完毕再恢复主线程.在很极端的情况下可能会造成ANR.
private static boolean optimizeAllLocked(Collection<File> dexFiles, File optimizedDir, AtomicInteger successCount, ResultCallback cb) {
final CountDownLatch lauch = new CountDownLatch(dexFiles.size());
final ExecutorService threadPool = Executors.newCachedThreadPool();
long startTick = System.nanoTime();
for (File dexFile : dexFiles) {
OptimizeWorker worker = new OptimizeWorker(dexFile, optimizedDir, successCount, lauch, cb);
threadPool.submit(worker);
}
try {
lauch.await();
long timeCost = (System.nanoTime() - startTick) / 1000000;
if (successCount.get() == dexFiles.size()) {
Log.i(TAG, "All dexes are optimized successfully, cost: " + timeCost + " ms.");
return true;
} else {
Log.e(TAG, "Dexes optimizing failed, some dexes are not optimized.");
return false;
}
} catch (InterruptedException e) {
Log.w(TAG, "Dex optimizing was interrupted.", e);
return false;
} finally {
threadPool.shutdown();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
加载Dex
经过一系列的校验,终于到真正加载dex补丁的步骤了.Tinker加载dex补丁按照系统版本不同分成了四条分支.同样加载失败之后记录失败信息到intentResult中.
- V4 Android SDK版本小于14
- V14 Android SDK版本小于19
- V19 Android SDK版本小于 23
- V23 Android SDK版本大于等于23
try {
SystemClassLoaderAdder.installDexes(application, classLoader, optimizeDir, legalFiles)
} catch (Throwable e) {
Log.e(TAG, "install dexes failed")
intentResult.putExtra(IntentUtil.INTENT_PATCH_EXCEPTION, e)
IntentUtil.setIntentReturnCode(intentResult, Constants.ERROR_LOAD_PATCH_VERSION_DEX_LOAD_EXCEPTION)
return false
}
Log.i(TAG, "after loaded classloader: " + application.getClassLoader().toString())
-
V4 Android SDK版本小于14
在Android SDK4到14之间PathClassLoader.java
的实现是直接继承自ClassLoader
,findClass
时是根据mFiles数组来遍历mDexs数组(类似于dexElements).从mDexs数组中的dex根据类名来加载Class,规则也是按照遍历的顺序加载,只要有加载出来的Class就直接return掉.
Android 2.3.6版本源码
public class PathClassLoader extends ClassLoader {
private final String path;
private final String[] mPaths;
private final File[] mFiles;
private final ZipFile[] mZips;
private final DexFile[] mDexs;
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
byte[] data = null;
int length = mPaths.length;
for (int i = 0; i < length; i++) {
if (mDexs[i] != null) {
Class clazz = mDexs[i].loadClassBinaryName(name, this);
if (clazz != null)
return clazz;
} else if (mZips[i] != null) {
String fileName = name.replace('.', '/') + ".class";
data = loadFromArchive(mZips[i], fileName);
} else {
File pathFile = mFiles[i];
if (pathFile.isDirectory()) {
String fileName =
mPaths[i] + "/" + name.replace('.', '/') + ".class";
data = loadFromDirectory(fileName);
} else {
}
}
}
throw new ClassNotFoundException(name + " in loader " + this);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
在DexClassLoader.java
的构造方法中可以看到path
,mPaths
,mFiles
, mZips
和mDexs
五个关键属性之间是互相联系的,所以在做热修复时要同时对这五个属性同步操作,来确保数据的一致性.
this.path = path;
this.libPath = libPath;
mPaths = path.split(":");
int length = mPaths.length;
mFiles = new File[length];
mZips = new ZipFile[length];
mDexs = new DexFile[length];
...
for (int i = 0; i < length; i++) {
File pathFile = new File(mPaths[i]);
mFiles[i] = pathFile;
if (pathFile.isFile()) {
try {
mZips[i] = new ZipFile(pathFile);
}
catch (IOException ioex) {
}
if (wantDex) {
try {
mDexs[i] = new DexFile(pathFile);
}
catch (IOException ioex) {}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
所以在Tinker中,要加载这类系统的补丁包最核心的地方就是path
,mPaths
,mFiles
, mZips
和mDexs
五个属性的的操作.根据补丁文件的个数建立四个关键属性对应的数组,再通过遍历补丁文件,对四个数组和一个字符串进行填充.再利用反射将新的数组插入到原数组头部,完成补丁加载的过程.
private static void install(ClassLoader loader, List<File> additionalClassPathEntries, File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, IOException {
int extraSize = additionalClassPathEntries.size();
Field pathField = ReflectUtil.findField(loader, "path");
StringBuilder path = new StringBuilder((String) pathField.get(loader));
String[] extraPaths = new String[extraSize];
File[] extraFiles = new File[extraSize];
ZipFile[] extraZips = new ZipFile[extraSize];
DexFile[] extraDexs = new DexFile[extraSize];
for (ListIterator<File> iterator = additionalClassPathEntries.listIterator();
iterator.hasNext();) {
File additionalEntry = iterator.next();
String entryPath = additionalEntry.getAbsolutePath();
path.append(':').append(entryPath);
int index = iterator.previousIndex();
extraPaths[index] = entryPath;
extraFiles[index] = additionalEntry;
extraZips[index] = new ZipFile(additionalEntry);
String outputPathName = PatchFileUtil.optimizedPathFor(additionalEntry, optimizedDirectory);
extraDexs[index] = DexFile.loadDex(entryPath, outputPathName, 0);
}
pathField.set(loader, path.toString());
ReflectUtil.expandFieldArray(loader, "mPaths", extraPaths);
ReflectUtil.expandFieldArray(loader, "mFiles", extraFiles);
ReflectUtil.expandFieldArray(loader, "mZips", extraZips);
try {
ReflectUtil.expandFieldArray(loader, "mDexs", extraDexs);
} catch (Exception e) {
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
对原数组的操作是利用反射,先拿到原数组的对象original, 再根据original的类型长度以及补丁数组的长度重新创建出一个新数组combined.接下来使用arraycopy
将补丁数组和原数组copy到combined中,最后将该数组赋值给filedName对应的属性.
public 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);
System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
System.arraycopy(original, 0, combined, extraElements.length, original.length);
jlrField.set(instance, combined);
}
-
V14 Android SDK版本小于19
在这个Android版本的区间内不再像老版本的那样要维护四个数组,源码从中抽离出了一个类DexPathList.java
,加载dex的关键数组也变成了dexElements
,并且dexElements
是根据makeDexElements
方法生成的.对比过源码其实就可以发现dexElements
其实就是老版本中mFiles
, mZips
和mDexs
的封装,makeDexElements
方法就是老版本DexClassLoader.java
构造方法中对数组初始化的动作.
Android 4.2.2版本源码
final class DexPathList {
/** list of dex/resource (class path) elements */
private final Element[] dexElements;
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE("Unable to load dex file: " + file, ex);
}
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ignored) {
}
} else {
System.logW("Unknown file type for: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
系统既然自己做了封装,那么我们反射调用起来也会更方便.首先反射拿到反射得到PathClassLoader中的pathList对象,再将补丁文件通过反射调用makeDexElements
得到补丁文件的Element[]
,再将补丁包的Element数组插入到dexElements
中,方法如V4.完成补丁加载.
private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
File optimizedDirectory)
throws IllegalArgumentException, IllegalAccessException,
NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
Field pathListField = ReflectUtil.findField(loader, "pathList");
Object dexPathList = pathListField.get(loader);
ReflectUtil.expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList,
new ArrayList<File>(additionalClassPathEntries), optimizedDirectory));
}
/**
* A wrapper around
* {@code private static final dalvik.system.DexPathList#makeDexElements}.
*/
private static Object[] makeDexElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method makeDexElements =
ReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class);
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
-
V19 Android SDK版本小于 23
在该版本系统区间中,加载补丁涉及到的修改只是增加了一个exElementsSuppressedExceptions
异常数组的维护.所以在加载补丁的时候就跟V14差不多了.既然只是多了一个异常的管理,为什么Tinker源码在利用反射找makeDexElements(ArrayList,File,ArrayList)
,如果找不到就接着找makeDexElements(List,File,List)
?为了在Android源码中找到答案我去查找了4.4, 5.0,5.1版本的DexPathList
源码,发现方法的参数都是ArrayList,根本没有List.百思不得姐之后就问了一下Tinker的作者,他们说在线上发现有机子的rom中这个方法的参数就是List.
private static Object[] makeDexElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makeDexElements = null;
try {
makeDexElements = ReflectUtil.findMethod(dexPathList, "makeDexElements", ArrayList.class, File.class,
ArrayList.class);
} catch (NoSuchMethodException e) {
Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
try {
makeDexElements = ReflectUtil.findMethod(dexPathList, "makeDexElements", List.class, File.class, List.class);
} catch (NoSuchMethodException e1) {
Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
throw e1;
}
}
return (Object[]) makeDexElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
-
V23 Android SDK版本大于等于23
因为V23中包含有Android 7.0的系统版本,由于Android N混合编译与对热补丁影响解析,这会造成要修复的class被缓存在App image中,App image中的class会插入PathClassLoader中,而PathClassLoader 加载补丁的时候不会替换缓存的class,最终会导致在全量更新的情况下有可能部分类是从base.apk中加载,部分类是从patch.dex中加载,抛出IllegalAccessError.Tinker的解决方案是在运行时改写PathClassLoader来加载类,让App image中的缓存失效.
所以要解决N里面混编的问题,核心着手点就是要替换PathClassLoader
使他在加载dex的时候不加载做过优化的dex文件,重新加载原始的dex文件.这个点要从哪里切入呢? 在Android 7.0的源码中定位到了在makePathElements
方法中调用的loadDexFile
方法.从代码上来看是要在调用的时候有传递有效的optimizedDirectory
参数,就会去opt过的路径下加载dex文件.所以我们在调用的时候不传optimizedDirectory
参数就可以达到重新加载原始dex文件从而去除混编优化的目的.
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);
}
}
知道了解决方案和切入点,接下来分析一下Tinker做法.在加载补丁之前利用反射替换原PathClassLoader以及与它相关的所有引用.首先根据原PathClassLoader的parent 构建出AndroidNClassLoader;再反射拿到original的pathList;接着反射拿到pathList对象的definingContext属性,因为该属性是original的引用,需要拿到之后替换成新loader的引用;继续反射拿到androidNClassLoader的pathList对象,并且替换成original的;再反射拿到original的pathList的dexElements,并且遍历出dexElements中真实的dex文件名之后存储起来;接下来反射拿到original的pathList的makePathElements方法并调用注意方法第二个参数optDir要设置为null,重新生成dexElements数组,并替换原来的数组.最终完成AndroidNClassLoader
的创建,以及子类引用的替换.
private static AndroidNClassLoader createAndroidNClassLoader(PathClassLoader original) throws Exception {
AndroidNClassLoader androidNClassLoader = new AndroidNClassLoader("", original);
Field originPathList = ShareReflectUtil.findField(original, "pathList");
Object originPathListObject = originPathList.get(original);
Field originClassloader = ShareReflectUtil.findField(originPathListObject, "definingContext");
originClassloader.set(originPathListObject, androidNClassLoader);
Field pathListField = ShareReflectUtil.findField(androidNClassLoader, "pathList");
pathListField.set(androidNClassLoader, originPathListObject);
List<File> additionalClassPathEntries = new ArrayList<>();
Field dexElement = ShareReflectUtil.findField(originPathListObject, "dexElements");
Object[] originDexElements = (Object[]) dexElement.get(originPathListObject);
for (Object element : originDexElements) {
DexFile dexFile = (DexFile) ShareReflectUtil.findField(element, "dexFile").get(element);
additionalClassPathEntries.add(new File(dexFile.getName()));
oldDexFiles.add(dexFile);
}
Method makePathElements = ShareReflectUtil.findMethod(originPathListObject, "makePathElements", List.class, File.class,
List.class);
ArrayList<IOException> suppressedExceptions = new ArrayList<>();
Object[] newDexElements = (Object[]) makePathElements.invoke(originPathListObject, additionalClassPathEntries, null, suppressedExceptions);
dexElement.set(originPathListObject, newDexElements);
return androidNClassLoader;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
做完新AndroidNClassLoader
的创建之后就是替换真正的ClassLoader的引用了.在全局Context中持有的LoadedApk
的对象mPackageInfo
的属性中,有一个ClassLoader类的对象mClassLoader.层层反射将mClassLoader的引用替换为上面创建出来的AndroidNClassLoader
对象.同时将Thread中持有的ClassLoader也同步替换为AndroidNClassLoader
.至此PathClassLoader
的修改和替换都已经完成了,接下来就可以正常得加载补丁dex了.
String defBase = "mBase"
String defPackageInfo = "mPackageInfo"
String defClassLoader = "mClassLoader"
Context baseContext = (Context) ReflectUtil.findField(application, defBase).get(application)
Object basePackageInfo = ReflectUtil.findField(baseContext, defPackageInfo).get(baseContext)
Field classLoaderField = ReflectUtil.findField(basePackageInfo, defClassLoader)
Thread.currentThread().setContextClassLoader(reflectClassLoader)
classLoaderField.set(basePackageInfo, reflectClassLoader)
在Android系统在该版本区间之内时,DexPathList
类中的findclass
方法跟V19相比是没有变化的.但是生成dexElements
数组用的方法名发生了变化.所以在这个版本中反射生成补丁包的Element[]
就需要兼容这些变化.
Android 6.0.0版本源码, 相比老版本makeDexElements(ArrayList,File,ArrayList)
方法变成了makePathElements(List,File,List)
.
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
···
}
Android 7.0.0版本源码,该方法名又发生了变化.根据职能做了一些区分和重载.
/**
* Makes an array of dex/resource path elements, one per element of
* the given array.
*/
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions,
ClassLoader loader) {
return makeElements(files, optimizedDirectory, suppressedExceptions, false, loader);
}
/**
* Makes an array of directory/zip path elements, one per element of the given array.
*/
private static Element[] makePathElements(List<File> files,
List<IOException> suppressedExceptions,
ClassLoader loader) {
return makeElements(files, null, suppressedExceptions, true, loader);
}
private static Element[] makePathElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions) {
return makeElements(files, optimizedDirectory, suppressedExceptions, false, null);
}
private static Element[] makeElements(List<File> files, File optimizedDirectory,
List<IOException> suppressedExceptions,
boolean ignoreDexFiles,
ClassLoader loader) {
···
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
结合上面两个系统版本的分析,在反射findMethod时做多种情况的处理,同时也避免V19中描述的rom问题.
private static Object[] makePathElements(
Object dexPathList, ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions)
throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Method makePathElements;
try {
makePathElements = ReflectUtil.findMethod(dexPathList, "makePathElements", List.class, File.class,
List.class);
} catch (NoSuchMethodException e) {
Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
try {
makePathElements = ReflectUtil.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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
卸载 Dex补丁
校验和加载Dex补丁的流程都已经分析完了.接下来就是验证补丁是否加载成功,这里是通过反射拿到TinkerTestDexLoad.isPatch
静态属性来判断.在没有补丁加载的情况下都是返回false的,在补丁中修改isPatch属性为true.所以只要反射拿到isPatch的属性为true就说明补丁已经成功加载进来了.如果返回false则卸载Dex补丁恢复到加载之前的数据状态.
private static boolean checkDexInstall(ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> clazz = Class.forName(CHECK_DEX_CLASS, true, classLoader);
Field filed = ShareReflectUtil.findField(clazz, CHECK_DEX_FIELD);
boolean isPatch = (boolean) filed.get(null);
Log.w(TAG, "checkDexInstall result:" + isPatch);
return isPatch;
}
补丁的卸载就不需要跟加载一样要区分那么多版本,因为Android 4.0开始就都是在操作DexPathList
类中的dexElements
数组,Android 4.0之前是在V4补丁加载中提到过的那四个数组.卸载就根据补丁的数量把数组头部的数据都移除掉.其中reduceFieldArray的作用跟加载补丁V4中提到的expandFieldArray完全相反.
public static void uninstallPatchDex(ClassLoader classLoader) throws Throwable {
if (sPatchDexCount <= 0) {
return;
}
if (Build.VERSION.SDK_INT >= 14) {
Field pathListField = ShareReflectUtil.findField(classLoader, "pathList");
Object dexPathList = pathListField.get(classLoader);
ShareReflectUtil.reduceFieldArray(dexPathList, "dexElements", sPatchDexCount);
} else {
ShareReflectUtil.reduceFieldArray(classLoader, "mPaths", sPatchDexCount);
ShareReflectUtil.reduceFieldArray(classLoader, "mFiles", sPatchDexCount);
ShareReflectUtil.reduceFieldArray(classLoader, "mZips", sPatchDexCount);
try {
ShareReflectUtil.reduceFieldArray(classLoader, "mDexs", sPatchDexCount);
} catch (Exception e) {
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
到这里Dex补丁的加载流程就完全分析完了,下面会继续分析SO和资源的操作流程.