Android ClassLoader

1. ClassLoader的定義

  • 將Class字節碼轉換成內存中的Class對象,實現Class的加載
    • Class字節碼本質就是字節數組

2. Android ClassLoader 結構

ClassLoader.PNG

  • Android之中的ClassLoader有PathClassLoader和DexClassLoader
    • 這兩種ClassLoader都派生自BaseDexClassLoader
  • BaseDexClassLoader是ClassLoader的子類
    • DexPathList是優化後的Dex列表
    • BootClassLoader是BaseDexClassLoader的內部類
    • URLClassLoader在android系統中無用

2.1. BootClassLoader

  • 和java虛擬機中不一樣的是BootClassLoader是ClassLoader內部類
  • 由java代碼實現而不是c++實現
  • 是Android平臺上全部ClassLoader的最終parent
  • 這個內部類是包內可見,因此咱們無法使用

2.2. URLClassLoader

  • 只能用於加載jar文件,可是因爲 dalvik 不能直接識別jar,因此在 Android 中沒法使用這個加載器

2.3. DexClassLoader

  • DexClassLoader支持加載APK、DEX和JAR,也能夠從SD卡進行加載
  • 在BaseDexClassLoader裏對JAR,ZIP,APK,DEX後綴的文件最後都會生成一個對應的DEX文件,因此最終處理的仍是dex文件,而URLClassLoader並無作相似的處理

2.4. PathClassLoader

  • PathClassLoader將optimizedDirectory置爲Null,也就是沒設置優化後的存放路徑
  • optimizedDirectory爲null時的默認路徑就是/data/dalvik-cache 目錄

2.4.1. PathClassLoader和DexClassLoader的區別|API26以前

  • DexClassLoader可指定optimizedDirectory

2.4.2.PathClassLoader和DexClassLoader無區別| API26以後

  • 其實在Android8.0|API26以後,系統是不容許指定optimizedDirectory目錄的,也就致使根本上無區別,源碼以下:
public PathClassLoader(String dexPath, ClassLoader parent) {
    super((String)null, (File)null, (String)null, (ClassLoader)null);
    throw new RuntimeException("Stub!");
}

public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
    super((String)null, (File)null, (String)null, (ClassLoader)null);
    throw new RuntimeException("Stub!");
}

public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }

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

   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();
    }
}
    
複製代碼

3. BaseDexClassLoader

3.1. 構造參數

//構造方法
	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();
        }
    }
    /** * Reports the current class loader chain to the registered {@code reporter}. * The chain is reported only if all its elements are {@code BaseDexClassLoader}. */
    private void reportClassLoaderChain() {
        ArrayList<BaseDexClassLoader> classLoadersChain = new ArrayList<>();
        ArrayList<String> classPaths = new ArrayList<>();
        classLoadersChain.add(this);
        classPaths.add(String.join(File.pathSeparator, pathList.getDexPaths()));
        boolean onlySawSupportedClassLoaders = true;
        ClassLoader bootClassLoader = ClassLoader.getSystemClassLoader().getParent();
        ClassLoader current = getParent();
        while (current != null && current != bootClassLoader) {
            if (current instanceof BaseDexClassLoader) {
                BaseDexClassLoader bdcCurrent = (BaseDexClassLoader) current;
                classLoadersChain.add(bdcCurrent);
              	classPaths.add(String.join(File.pathSeparator, bdcCurrent.pathList.getDexPaths()));
            } else {
               onlySawSupportedClassLoaders = false;
                break;
            }
            current = current.getParent();
        }
        if (onlySawSupportedClassLoaders) {
            reporter.report(classLoadersChain, classPaths);
        }
    }
複製代碼
  • String dexPath
    • 目標類所在的APK或者JAR文件的全路徑,類加載器對其進行加載
    • 若是要同時加載多個路徑,經過**System.getProperty(「path.separtor」)**獲取分隔符進行分割
    • 將dexPath路徑上的文件ODEX優化到內部位置optimizedDirectory之中,對其進行加載
  • File optimizedDirectory
    • 因爲DEX文件包含在JAR或者APK文件之中,因此要對其進行解壓以獲取DEX文件,此參數指定了解壓目錄
    • 當系統首次啓動的時候直接獲取的是解壓後的DEX(ODEX)
    • ClassLoader只能加載內部存儲路徑中的dex文件,因此這個路徑必須爲內部路徑。
  • String librarySearchPath
    • 目標類之中的C/C++庫的存放路徑
  • ClassLoader parent
    • 該類加載器的父類加載器,通常爲當前執行類的裝載器,例如在Android中以context.getClassLoader()做爲父裝載器。

3.2. loadClass

  • 用於控制加載Class的入口
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) {
            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.
                c = findClass(name);
            }
        }
        return c;
}
複製代碼
  • 首先判斷這個類是否以前被加載過,若是有則直接返回
  • 若是沒有則首先嚐試讓parent ClassLoader進行加載,加載不成功纔在本身的findClass中進行加載。

3.2.1. parents delegate

  • 和java虛擬機中常見的parents delegate模型一致的,這種模型並非一個強制性的約束模型
  • 好比你能夠繼承ClassLoader複寫loadCalss方法來破壞這種模型,只不過parents delegate是一種被推薦的實現類加載器的方式
  • jdk1.2之後已經不提倡用戶在覆蓋loadClass方法,而應該把本身的類加載邏輯寫到findClass中。
  • parents delegate機制爲的是放置類重複加載,先有父親進行循環分析。

3.2.2. 共享與隔離

  • 實現共享,一些Framework層級的類一旦被頂層的ClassLoader加載過就緩存在內存裏面,之後任何地方用到都不須要從新加載。
  • 實現隔離,不一樣繼承路線上的ClassLoader加載的類確定不是同一個類,這樣的限制避免了用戶本身的代碼冒充核心類庫的類訪問核心類庫包可見成員的狀況。

3.3. findClass

  • 用於經過本身內部的邏輯獲取目標類的字節碼
@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        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;
    }

	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;
    }
複製代碼
  • pathList內部包含的就是ODEX集合(優化後的dex),經過findClass獲取到全部dexElements,最終調用findClass獲取Class。

3.4. defineClass|非Android平臺

  • 用於將字節碼轉換成Class對象,可是在Android平臺是不被容許的
@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len)
    throws ClassFormatError
{
    throw new UnsupportedOperationException("can't load this type of class file");
}
複製代碼

4.ART虛擬機

Android Runtime(縮寫爲ART),在Android 5.0及後續Android版本中做爲正式的運行時庫取代了以往的Dalvik虛擬機。ART可以把應用程序的字節碼轉換爲機器碼,是Android所使用的一種新的虛擬機。java

它與Dalvik的主要不一樣在於:android

  • Dalvik採用的是JIT技術,字節碼都須要經過即時編譯器(just in time ,JIT)轉換爲機器碼,這會拖慢應用的運行效率,
  • ART採用Ahead-of-time(AOT)技術,應用在第一次安裝的時候,字節碼就會預先編譯成機器碼,這個過程叫作預編譯。ART同時也改善了性能、垃圾回收(Garbage Collection)、應用程序除錯以及性能分析

可是,運行時內存佔用空間較少一樣意味着編譯二進制須要更高的存儲c++

  • ART模式相比原來的Dalvik,會在安裝APK的時候,使用Android系統自帶的dex2oat工具把APK裏面的.dex文件轉化成OAT文件,OAT文件是一種Android私有ELF文件格式,它不只包含有從DEX文件翻譯而來的本地機器指令,還包含有原來的DEX文件內容。這使得咱們無需從新編譯原有的APK就可讓它正常地在ART裏面運行,也就是咱們不須要改變原來的APK編程接口
  • ART模式的系統裏,一樣存在DexClassLoader類,包名路徑也沒變,只不過它的具體實現與原來的有所不一樣,可是接口是一致的。實際上,ART運行時就是和Dalvik虛擬機同樣,實現了一套徹底兼容Java虛擬機的接口
  • class:java 編譯後的⽂件,每一個類對應⼀個 class ⽂件
  • dex:Dalvik EXecutable 把 class 打包在⼀起,⼀個 dex 能夠包含多個 class ⽂件
  • odex:Optimized DEX 針對系統的優化,例如某個⽅法的調⽤指令,會把虛擬的調⽤轉換爲使⽤具體的 index,這樣在執⾏的時候就不⽤再查找了
  • oat:Optimized Android fifile Type。使⽤ AOT 策略對 dex 預先編譯(解釋)成本地指令,這樣再運⾏階段就不需再經歷⼀次解釋過程,程序的運⾏能夠更快AOT:Ahead-Of-Time compilation 預先編譯

5.DEX文件

  • class:java 編譯後的⽂件,每一個類對應⼀個 class ⽂件
  • dex:Dalvik EXecutable 把 class 打包在⼀起,⼀個 dex 能夠包含多個 class ⽂件
  • odex:Optimized DEX 針對系統的優化,例如某個⽅法的調⽤指令,會把虛擬的調⽤轉換爲使⽤具體的 index,這樣在執⾏的時候就不⽤再查找了
  • oat:Optimized Android fifile Type。使⽤ AOT 策略對 dex 預先編譯(解釋)成本地指令,這樣再運⾏階段就不需再經歷⼀次解釋過程,程序的運⾏能夠更快
    • AOT:Ahead-Of-Time compilation 預先編譯
相關文章
相關標籤/搜索