Android熱更新實現原理

yangxi_001發表於2017-02-09

原文出處:http://blog.csdn.net/lzyzsd/article/details/49843581 

最近Android社群的氛圍很不錯嘛,連續放出一系列的Android動態載入外掛和熱更新庫,這篇文章就來介紹一下Android中實現熱更新的原理。

ClassLoader

我們知道Java在執行時載入對應的類是通過ClassLoader來實現的,ClassLoader本身是一個抽象來,Android中使用PathClassLoader類作為Android的預設的類載入器, 
PathClassLoader其實實現的就是簡單的從檔案系統中載入類檔案。PathClassLoade本身繼承自BaseDexClassLoader,BaseDexClassLoader重寫了findClass方法, 
該方法是ClassLoader的核心

  1. @Override
  2. protected Class<?> findClass(String name) throws ClassNotFoundException {
  3.     List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
  4.     Class c = pathList.findClass(name, suppressedExceptions);
  5.     if (== null) {
  6.         ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
  7.         for (Throwable t : suppressedExceptions) {
  8.             cnfe.addSuppressed(t);
  9.         }
  10.         throw cnfe;
  11.     }
  12.     return c;
  13. }

看原始碼可知,BaseDexClassLoader將findClass方法委託給了pathList物件的findClass方法,pathList物件是在BaseDexClassLoader的建構函式中new出來的, 
它的型別是DexPathList。看下DexPathList.findClass原始碼是如何做的:

  1. public Class findClass(String name, List<Throwable> suppressed) {
  2.     for (Element element : dexElements) {
  3.         DexFile dex = element.dexFile;
  4.  
  5.         if (dex != null) {
  6.             Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
  7.             if (clazz != null) {
  8.                 return clazz;
  9.             }
  10.         }
  11.     }
  12.     if (dexElementsSuppressedExceptions != null) {
  13.         suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
  14.     }
  15.     return null;
  16. }

直接就是遍歷dexElements列表,然後通過呼叫element.dexFile物件上的loadClassBinaryName方法來載入類,如果返回值不是null,就表示載入類成功,會將這個Class物件返回。 
而dexElements物件是在DexPathList類的建構函式中完成初始化的。

  1. this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);

makeDexElements所做的事情就是遍歷我們傳遞來的dexPath,然後一次載入每個dex檔案。

實現

上面分析了Android中的類的載入的流程,可以看出來DexPathList物件中的dexElements列表是類載入的一個核心,一個類如果能被成功載入,那麼它的dex一定 
會出現在dexElements所對應的dex檔案中,並且dexElements中出現的順序也很重要,在dexElements前面出現的dex會被優先載入,一旦Class被載入成功, 
就會立即返回,也就是說,我們的如果想做hotpatch,一定要保證我們的hotpacth dex檔案出現在dexElements列表的前面。

要實現熱更新,就需要我們在執行時去更改PathClassLoader.pathList.dexElements,由於這些屬性都是private的,因此需要通過反射來修改。另外,構造我們自己的dex檔案 
所對應的dexElements陣列的時候,我們也可以採取一個比較取巧的方式,就是通過構造一個DexClassLoader物件來載入我們的dex檔案,並且呼叫一次dexClassLoader.loadClass(dummyClassName); 
方法,這樣,dexClassLoader.pathList.dexElements中,就會包含我們的dex,通過把dexClassLoader.pathList.dexElements插入到系統預設的classLoader.pathList.dexElements列表前面,就可以讓系統優先載入我們的dex中的類,從而可以實現熱更新了。下面展示一部分程式碼

  1. private static synchronized Boolean injectAboveEqualApiLevel14(
  2.             String dexPath, String defaultDexOptPath, String nativeLibPath, String dummyClassName) {
  3.     Log.i(TAG, "--> injectAboveEqualApiLevel14");
  4.     PathClassLoader pathClassLoader = (PathClassLoader) DexInjector.class.getClassLoader();
  5.     DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, nativeLibPath, pathClassLoader);
  6.     try {
  7.         dexClassLoader.loadClass(dummyClassName);
  8.         Object dexElements = combineArray(
  9.                 getDexElements(getPathList(pathClassLoader)),
  10.                 getDexElements(getPathList(dexClassLoader)));
  11.  
  12.  
  13.         Object pathList = getPathList(pathClassLoader);
  14.         setField(pathList, pathList.getClass(), "dexElements", dexElements);
  15.     } catch (Throwable e) {
  16.         e.printStackTrace();
  17.         return false;
  18.     }
  19.     Log.i(TAG, "<-- injectAboveEqualApiLevel14 End.");
  20.     return true;
  21. }

完整的demo請參考我的GitHub

上面只是說了一下hotpatch的原理,具體實現的時候,如果你的app應用了multidex,還會遇到其他的坑,請參考:

相關文章