日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

ClassLoader 類型

JAVA 中的 ClassLoader 可以加載 jar 文件和 Class文件(本質是加載 Class 文件),這一點在 Android 中并不適用,因為無論 DVM 還是 ART 它們加載的不再是 Class 文件,而是 dex 文件。

Android 中的 ClassLoader 類型和 Java 中的 ClassLoader 類型類似,也分為兩種類型,分別是 系統 ClassLoader 和 自定義 ClassLoader 。其中 Android 系統 ClassLoader 包括三種分別是 BootClassLoader 、 PathClassLoader 和 DexClassLoader ,而 Java 系統類加載器也包括3種,分別是 Bootstrap ClassLoader 、 Extensions ClassLoader 和 App ClassLoader 。

BootClassLoader

Android 系統啟動時會使用 BootClassLoader 來預加載常用類,與 Java 中的 BootClassLoader 不同,它并不是由 C/C++ 代碼實現,而是由 Java 實現的, BootClassLoade 的代碼如下所示

// libcore/ojluni/src/main/java/java/lang/ClassLoader.java
class BootClassLoader extends ClassLoader {

    private static BootClassLoader instance;

    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

    public BootClassLoader() {
        super(null);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }

    ...
}

BootClassLoader 是 ClassLoader 的內部類,并繼承自 ClassLoader 。 BootClassLoader 是一個單例類, 需要注意的是 BootClassLoader 的訪問修飾符是默認的,只有在同一個包中才可以訪問,因此我們在應用程序中是無法直接調用的 。

PathClassLoader

Android 系統使用 PathClassLoader 來加載系統類和應用程序的類,如果是加載非系統應用程序類,則會加載 data/app/$packagename 下的 dex 文件以及包含 dex 的 apk 文件或 jar 文件,不管是加載哪種文件,最終都是要加載 dex 文件,在這里為了方便理解,我們將 dex 文件以及包含 dex 的 apk 文件或 jar 文件統稱為 dex 相關文件。 PathClassLoader 不建議開發直接使用。

// libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

PathClassLoader 繼承自 BaseDexClassLoader ,很明顯 PathClassLoader 的方法實現都在 BaseDexClassLoader 中。

PathClassLoader 的構造方法有三個參數:

  • dexPath:dex 文件以及包含 dex 的 apk 文件或 jar 文件的路徑集合,多個路徑用文件分隔符分隔,默認文件分隔符為‘:’。
  • librarySearchPath:包含 C/C++ 庫的路徑集合,多個路徑用文件分隔符分隔分割,可以為 null
  • parent:ClassLoader 的 parent

DexClassLoader

DexClassLoader 可以加載 dex 文件以及包含 dex 的 apk 文件或 jar 文件,也支持從 SD 卡進行加載,這也就意味著 DexClassLoader 可以在應用未安裝的情況下加載 dex 相關文件。 因此,它是熱修復和插件化技術的基礎。

public class DexClassLoader extends BaseDexClassLoader {

    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

DexClassLoader 構造方法的參數要比 PathClassLoader 多一個 optimizedDirectory 參數,參數 optimizedDirectory 代表什么呢?應用程序在第一次被加載的時候,為了提高以后的啟動速度和執行效率,Android 系統會對 dex 相關文件做一定程度的優化,并生成一個 ODEX 文件,此后再運行這個應用程序的時候,只要加載優化過的 ODEX 文件就行了,省去了每次都要優化的時間,而參數 optimizedDirectory 就是代表存儲 ODEX 文件的路徑,這個路徑必須是一個內部存儲路徑。 PathClassLoader 沒有參數 optimizedDirectory ,這是因為 PathClassLoader 已經默認了參數 optimizedDirectory 的路徑為: /data/dalvik-cache 。 DexClassLoader 也繼承自 BaseDexClassLoader ,方法實現也都在 BaseDexClassLoader 中。

關于以上 ClassLoader 在 Android 系統中的創建過程,這里牽扯到 Zygote 進程,非本文的重點,故不在此進行討論。

ClassLoader 繼承關系

一篇文章搞懂熱修復類加載方案原理

 

  • ClassLoader 是一個抽象類,其中定義了 ClassLoader 的主要功能。 BootClassLoader 是它的內部類。
  • SecureClassLoader 類和 JDK8 中的 SecureClassLoader 類的代碼是一樣的,它繼承了抽象類 ClassLoader 。 SecureClassLoader 并不是 ClassLoader 的實現類,而是拓展了 ClassLoader 類加入了權限方面的功能,加強了 ClassLoader 的安全性。
  • URLClassLoader 類和 JDK8 中的 URLClassLoader 類的代碼是一樣的,它繼承自 SecureClassLoader ,用來通過URl路徑從 jar 文件和文件夾中加載類和資源。
  • BaseDexClassLoader 繼承自 ClassLoader ,是抽象類 ClassLoader 的具體實現類, PathClassLoader 和 DexClassLoader 都繼承它。

下面看看運行一個 Android 程序需要用到幾種類型的類加載器

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var classLoader = this.classLoader

        // 打印 ClassLoader 繼承關系
        while (classLoader != null) {
            Log.d("MainActivity", classLoader.toString())
            classLoader = classLoader.parent
        }
    }
}

將 MainActivity 的類加載器打印出來,并且打印當前類加載器的父加載器,直到沒有父加載器,則終止循環。打印結果如下:

com.zhgqthomas.github.hotfixdemo D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk"],nativeLibraryDirectories=[/data/app/com.zhgqthomas.github.hotfixdemo-2/lib/arm64, /oem/lib64, /system/lib64, /vendor/lib64]]]

com.zhgqthomas.github.hotfixdemo D/MainActivity: java.lang.BootClassLoader@4d7e926

可以看到有兩種類加載器,一種是 PathClassLoader ,另一種則是 BootClassLoader 。 DexPathList 中包含了很多路徑,其中 /data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk 就是示例應用安裝在手機上的位置。

雙親委托模式

類加載器查找 Class 所采用的是雙親委托模式, 所謂雙親委托模式就是首先判斷該 Class 是否已經加載,如果沒有則不是自身去查找而是委托給父加載器進行查找,這樣依次的進行遞歸,直到委托到最頂層的 BootstrapClassLoader ,如果 BootstrapClassLoader 找到了該 Class,就會直接返回,如果沒找到,則繼續依次向下查找,如果還沒找到則最后會交由自身去查找。 這是 JDK 中 ClassLoader 的實現邏輯,Android 中的 ClassLoader 在 findBootstrapClassOrNull 方法的邏輯處理上存在差異。

// ClassLoader.java

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        // 委托父加載器進行查找
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                }
            }
            return c;
    }

上面的代碼很容易理解,首先會查找加載類是否已經被加載了,如果是直接返回,否則委托給父加載器進行查找,直到沒有父加載器則會調用 findBootstrapClassOrNull 方法。

下面看一下 findBootstrapClassOrNull 在 JDK 和 Android 中分別是如何實現的

// JDK ClassLoader.java

    private Class<?> findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

JDK 中 findBootstrapClassOrNull 會最終交由 BootstrapClassLoader 去查找 Class 文件,上面提到過 BootstrapClassLoader 是由 C++ 實現的,所以 findBootstrapClass 是一個 native 的方法

// JDK ClassLoader.java

    private native Class<?> findBootstrapClass(String name);

在 Android 中 findBootstrapClassOrNull 的實現跟 JDK 是有差別的

// Android 
    private Class<?> findBootstrapClassOrNull(String name)
    {
        return null;
    }

Android 中因為不需要使用到 BootstrapClassLoader 所以該方法直接返回來 null

正是利用類加載器查找 Class 采用的雙親委托模式,所以可以利用反射修改類加載器加載 dex 相關文件的順序,從而達到熱修復的目的

類加載過程

通過上面分析可知

  • PathClassLoader 可以加載 Android 系統中的 dex 文件
  • DexClassLoader 可以加載任意目錄的 dex/zip/apk/jar 文件,但是要指定 optimizedDirectory 。

通過代碼可知這兩個類只是繼承了 BaseDexClassLoader ,具體的實現依舊是由 BaseDexClassLoader 來完成。

BaseDexClassLoader

// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

public class BaseDexClassLoader extends ClassLoader {

    ...

    private final DexPathList pathList;

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
    }

    /**
     * @hide
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

        if (reporter != null) {
            reportClassLoaderChain();
        }
    }

    ...

    public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
        // TODO We should support giving this a library search path maybe.
        super(parent);
        this.pathList = new DexPathList(this, dexFiles);
    }

    ...
}

通過 BaseDexClassLoader 構造方法可以知道,最重要的是去初始化 pathList 也就是 DexPathList 這個類,該類主要是用于管理 dex 相關文件

// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions); // 查找邏輯交給 DexPathList
        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;
    }

BaseDexClassLoader 中最重要的是這個 findClass 方法,這個方法用來加載 dex 文件中對應的 class 文件。而最終是交由 DexPathList 類來處理實現 findClass

DexPathList

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

final class DexPathList {
    ...

    /** class definition context */
    private final ClassLoader definingContext;

    /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private Element[] dexElements;

    ...

    DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
       ...

        this.definingContext = definingContext;

        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);
        ...
    }

}

查看 DexPathList 核心構造函數的代碼可知, DexPathList 類通過 Element 來存儲 dex 路徑 ,并且通過 makeDexElements 函數來加載 dex 相關文件,并返回 Element 集合

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

    private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      /*
       * Open all files and load the (direct or contained) dex files up front.
       */
      for (File file : files) {
          if (file.isDirectory()) {
              // We support directories for looking up resources. Looking up resources in
              // directories is useful for running libcore tests.
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();

              DexFile dex = null;
              if (name.endsWith(DEX_SUFFIX)) { // 判斷是否是 dex 文件
                  // Raw dex file (not inside a zip/jar).
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else { // 如果是 apk, jar, zip 等文件
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  } catch (IOException suppressed) {
                      /*
                       * IOException might get thrown "legitimately" by the DexFile constructor if
                       * the zip file turns out to be resource-only (that is, no classes.dex file
                       * in it).
                       * Let dex == null and hang on to the exception to add to the tea-leaves for
                       * when findClass returns null.
                       */
                      suppressedExceptions.add(suppressed);
                  }

                    // 將 dex 文件或壓縮文件包裝成 Element 對象,并添加到 Element 集合中
                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      elements[elementsPos++] = new Element(dex, file);
                  }
              }
              if (dex != null && isTrusted) {
                dex.setTrusted();
              }
          } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

總體來說, DexPathList 的構造函數是將 dex 相關文件(可能是 dex、apk、jar、zip , 這些類型在一開始時就定義好了)封裝成一個 Element 對象,最后添加到 Element 集合中

其實,Android 的類加載器不管是 PathClassLoader,還是 DexClassLoader,它們最后只認 dex 文件,而 loadDexFile 是加載 dex 文件的核心方法,可以從 jar、apk、zip 中提取出 dex

// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

在 DexPathList 的構造函數中已經初始化了 dexElements ,所以這個方法就很好理解了,只是對 Element 數組進行遍歷,一旦找到類名與 name 相同的類時,就直接返回這個 class,找不到則返回 null

熱修復實現

通過上面的分析可以知道運行一個 Android 程序是使用到 PathClassLoader ,即 BaseDexClassLoader ,而 apk 中的 dex 相關文件都會存儲在 BaseDexClassLoader 的 pathList 對象的 dexElements 屬性中。

那么熱修復的原理就是將改好 bug 的 dex 相關文件放進 dexElements 集合的頭部,這樣遍歷時會首先遍歷修復好的 dex 并找到修復好的類,因為類加載器的雙親委托模式,舊 dex 中的存有 bug 的 class 是沒有機會上場的。這樣就能實現在沒有發布新版本的情況下,修復現有的 bug class

手動實現熱修復功能

根據上面熱修復的原理,對應的思路可歸納如下

  1. 創建 BaseDexClassLoader 的子類 DexClassLoader 加載器
  2. 加載修復好的 class.dex (服務器下載的修復包)
  3. 將自有的和系統的 dexElements 進行合并,并設置自由的 dexElements 優先級
  4. 通過反射技術,賦值給系統的 pathList

分享到:
標簽:修復
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定