Android 中的ClassLoader

概述

上一篇文章咱們瞭解了JavaClassLoader,上一篇文章傳送門JVM 類加載機制java

其實Android中的ClassLoaderjava中的是不同的,由於java中的CalssLoader主要加載Class文件,可是Android中的ClassLoader主要加載dex文件數組

Android中的ClassLoader

Android中的ClassLoader分爲倆種類型,系統類加載器自定義類加載器。其中系統的類加載器分爲三種,BootClassLoaderPathClassLoaderDexClassLoader安全

BootClassLoader

Android 系統啓動時,會用BootClassLoader來預加載經常使用類,與java中的ClassLoader不一樣,他不是用C++實現,而是用java實現的,以下:cookie

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

BootClassLoaderClassLoader的內部類,並繼承自ClassLoaderBootClassLoader是一個單例類,須要注意的是BootClassLoader是默認修飾符,只能包內訪問,咱們是沒法使用的app

DexClassLoader

DexClassLoader能夠加載dex文件和包含dex文件的壓縮文件(好比jar和apk文件),無論加載那種文件,最終都是加載dex文件,咱們看一下代碼ide

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}
複製代碼

DexClassLoader有四個參數post

  • dexPath:dex相關文件的路徑集合,多個文件用路徑分割符分割,默認的文件分割符爲 ":"
  • optimizedDirectory:解壓的dex文件儲存的路徑,這個路徑必須是一個內部儲存路徑,通常狀況下使用當錢應用程序的私有路徑/data/data/<Package Name>/...
  • librarySearchPath:包含C++庫的路徑集合,多個路徑用文件分割符分割,能夠爲null
  • parent:父加載器

DexClassLoade繼承自BaseDexClassLoader,全部的實現都是在BaseDexClassLoaderthis

PathClassLoader

AndroidPathClassLoader來加載系統類和應用程序類,代碼以下:spa

public class PathClassLoader extends BaseDexClassLoader {
    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!");
    }
}
複製代碼

PathClassLoader繼承自BaseDexClassLoader,全部的實現都是在BaseDexClassLoadercode

PathClassLoader構造方法沒有optimizedDirectory參數,由於PathClassLoader默認optimizedDirectory參數是/data/dalvik-cache,很顯然PathClassLoader沒法定義解壓的dex儲存的位置,所以PathClassLoader一般用來加載已經安裝的apkdex文件(安裝的apk的dex文件在/data/dalvik-cache中)

ClassLoder的繼承關係

運行一個應用程序須要幾個ClassLoader呢?

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ClassLoader classLoader = MainActivity.class.getClassLoader();
        while (classLoader != null) {
            Log.d("mmmClassLoader", classLoader.toString()+"\n");
            classLoader = classLoader.getParent();
        }

    }
複製代碼

看下log

mmmClassLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk"],
nativeLibraryDirectories=[/data/app/com.baidu.bpit.aibaidu.idl3-2/lib/arm64, /data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk!/lib/arm64-v8a, /system/lib64, /vendor/lib64, /system/vendor/lib64, /product/lib64]]]
 
java.lang.BootClassLoader@fcb14c9
複製代碼

咱們看到主要用了倆個ClassLoader,分別是PathClassLoaderBootClassLoader,其中DexPathList包含了不少apk的路徑,其中/data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk就是實例應用安裝在手機上的位置,DexPathList是在BaseDexClassLoder中建立的,裏面儲存dex相關文件的路徑

除了上方的3中ClassLoader,Android還提供了其餘的類加載器和ClassLoader相關類,繼承關係以下:

分別介紹一下上方的8種ClassLoader

  • ClassLoader是一個抽象類,其中定義了ClassLoder的主要功能,BootClassLoader是他的內部類
  • SecureClassLoader和JDK8中的SecureClassLoader代碼是同樣的,他繼承抽象類ClassLoaderSecureClassLoader並非ClassLoader的實現類,而是擴展了ClassLoader權限方面的功能,增強了Classloader的安全性
  • URLClassLoader和JDK8中的URLClassLoader是同樣的,他繼承了SecureClassLoader通能過URL路徑從jar文件中加載類和資源
  • InMemoryDexClassLoader他是Android8.0新加的類加載器,繼承自BaseDexClassLoader,用於加載內存中的dex文件
  • BaseDexClassLoader繼承自ClassLoader,是抽象類ClassLoader的具體實現類

ClassLoader的加載過程

Android中的ClassLoader一樣遵循雙親委託模型,ClassLoader的加載方法爲loadClass,方法定義在ClassLoader中

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException{
            // 首先檢查類是否被加載過
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    //若是父類拋出ClassNotFoundException異常
                    //則說明父類不能加載該類
                }

                if (c == null) {
                    //若是父類沒法加載,則調用自身的findClass進行加
                    c = findClass(name);
                }
            }
            return c;
    }
複製代碼

上方邏輯很清楚,首先檢查類是否被加載過,若是沒有被加載過,就調用父類的加載器加載,若是父類加載器爲空就調用啓動加載器加載,若是父類加載失敗,就調用本身的findClass加載,咱們看一下這個方法

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
複製代碼

直接拋出異常,這說明findClass須要子類BaseDexClassLoader實現,以下:

public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);

        if (reporter != null) {
            reporter.report(this.pathList.getDexPaths());
        }
    }
    
    ...
    
      @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        //註釋1
        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;
    }
複製代碼

首先在構造方法內建立了DexPathList,而後再註釋1處調用DexPathListfindClass方法

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

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

在註釋1處遍歷dexElements數組,在註釋2處調用ElementfindClass方法

public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) {
            System.err.println("Warning: Using deprecated Element constructor. Do not use internal"
                    + " APIs, this constructor will be removed in the future.");
            if (dir != null && (zip != null || dexFile != null)) {
                throw new IllegalArgumentException("Using dir and zip|dexFile no longer"
                        + " supported.");
            }
            if (isDirectory && (zip != null || dexFile != null)) {
                throw new IllegalArgumentException("Unsupported argument combination.");
            }
            if (dir != null) {
                this.path = dir;
                this.dexFile = null;
            } else {
                this.path = zip;
                this.dexFile = dexFile;
            }
        }
        ...
        
        
            public Class<?> findClass(String name, ClassLoader definingContext,
                List<Throwable> suppressed) {
            //註釋1
            return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
                    : null;
        }
複製代碼

咱們從構造方法能夠看出,它內部封裝了DexFile,他用於加載dex,註釋1處若是dexFile不爲null則調用DexFileloadClassBinaryName方法

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
        return defineClass(name, loader, mCookie, this, suppressed);
    }
複製代碼

又調用了defineClass方法

private static Class defineClass(String name, ClassLoader loader, Object cookie, DexFile dexFile, List<Throwable> suppressed) {
        Class result = null;
        try {
            //註釋1
            result = defineClassNative(name, loader, cookie, dexFile);
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
複製代碼

註釋1處調用了defineClassNative方法來加載dex相關文件,這個是native方法不在向下分析

參考: 《Android進階解密》

相關文章
相關標籤/搜索