天天看點

Android 中的ClassLoader

概述

上一篇文章我們了解了

Java

ClassLoader

,上一篇文章傳送門JVM 類加載機制

其實

Android

中的

ClassLoader

java

中的是不一樣的,因為

java

中的

CalssLoader

主要加載

Class

檔案,但是

Android

中的

ClassLoader

主要加載

dex

檔案

Android中的ClassLoader

Android

中的

ClassLoader

分為倆種類型,

系統類加載器

自定義類加載器

。其中

系統的類加載器

分為三種,

BootClassLoader

PathClassLoader

DexClassLoader

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;
    }
...
           

BootClassLoader

ClassLoader

的内部類,并繼承自

ClassLoader

BootClassLoader

是一個單例類,需要注意的是

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

Android

PathClassLoader

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

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

通常用來加載已經安裝的

apk

dex

檔案(安裝的

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

,分别是

PathClassLoader

BootClassLoader

,其中

DexPathList

包含了很多

apk

的路徑,其中

/data/app/com.baidu.bpit.aibaidu.idl3-2/base.apk

就是執行個體應用安裝在手機上的位置,

DexPathList

是在

BaseDexClassLoder

中建立的,裡面儲存

dex

相關檔案的路徑

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

Android 中的ClassLoader

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

  • ClassLoader

    是一個抽象類,其中定義了

    ClassLoder

    的主要功能,

    BootClassLoader

    是他的内部類
  • SecureClassLoader

    和JDK8中的

    SecureClassLoader

    代碼是一樣的,他繼承抽象類

    ClassLoader

    SecureClassLoader

    并不是

    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處調用

DexPathList

findClass

方法

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處調用

Element

findClass

方法

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

則調用

DexFile

loadClassBinaryName

方法

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