現(xiàn)在主要由兩大方法
1.阿里AndFix畜疾,主要是采用Ndk實現(xiàn)對方法指針的替換
2.騰訊Tinker
現(xiàn)在主要說的是tinker的實現(xiàn)方法:
一. 首先介紹下兩個概念:
public class PathClassLoader extends BaseDexClassLoader {
用來加載已安裝應用程序的dex
public class DexClassLoader extends BaseDexClassLoader {
可以加載指定的某個dex文件聚蝶。(限制:必須要在應用程序的目錄下面抬驴,所以下載下來需要復制到本目錄里)
二. 很明顯我們需要使用DexClassLoader來實現(xiàn)需求
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.originalPath = dexPath;
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
上面創(chuàng)建了一個DexPathList
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
……
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory);
}
private static Element[] makeDexElements(ArrayList<File> files,
File optimizedDirectory) {
ArrayList<Element> elements = new ArrayList<Element>();
for (File file : files) {
ZipFile zip = null;
DexFile dex = null;
String name = file.getName();
if (name.endsWith(DEX_SUFFIX)) {
dex = loadDexFile(file, optimizedDirectory);
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {
zip = new ZipFile(file);
}
……
if ((zip != null) || (dex != null)) {
elements.add(new Element(file, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}
/**
* Converts a dex/jar file path and an output directory to an
* output file path for an associated optimized dex file.
*/
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();
}
}
File result = new File(optimizedDirectory, fileName);
return result.getPath();
}
optimizedDirectory是用來緩存我們需要加載的dex文件的麦轰,并創(chuàng)建一個DexFile對象递鹉,如果它為null,那么會直接使用dex文件原有的路徑來創(chuàng)建DexFile
對象休玩。
加載類的過程:
ClassLoader通過loadClass來加載需要的類
public Class<?> loadClass(String className) throws ClassNotFoundException {
return loadClass(className, false);
}
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
ClassNotFoundException suppressed = null;
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
try {
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
最后還是調用的DexPathList的findClass
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;
}
從上面代碼看出如果element里面找到了class,for循環(huán)就會退出
所以我們只要修改dexElements這個集合就行了劫狠,把我們創(chuàng)建的dexElements跟原始的dexElements合并拴疤。
dexA:原app的dex文件
dexB:補丁dex文件,可以是多個
1.先把下載下來的dex文件用file形式保存到一個集合中
//遍歷所有的修復的dex
File fileDir = context.getDir(MyConstants.DEX_DIR,Context.MODE_PRIVATE);
File[] listFiles = fileDir.listFiles();
for(File file:listFiles){
if(file.getName().startsWith("classes")&&file.getName().endsWith(".dex")){
loadedDex.add(file);//存入集合
}
}
2.使用反射獲取dexA,dexB的classLoader独泞,dexA的classLoader使用PathClassLoader獲取呐矾,dexB的classLoader由dexClassLoader獲取
PathClassLoader pathLoader = (PathClassLoader) appContext.getClassLoader();
for (File dex : loadedDex) {
//2.加載指定的修復的dex文件。
DexClassLoader classLoader = new DexClassLoader(
dex.getAbsolutePath(),//String dexPath,
fopt.getAbsolutePath(),//String optimizedDirectory,
null,//String libraryPath,
pathLoader//ClassLoader parent
);
}
3.通過classloader獲取dexA和dexB的pathList
Object dexObj = getPathList(classLoader);
Object pathObj = getPathList(pathLoader);
4.通過pathList獲取dexA和dexB的dexElements阐肤,并把這兩個合并
Object mDexElementsList = getDexElements(dexObj);
Object pathDexElementsList = getDexElements(pathObj);
//合并完成
Object dexElements = combineArray(mDexElementsList,pathDexElementsList);
5.把合并之后的dexElements賦值給dexA的classLoader的pathList中的dexElements
Object mDexElementsList = getDexElements(dexObj);
Object pathDexElementsList = getDexElements(pathObj);
//合并完成
Object dexElements = combineArray(mDexElementsList,pathDexElementsList);
//重寫給PathList里面的lement[] dexElements;賦值
Object pathList = getPathList(pathLoader);
setField(pathList,pathList.getClass(),"dexElements",dexElements);
那么具體步驟就已經完成了凫佛。
6.怎么才能打出一個dex包?
通過dx.bat這個系統(tǒng)提供的工具就可以
1.找到MyTestClass.class
com\app\build\intermediates\bin\MyTestClass.class
2.配置dx.bat的環(huán)境變量
Android\sdk\build-tools\23.0.3\dx.bat
3.命令
dx --dex --output=D:\Users\jiang\Desktop\dex\classes2.dex D:\Users\jiang\Desktop\dex
命令解釋:
--output=D:\Users\jiang\Desktop\dex\classes2.dex 指定輸出路徑
D:\Users\jiang\Desktop\dex 最后指定去打包哪個目錄下面的class字節(jié)文件(注意要包括全路徑的文件夾孕惜,也可以有多個class)
代碼地址如下: