Android 中的ClassLoader

概述

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

其實Android中的ClassLoaderjava中的是不一樣的,因爲java中的CalssLoader主要加載Class文件,但是Android中的ClassLoader主要加載dex文件

Android中的ClassLoader

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

BootClassLoader

Android 系統啓動時,會用BootClassLoader來預加載常用類,與java中的ClassLoader不同,他不是用C++實現,而是用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;
    }
...

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

DexClassLoader

DexClassLoader可以加載dex文件和包含dex文件的壓縮文件(比如jar和apk文件),不管加載那種文件,最終都是加載dex文件,我們看一下代碼

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有四個參數

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

DexClassLoade繼承自BaseDexClassLoader,所有的實現都是在BaseDexClassLoader

PathClassLoader

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

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,所有的實現都是在BaseDexClassLoader

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進階解密》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章