是很久之前的技術了,之前也寫過一篇類似的,有了一些新的理解看法,就寫的詳細一點。
學習這個之前,需要了解一下class文件到底是個啥。許多人可能寫了這麼多年java,但是根本就不知道.class文件是怎麼組成的。大部分時候實現功能也用不上知道。所以很多時候你都只能浮於表面。
.Class文件
因爲JVM是加載.class文件,使得JVM的可移植性很強,因爲它只管加載.class文件就行了,無需管你是什麼語言編譯出來的.class。
.class文件是由無符號數與表組成:
無符號數:屬於基本的數據類型,以u1、u2、u4、u8來分別代表1個字節、2個字節、4個字節和8個字節的無符號數。
表:表是由多個無符號數或者其他表作爲數據項構成的複合數據類型,class文件中所有的表都以“_info”結尾。
那表是什麼東西呢,首先要先知道.class文件的組成結構
魔數是一個4個字節的固定值,JVM用來校驗你這個文件是不是.class文件。這裏只着重說一下常量池。
常量池有18種TAG用來表示表的類型
我只拿出幾個比較用的多的類型舉例。尤其說下String類型這張表。它是由1個字節的TAG以及2個字節的地址下標組成。
會指向一個UTF8_info類型的表。UTF8_info由1個字節的TAG,2個字節長度的length以及一個bytes數組組成。bytes就是存放的char值,長度就是length。
常量池後面的類型也是由各種表組成,感興趣的朋友可以自行查閱相關文檔。
我們的android虛擬機是一個特殊的JVM,JVM是用來加載.class文件,基於棧結構,Dalvik是加載.dex文件,是寄存器結構。
.dex文件對.class文件進行了優化,去除一些冗餘的字段,而且字節碼上也有進行優化。
類加載器
BootClassLoader:Android系統啓動時會使用BootClassLoader來預加載常用類。
PathClassLoader:8.0之後和DexClassLoader沒啥區別。用來加載Dex與Apk文件。
重要的是類加載的機制。
雙親委派機制
1、防止重複加載同一個.class。通過委託去向上面問一問,加載過了,就不用再加載一遍。保證數據安全。
2、保證核心.class不能被篡改。通過委託方式,不會去篡改核心.clas,即使篡改也不會去加載,即使加載也不會是同一個.class對象了。不同的加載器加載同一個.class也不是同一個Class對象。這樣保證了Class執行安全。
最簡單的熱修復方式就是藉助這個機制。讓被修復的類在後面加載,修復的類先被加載。5.0之後就不會有CLASS_ISPREVERIFIED標誌了。所以只要你最小版本號高於5.0就無需去進行插樁,需要學習插樁看我另外一篇博客。
實現
public class HotFixUtil {
private static HotFixUtil hotFixUtil = new HotFixUtil();
private DexClassLoader dexClassLoader;
public static HotFixUtil getInstance() {
return hotFixUtil;
}
/**
* 加載修復的dex
* @param context
* @param name
*/
public void loadDex(Context context, String name) {
File fileDir = context.getDir("dex", Context.MODE_PRIVATE);
String dexPath = fileDir.getAbsoluteFile() + File.separator + name;
File dexFile = new File(dexPath);
if (!dexFile.exists()) {
try {
dexFile.createNewFile();
copyFiles(context, name, dexFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//獲取dexElements
public void initDex(Context context) {
File fileDir=context.getDir("dex", Context.MODE_PRIVATE);
List<File> listDex = getList(fileDir);
try {
//獲取pathList
Field pathListField = getField(context.getClassLoader(), "pathList");
Object pathList = pathListField.get(context.getClassLoader());
//獲取老的dexElements
Field dexElementsField = getField(pathList, "dexElements");
Object[] oldDexElements = (Object[]) dexElementsField.get(pathList);
//獲取 makeDexElements 方法
Method makeDexElements = getMethod(pathList, "makeDexElements", List.class, File.class,
List.class, ClassLoader.class);
//裝載fix.dex
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
Object[] myDexElements = (Object[]) makeDexElements.invoke(pathList, listDex, fileDir, suppressedExceptions, context.getClassLoader());
//合併
Object[] newDexElements = (Object[]) Array.newInstance(oldDexElements.getClass().getComponentType(), oldDexElements.length + myDexElements.length);
System.arraycopy(myDexElements,0,newDexElements,0,myDexElements.length);
System.arraycopy(oldDexElements,0,newDexElements,myDexElements.length,oldDexElements.length);
//設置
dexElementsField.set(pathList,newDexElements);
Toast.makeText(context,"裝載完成",Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}
private List<File> getList(File fileDir){
File[] files = fileDir.listFiles();
List<File> list=new ArrayList<>();
for (File file : files) {
if(file.getName().endsWith(".dex")){
list.add(file);
}
}
return list;
}
public DexClassLoader getDexClassLoader() {
return dexClassLoader;
}
public static Field getField(Object object, String name) throws NoSuchFieldException {
Class<?> aClass = object.getClass();
while (aClass != null) {
try {
Field declaredField = aClass.getDeclaredField(name);
if (!declaredField.isAccessible()) {
declaredField.setAccessible(true);
}
return declaredField;
} catch (NoSuchFieldException e) {
e.printStackTrace();
aClass=aClass.getSuperclass();
}
}
throw new NoSuchFieldException("找不到字段");
}
public static Method getMethod(Object object, String name,Class... value) throws NoSuchMethodException {
Class<?> aClass = object.getClass();
while (aClass != null) {
try {
Method declaredMethod = aClass.getDeclaredMethod(name, value);
if (!declaredMethod.isAccessible()) {
declaredMethod.setAccessible(true);
}
return declaredMethod;
} catch (NoSuchMethodException e) {
e.printStackTrace();
aClass=aClass.getSuperclass();
}
}
throw new NoSuchMethodException("找不到方法");
}
public static void copyFiles(Context context, String fileName, File desFile) {
InputStream in = null;
OutputStream out = null;
try {
in = context.getAssets().open(fileName);
out = new FileOutputStream(desFile.getAbsolutePath());
byte[] bytes = new byte[1024];
int len = 0;
while ((len = in.read(bytes)) != -1)
out.write(bytes, 0, len);
out.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null)
in.close();
if (out != null)
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
註釋比較清晰了,代碼也在git上,需要的自行下載吧~
代碼地址:https://github.com/VjayYi/HotFixDemo
博客有借鑑一些其他博客內容,圖片PPT均爲自己所畫。
.