Android類裝載機制

前言

上兩篇文章分析了資源的加載和進程,Activity啓動相關的內容,這篇是Dex加載相關的內容了,本篇結束,咱們也就能夠開始對於一些熱修復,插件化框架的實現剖析了。java

Android中ClassLoader

ClassLoader

上圖爲Android中ClassLoader的類圖,與JVM不一樣,Dalvik的虛擬機不能用ClassCload直接加載.dex,Android從ClassLoader派生出了兩個類:DexClassLoaderPathClassLoader。而這兩個類就是咱們加載dex文件的關鍵,這二者的區別是:android

  • DexClassLoader:能夠加載jar/apk/dex,能夠從SD卡中加載未安裝的apk。git

  • PathClassLoader:要傳入系統中apk的存放Path,因此只能加載已經安裝的apk文件。github

Dalvik虛擬機如同其餘Java虛擬機同樣,在運行程序時首先須要將對應的類加載到內存中。而在Java標準的虛擬機中,類加載能夠從class文件中讀取,也能夠是其餘形式的二進制流。所以,咱們經常利用這一點,在程序運行時手動加載Class,從而達到代碼動態加載執行的目的。編程

只不過Android平臺上虛擬機運行的是Dex字節碼,一種對class文件優化的產物,傳統Class文件是一個Java源碼文件會生成一個.class文件,而Android是把全部Class文件進行合併,優化,而後生成一個最終的class.dex,目的是把不一樣class文件重複的東西只需保留一份,若是咱們的Android應用不進行分dex處理,最後一個應用的apk只會有一個dex文件。數組

laodClass.png

PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader,其中的主要邏輯都是在BaseDexClassLoader完成的。bash

能夠看出在加載類時首先判斷這個類是否以前被加載過,若是有則直接返回,若是沒有則首先嚐試讓parent ClassLoader進行加載,加載不成功纔在本身的findClass中進行加載。這和java虛擬機中常見的雙親委派模型一致的,這種模型並非一個強制性的約束模型,好比你能夠繼承ClassLoader複寫loadCalss方法來破壞這種模型,只不過雙親委派模是一種被推薦的實現類加載器的方式,並且jdk1.2之後已經不提倡用戶在覆蓋loadClass方法,而應該把本身的類加載邏輯寫到findClass中。數據結構

ClassLoader源碼分析

  • BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {
    private final String originalPath;
    private final DexPathList pathList;
 
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.originalPath = dexPath;
        //構造DexPahtList對象
        this.pathList =
            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //Class 查找在PathList中根據類名進行查找
        Class clazz = pathList.findClass(name);
        if (clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }
    @Override
    protected URL findResource(String name) {
        return pathList.findResource(name);
    }
    @Override
    protected Enumeration<URL> findResources(String name) {
        return pathList.findResources(name);
    }
    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }

    @Override
    protected synchronized Package getPackage(String name) {
        if (name != null && !name.isEmpty()) {
            Package pack = super.getPackage(name);
            if (pack == null) {
                pack = definePackage(name, "Unknown", "0.0", "Unknown",
                        "Unknown", "0.0", "Unknown", null);
            }
            return pack;
        }
        return null;
    }
    @Override
    public String toString() {
        return getClass().getName() + "[" + originalPath + "]";
    }
}
複製代碼

經過源碼能夠看出,在BaseDexClassLoader的構造函數中建立了DexPathList實例,在BaseDexClassLoader中,對於類的查找和資源的查找,都是經過其中的DexPathList實例來進行的。對於類的等相關信息的查找是在DexPathList中實現的,接下來,咱們看一下DexPathList的源碼實現。框架

final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String APK_SUFFIX = ".apk";
    /** class definition context */
    private final ClassLoader definingContext;
  //Dex,Resource元素
    private final Element[] dexElements;
    //Native庫的地址路徑
    private final File[] nativeLibraryDirectories;

    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        if (definingContext == null) {
            throw new NullPointerException("definingContext == null");
        }
        if (dexPath == null) {
            throw new NullPointerException("dexPath == null");
        }
        if (optimizedDirectory != null) {
            if (!optimizedDirectory.exists())  {
                throw new IllegalArgumentException(
                        "optimizedDirectory doesn't exist: "
                        + optimizedDirectory);
            }
            if (!(optimizedDirectory.canRead()
                            && optimizedDirectory.canWrite())) {
                throw new IllegalArgumentException(
                        "optimizedDirectory not readable/writable: "
                        + optimizedDirectory);
            }
        }
        this.definingContext = definingContext;
        this.dexElements =
            makeDexElements(splitDexPath(dexPath), optimizedDirectory);
        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);
    }
}
複製代碼

在構造函數中,根據dexPath,構建一個DexElement數組,在後面對於類的查找就會在該數組中進行查找。ide

  • makeDexElements
/**
 * 根據給予的地址構建一個Element數組
 */
private static Element[] makeDexElements(ArrayList<File> files,
        File optimizedDirectory) {
    ArrayList<Element> elements = new ArrayList<Element>();
    /*
     * Open all files and load the (direct or contained) dex files
     * up front.
     */
    for (File file : files) {
        ZipFile zip = null;
        DexFile dex = null;
        String name = file.getName();
        if (name.endsWith(DEX_SUFFIX)) {
            // Raw dex file (not inside a zip/jar).
            try {
                dex = loadDexFile(file, optimizedDirectory);
            } catch (IOException ex) {
                System.logE("Unable to load dex file: " + file, ex);
            }
        } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
                || name.endsWith(ZIP_SUFFIX)) {
            try {
                zip = new ZipFile(file);
            } catch (IOException ex) {
            }
            try {
                dex = loadDexFile(file, optimizedDirectory);
            } catch (IOException ignored) {
            }
        } else {
        
        }
        if ((zip != null) || (dex != null)) {
            elements.add(new Element(file, zip, dex));
        }
    }
    return elements.toArray(new Element[elements.size()]);
}

複製代碼

根據傳遞的路徑,而後解析路徑下的內容,根據dex,zip等構建Element實例,而後將這個實例添加到Element數組之中。

  • loadDexFile
/**
 * 構造一個DexFile對象
 */
private static DexFile loadDexFile(File file, File optimizedDirectory)
        throws IOException {
    if (optimizedDirectory == null) {
        return new DexFile(file);
    } else {
        String optimizedPath = optimizedPathFor(file, optimizedDirectory);
        return DexFile.loadDex(file.getPath(), optimizedPath, 0);
    }
}
複製代碼

Element數據結構

static class Element {
        public final File file;
        public final ZipFile zipFile;
        public final DexFile dexFile;
        public Element(File file, ZipFile zipFile, DexFile dexFile) {
            this.file = file;
            this.zipFile = zipFile;
            this.dexFile = dexFile;
        }
}
複製代碼
  • 類的查找
/**
 * 類的查找,從DexElements中,逐個DexFile中查找,若是找到,         
 *  就加載這個類,而後返回
 */
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;
}

複製代碼

根據傳遞的目錄,加載相應的dex,apk來構建層Element實例。

  • ART和Dalvik

Android Runtime(縮寫爲ART),在Android 5.0及後續Android版本中做爲正式的運行時庫取代了以往的Dalvik虛擬機。ART可以把應用程序的字節碼轉換爲機器碼,是Android所使用的一種新的虛擬機。它與Dalvik的主要不一樣在於:Dalvik採用的是JIT技術,字節碼都須要經過即時編譯器轉換爲機器碼,這會拖慢應用的運行效率,而ART採用Ahead-of-time(AOT)技術,應用在第一次安裝的時候,字節碼就會預先編譯成機器碼,這個過程叫作預編譯。ART同時也改善了性能、垃圾回收(Garbage Collection)、應用程序出錯以及性能分析。可是請注意,運行時內存佔用空間較少一樣意味着編譯二進制須要更高的存儲。

ART模式相比原來的Dalvik,會在安裝APK的時候,使用Android系統自帶的dex2oat工具把APK裏面的.dex文件轉化成OAT文件,OAT文件是一種Android私有ELF文件格式,它不只包含有從DEX文件翻譯而來的本地機器指令,還包含有原來的DEX文件內容。這使得咱們無需從新編譯原有的APK就可讓它正常地在ART裏面運行,也就是咱們不須要改變原來的APK編程接口。ART模式的系統裏,一樣存在DexClassLoader類,包名路徑也沒變,只不過它的具體實現與原來的有所不一樣,可是接口是一致的。實際上,ART運行時就是和Dalvik虛擬機同樣,實現了一套徹底兼容Java虛擬機的接口。

  • dexopt仍是dexoat

在安裝的優化過程,dexopt函數會啓動一個子進程來執行優化,同時會根據目前系統使用的是Dalvik虛擬機仍是ART來決定將apk轉換層何種格式,若是轉換層odex格式,則調用/system/bin/dexopt文件來執行轉化,若是轉換層ART的oat格式,則調用/system/bin/dex2oat來執行轉換。

則dexopt和dexoat中會執行一些優化操做,這個優化操做也會影響到咱們後續類的動態加載。但因爲上層接口一致,所以,做爲開發者無需關心安裝時具體的優化方式。

參考資料

Android動態加載之ClassLoader詳解

Dexopt優化和驗證Dalvik (Dalvik Optimization and Verification With dexopt)

相關文章
相關標籤/搜索