編輯:關於Android編程
熱修復概念: 以補丁的方式動態修復緊急Bug,不再需要重新發布App,不再需要用戶重新下載。
PathClassloader和DexClassLoader:
(1)PathClassloader作為其系統類和應用類的加載器,只能去加載已經安裝到Android系統中的apk文件。
(2)DexClassLoader可以用來從.jar和.apk類型的文件內部加載classes.dex文件。可以用來執行非安裝的程序代碼。
(3)Android使用PathClassLoader作為其類加載器,DexClassLoader可以從.jar和.apk類型的文件內部加載classes.dex文件。
熱修復原理:
PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader
在BaseDexClassLoader中有如下源碼:
#BaseDexClassLoader @Override protected Class findClass(String name) throws ClassNotFoundException { Class clazz = pathList.findClass(name); if (clazz == null) { throw new ClassNotFoundException(name); } return clazz; } #DexPathList 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; } #DexFile public Class loadClassBinaryName(String name, ClassLoader loader) { return defineClass(name, loader, mCookie); } private native static Class defineClass(String name, ClassLoader loader, int cookie);1 n j n
1 BaseDexClassLoader中有個pathList對象,pathList中包含一個DexFile的集合dexElements,而對於類加載呢,就是遍歷這個集合,通過DexFile去尋找。
2 一個ClassLoader可以包含多個dex文件,每個dex文件是一個Element,多個dex文件排列成一個有序的數組dexElements,當找類的時候,會按順序遍歷dex文件,然後從當前遍歷的dex文件中找類,如果找類則返回,如果找不到從下一個dex文件繼續查找。
3 理論上,如果在不同的dex中有相同的類存在,那麼會優先選擇排在前面的dex文件的類.
4 把有問題的類打包到一個dex(patch.dex)中去,然後把這個dex插入到Elements的最前面.
dex校驗: 如果兩個相關聯的類在不同的dex中就會報錯,例如ClassA 引用了ClassB,但是發現這這兩個類所在的dex不在一起,其中:
1. ClassA 在classes.dex中
2. ClassB 在patch.dex中
結果發生了錯誤。
dex校驗的前提: 如果引用者這個類被打上了CLASS_ISPREVERIFIED標志,那麼就會進行dex的校驗。
相關類打上CLASS_ISPREVERIFIED標志的發生場景:
在虛擬機啟動的時候,當verify選項被打開的時候,如果static方法、private方法、構造函數等,其中的直接引用(第一層關系)到的類都在同一個dex文件中,那麼這個類就會被打上CLASS_ISPREVERIFIED
其中AntilazyLoad類會被打包成單獨的hack.dex,這樣當安裝apk的時候,classes.dex內的類都會引用一個在不相同dex中的AntilazyLoad類,這樣就防止了類被打上CLASS_ISPREVERIFIED的標志了,只要沒被打上這個標志的類都可以進行打補丁操作。
在class文件中插入代碼來阻止相關類打上CLASS_ISPREVERIFIED標志:在dx工具執行之前,將LoadBugClass.class文件呢,進行修改,再其構造中添加System.out.println(dodola.hackdex.AntilazyLoad.class),然後繼續打包的流程。
原始代碼
package dodola.hackdex; public class AntilazyLoad { } package dodola.hotfix; public class BugClass { public String bug() { return "bug class"; } } package dodola.hotfix; public class LoadBugClass { public String getBugString() { BugClass bugClass = new BugClass(); return bugClass.bug(); } }
插入代碼
System.out.println(dodola.hackdex.AntilazyLoad.class)
在構造函數中插入操作代碼(javassist)
package test; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; public class InjectHack { public static void main(String[] args) { try { String path = "/Users/zhy/develop_work/eclipse_android/imooc/JavassistTest/"; ClassPool classes = ClassPool.getDefault(); classes.appendClassPath(path + "bin");//項目的bin目錄即可 CtClass c = classes.get("dodola.hotfix.LoadBugClass"); CtConstructor ctConstructor = c.getConstructors()[0]; ctConstructor .insertAfter("System.out.println(dodola.hackdex.AntilazyLoad.class);"); c.writeFile(path + "/output"); } catch (Exception e) { e.printStackTrace(); } } }
把AntilazyLoad.class打包成jar包,然後寫入App的私有目錄,最後把該jar對應的dexElements文件插入到數組的最前面。
public class HotfixApplication extends Application { @Override public void onCreate() { super.onCreate(); //創建jar對應的文件 File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar"); //將asset文件中的jar寫到App的私有目錄下面。 Utils.prepareDex(this.getApplicationContext(), dexPath, "hackdex_dex.jar"); // 把jar對應的dexElements插入到dex數組最前面。 HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad"); try { this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
創建jar對應的文件
public class Utils { private static final int BUF_SIZE = 2048; public static boolean prepareDex(Context context, File dexInternalStoragePath, String dex_file) { BufferedInputStream bis = null; OutputStream dexWriter = null; bis = new BufferedInputStream(context.getAssets().open(dex_file)); dexWriter = new BufferedOutputStream(new FileOutputStream(dexInternalStoragePath)); byte[] buf = new byte[BUF_SIZE]; int len; while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) { dexWriter.write(buf, 0, len); } dexWriter.close(); bis.close(); return true; }
找相應的ClassLoader進行操作
public final class HotFix { public static void patch(Context context, String patchDexFile, String patchClassName) { if (patchDexFile != null && new File(patchDexFile).exists()) { try { if (hasLexClassLoader()) { injectInAliyunOs(context, patchDexFile, patchClassName); } else if (hasDexClassLoader()) { injectAboveEqualApiLevel14(context, patchDexFile, patchClassName); } else { injectBelowApiLevel14(context, patchDexFile, patchClassName); } } catch (Throwable th) { } } } }
Combine(合並)App的DexElements和*AntilazyLoad.class的DexElements*
private static boolean hasDexClassLoader() { try { Class.forName("dalvik.system.BaseDexClassLoader"); return true; } catch (ClassNotFoundException e) { return false; } } private static void injectAboveEqualApiLevel14(Context context, String str, String str2) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader(); Object a = combineArray(getDexElements(getPathList(pathClassLoader)), getDexElements(getPathList( new DexClassLoader(str, context.getDir("dex", 0).getAbsolutePath(), str, context.getClassLoader())))); Object a2 = getPathList(pathClassLoader); setField(a2, a2.getClass(), "dexElements", a); pathClassLoader.loadClass(str2); }
將Patch.jar補丁插入到APP中,過程和插入AntilazyLoad.class一樣
public class HotfixApplication extends Application { @Override public void onCreate() { super.onCreate(); File dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "hackdex_dex.jar"); Utils.prepareDex(this.getApplicationContext(), dexPath, "hack_dex.jar"); HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hackdex.AntilazyLoad"); try { this.getClassLoader().loadClass("dodola.hackdex.AntilazyLoad"); } catch (ClassNotFoundException e) { e.printStackTrace(); } dexPath = new File(getDir("dex", Context.MODE_PRIVATE), "path_dex.jar"); Utils.prepareDex(this.getApplicationContext(), dexPath, "path_dex.jar"); HotFix.patch(this, dexPath.getAbsolutePath(), "dodola.hotfix.BugClass"); } }
(1)因為我們的Patch是以獨立的jar包,插入到APP的DexElements中,
所以如果APP中中的類引用了Patch中的類,就會在校驗時報錯。因為當進行dex校驗時,如果兩個相關聯的類在不同的dex中就會報錯。(LoadBugClass引用BugClass)。
(2) 為了防止上述錯誤就要阻止Dex校驗,阻止Dex校驗的方法是阻止相關類打上CLASS_ISPREVERIFIED標志。
(3) 阻止相關類打上CLASS_ISPREVERIFIED標志的做法是:在相關引用的類(LoadBugClass.class)的構造方法中,引用另外一個jar中類AntilazyLoad.class
(4)因為AntilazyLoad.class在另一個jar中,所以需要把該jar對應的dex插入到App中。並且在Application中的onCreate()方法中將該類加載進來。
(5)把Patch.jar對應的dexElement加載進App中。因為Patch.jar放在dex數組的第一個位置,所以首先被加載。即:如果在不同的dex中有相同的類存在,那麼會優先選擇排在前面的dex文件的類。
Android自帶音頻均衡器MusicFx分析 種種原因,我要簡單分析一個Android中built-in的音頻均衡器MusicFx。重點是它的默認Í
我的程序是在MainActivity中有一個自定義的MyLayout布局,MyLayout布局下面有一個自定義的MyButton。情況1PS:表格中super代表返回父類
Toast的自定義使用原理與其類似。1.Toast源碼分析老規矩,我們先去看Toast的源碼。Toast有兩種顯示布局方式,一種最常見調用Toast.makeText()
連續的輸入事件可能會產生一定的手勢操作,例如滑動手勢和捏合手勢。在Chromium中,網頁的輸入事件是在Browser進程中捕捉的。Browser進程捕獲輸入事件之後,會