从源码分析 Android dexClassLoader 加载机制原理

  • Post author:
  • Post category:其他




DexClassLoader 是加载包含classes.dex文件的jar文件或者apk文件;


通过构造函数发现需要一个应用私有的,可写的目录去缓存优化的classes。可以用使用File dexoutputDir = context.getDir(“dex”,0);创建一个这样的目录,不要使用外部缓存,以保护你的应用被代码注入。


其源码如下:



public class







DexClassLoader





extends





BaseDexClassLoader


{

37    /**
38     * Creates a {@code DexClassLoader} that finds interpreted and native
39     * code.  Interpreted classes are found in a set of DEX files contained
40     * in Jar or APK files.
41     *
42     * <p>The path lists are separated using the character specified by the
43     * {@code path.separator} system property, which defaults to {@code :}.
44     *
45     * @param dexPath the list of jar/apk files containing classes and
46     *     resources, delimited by {@code File.pathSeparator}, which
47     *     defaults to {@code ":"} on Android
48     * @param optimizedDirectory directory where optimized dex files
49     *     should be written; must not be {@code null}
50     * @param libraryPath the list of directories containing native
51     *     libraries, delimited by {@code File.pathSeparator}; may be
52     *     {@code null}
53     * @param parent the parent class loader
54     */
55    public DexClassLoader(String dexPath, String optimizedDirectory,
56            String libraryPath, ClassLoader parent) {
57        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
58    }
59}
60

再解释下几个构造函数参数的意义:

dexpath为jar或apk文件目录。


optimizedDirectory为优化dex缓存目录。


libraryPath包含native lib的目录路径。


parent父类加载器。


然后执行的是父类的构造函数:


super(dexPath, new File(optimizedDirectory), libraryPath, parent);


BaseDexClassLoader 的构造函数如下:


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


第一句调用的还是父类的构造函数,也就是ClassLoader的构造函数:


protected ClassLoader(ClassLoader parentLoader) {
        this(parentLoader, false);
    }
    /*
     * constructor for the BootClassLoader which needs parent to be null.
     */
    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
       if (parentLoader == null && !nullAllowed) {
            throw new NullPointerException(“parentLoader == null && !nullAllowed”);
      }
      parent = parentLoader;
}


该构造函数把传进来的父类加载器赋给了私有变量parent。


再来看第二句:


this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);


pathList为该类的私有成员变量,类型为DexPathList,进入到DexPathList函数:

/**
78     * Constructs an instance.
79     *
80     * @param definingContext the context in which any as-yet unresolved
81     * classes should be defined
82     * @param dexPath list of dex/resource path elements, separated by
83     * {@code File.pathSeparator}
84     * @param libraryPath list of native library directory path elements,
85     * separated by {@code File.pathSeparator}
86     * @param optimizedDirectory directory where optimized {@code .dex} files
87     * should be found and written to, or {@code null} to use the default
88     * system directory for same
89     */
90    public DexPathList(ClassLoader definingContext, String dexPath,
91            String libraryPath, File optimizedDirectory) {
92
93        if (definingContext == null) {
94            throw new NullPointerException("definingContext == null");
95        }
96
97        if (dexPath == null) {
98            throw new NullPointerException("dexPath == null");
99        }
100
101        if (optimizedDirectory != null) {
102            if (!optimizedDirectory.exists())  {
103                throw new IllegalArgumentException(
104                        "optimizedDirectory doesn't exist: "
105                        + optimizedDirectory);
106            }
107
108            if (!(optimizedDirectory.canRead()
109                            && optimizedDirectory.canWrite())) {
110                throw new IllegalArgumentException(
111                        "optimizedDirectory not readable/writable: "
112                        + optimizedDirectory);
113            }
114        }
115
116        this.definingContext = definingContext;
117
118        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();

119        // save dexPath for BaseDexClassLoader
120        this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
1        suppressedExceptions);
122
123        // Native libraries may exist in both the system and
124        // application library paths, and we use this search order:
125        //
126        //   1. This class loader's library path for application libraries (libraryPath):
127        //   1.1. Native library directories
128        //   1.2. Path to libraries in apk-files
129        //   2. The VM's library path from the system property for system libraries
130        //      also known as java.library.path
131        //
132        // This order was reversed prior to Gingerbread; see http://b/2933456.
133        this.nativeLibraryDirectories = splitPaths(libraryPath, false);
134        this.systemNativeLibraryDirectories =
135                splitPaths(System.getProperty("java.library.path"), true);
136        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
137        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
138
139        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,
140                                                          suppressedExceptions);
141
142        if (suppressedExceptions.size() > 0) {
143            this.dexElementsSuppressedExceptions =
144                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
145        } else {
146            dexElementsSuppressedExceptions = null;
147        }
148    }
149


前面是一些对于传入参数的验证,然后调用了makeDexElements。


private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
                                             ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
        for (File file : files) {
            File zip = null;
            DexFile dex = null;
            String name = file.getName();

            if (name.endsWith(DEX_SUFFIX)) {               //dex文件处理
                // 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)) {   //apk,jar,zip文件处理
                zip = file;

                try {
                    dex = loadDexFile(file, optimizedDirectory);
                } catch (IOException suppressed) {
                    suppressedExceptions.add(suppressed);
                }
            } else if (file.isDirectory()) {
                elements.add(new Element(file, true, null, null));
            } else {
                System.logW(“Unknown file type for: ” + file);
            }

            if ((zip != null) || (dex != null)) {
                elements.add(new Element(file, false, zip, dex));
            }
        }

        return elements.toArray(new Element[elements.size()]);
    }
}


不管是dex文件,还是apk文件最终加载的都是loadDexFile,跟进这个函数:

如果optimizedDirectory为null就会调用openDexFile(fileName, null, 0);加载文件。

否则调用DexFile.loadDex(file.getPath(), optimizedPath, 0);

而这个函数也只是直接调用new DexFile(sourcePathName, outputPathName, flags);

里面调用的也是openDexFile(sourceName, outputName, flags);

所以最后都是调用openDexFile,跟进这个函数:
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);
        }
}

private static int openDexFile(String sourceName, String outputName,
        int flags) throws IOException {
        return openDexFileNative(new File(sourceName).getCanonicalPath(),
                                 (outputName == null) ? null : new File(outputName).getCanonicalPath(),
                                 flags);
}


而这个函数调用的是so的openDexFileNative这个函数。打开成功则返回一个cookie。


接下来就是分析native函数的实现部分了。


———-openDexFileNative———-

static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,JValue* pResult)
{
    ……………
if (hasDexExtension(sourceName)
            && dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
        ALOGV(“Opening DEX file ‘%s’ (DEX)”, sourceName);

        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
        pDexOrJar->isDex = true;
        pDexOrJar->pRawDexFile = pRawDexFile;
        pDexOrJar->pDexMemory = NULL;
    } else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
        ALOGV(“Opening DEX file ‘%s’ (Jar)”, sourceName);

        pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
        pDexOrJar->isDex = false;
        pDexOrJar->pJarFile = pJarFile;
        pDexOrJar->pDexMemory = NULL;
    } else {
        ALOGV(“Unable to open DEX file ‘%s’”, sourceName);
        dvmThrowIOException(“unable to open DEX file”);
    }
    ……………
}


这里会根据是否为dex文件或者包含classes.dex文件的jar,分别调用函数dvmRawDexFileOpen和dvmJarFileOpen来处理,最终返回一个DexOrJar的结构。


首先来看dvmRawDexFileOpen函数的处理:


1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51


52


53


54


55


56


57


58


59


60


61


62


63


64


65


66


67


68


69


70


71


72


73


74


75


76


77


78


79


80


81


82


83


84


85


86


87


88


89


90


91


92


93


94


95


96


97


98


99


100


101


102


103


104




int

dvmRawDexFileOpen

(


const


char


*

fileName,

const


char


*

odexOutputName,


RawDexFile

**

ppRawDexFile, bool isBootstrap

)




{




……………..



dexFd


=

open

(

fileName, O_RDONLY

)


;




if


(

dexFd

<


0


)


goto

bail

;






/* If we fork/exec into dexopt, don’t let it inherit the open fd. */



dvmSetCloseOnExec

(

dexFd

)


;






//校验前8个字节的magic是否正确,然后把校验和保存到adler32




if


(

verifyMagicAndGetAdler32

(

dexFd,

&

adler32

)


<


0


)


{




ALOGE

(


“Error with header for %s”

, fileName

)


;




goto

bail

;




}




//得到文件修改时间以及文件大小




if


(

getModTimeAndSize

(

dexFd,

&

modTime,

&

fileSize

)


<


0


)


{




ALOGE

(


“Error with stat for %s”

, fileName

)


;




goto

bail

;




}



……………..



//调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回



optFd

=

dvmOpenCachedDexFile

(

fileName, cachedName, modTime,


adler32, isBootstrap,

&

newFile,

/*createIfMissing=*/


true


)


;






if


(

optFd

<


0


)


{




ALOGI

(


“Unable to open or create cache for %s (%s)”

,


fileName, cachedName

)


;




goto

bail

;




}



locked

=


true


;






//如果成功生了opt头




if


(

newFile

)


{




u8 startWhen, copyWhen, endWhen

;



bool result

;



off_t dexOffset

;





dexOffset

=

lseek

(

optFd,

0

, SEEK_CUR

)


;



result

=


(

dexOffset

>


0


)


;






if


(

result

)


{




startWhen

=

dvmGetRelativeTimeUsec

(


)


;




// 将dex文件中的内容写入文件的当前位置,也就是从dexOffset的偏移处开始写



result

=

copyFileToFile

(

optFd, dexFd, fileSize

)


==


0


;



copyWhen

=

dvmGetRelativeTimeUsec

(


)


;




}






if


(

result

)


{





//对dex文件进行优化



result

=

dvmOptimizeDexFile

(

optFd, dexOffset, fileSize,


fileName, modTime, adler32, isBootstrap

)


;




}






if


(


!

result

)


{




ALOGE

(


“Unable to extract+optimize DEX from ‘%s'”

, fileName

)


;




goto

bail

;




}





endWhen

=

dvmGetRelativeTimeUsec

(


)


;



ALOGD

(


“DEX prep ‘%s’: copy in %dms, rewrite %dms”

,


fileName,



(


int


)


(

copyWhen



startWhen

)


/


1000

,



(


int


)


(

endWhen



copyWhen

)


/


1000


)


;




}






//dvmDexFileOpenFromFd这个函数最主要在这里干了两件事情




// 1.将优化后得dex文件(也就是odex文件)通过mmap映射到内存中,并通过mprotect修改它的映射内存为只读权限




// 2.将映射为只读的这块dex数据中的内容全部提取到DexFile这个数据结构中去




if


(

dvmDexFileOpenFromFd

(

optFd,

&

pDvmDex

)


!=


0


)


{




ALOGI

(


“Unable to map cached %s”

, fileName

)


;




goto

bail

;




}






if


(

locked

)


{





/* unlock the fd */




if


(


!

dvmUnlockCachedDexFile

(

optFd

)


)


{





/* uh oh — this process needs to exit or we’ll wedge the system */



ALOGE

(


“Unable to unlock DEX file”


)


;




goto

bail

;




}



locked

=


false


;




}





ALOGV

(


“Successfully opened ‘%s'”

, fileName

)


;




//填充结构体 RawDexFile




*

ppRawDexFile

=


(

RawDexFile

*


)

calloc

(


1

, sizeof

(

RawDexFile

)


)


;




(


*

ppRawDexFile

)


->

cacheFileName

=

cachedName

;




(


*

ppRawDexFile

)


->

pDvmDex

=

pDvmDex

;



cachedName

=


NULL


;


// don’t free it below



result

=


0


;





bail

:



free

(

cachedName

)


;




if


(

dexFd

>=


0


)


{




close

(

dexFd

)


;




}




if


(

optFd

>=


0


)


{





if


(

locked

)




(


void


)

dvmUnlockCachedDexFile

(

optFd

)


;



close

(

optFd

)


;




}




return

result

;




}


最后成功的话,填充RawDexFile。


dvmJarFileOpen的代码处理也是差不多的。


1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29


30


31


32


33


34


35


36


37


38


39


40


41


42


43


44


45


46


47


48


49


50


51


52


53


54


55


56


57


58


59


60


61


62


63


64


65


66


67


68


69


70


71


72


73


74


75


76


77




int

dvmJarFileOpen

(


const


char


*

fileName,

const


char


*

odexOutputName,




JarFile



**

ppJarFile, bool isBootstrap

)




{














//调用函数dexZipOpenArchive来打开zip文件,并缓存到系统内存里




if


(

dexZipOpenArchive

(

fileName,

&

archive

)


!=


0


)




goto

bail

;



archiveOpen

=


true


;







//这行代码设置当执行完成后,关闭这个文件句柄



dvmSetCloseOnExec

(

dexZipGetArchiveFd

(


&

archive

)


)


;







//优先处理已经优化了的Dex文件



fd

=

openAlternateSuffix

(

fileName,

“odex”

, O_RDONLY,

&

cachedName

)


;







//从压缩包里找到Dex文件,然后打开这个文件



entry

=

dexZipFindEntry

(


&

archive, kDexInJarName

)


;







//把未经过优化的Dex文件进行优化处理,并输出到指定的文件




if


(

odexOutputName

==


NULL


)


{




cachedName

=

dexOptGenerateCacheFileName

(

fileName,


kDexInJarName

)


;




}







//创建缓存的优化文件



fd

=

dvmOpenCachedDexFile

(

fileName, cachedName,


dexGetZipEntryModTime

(


&

archive, entry

)

,


dexGetZipEntryCrc32

(


&

archive, entry

)

,


isBootstrap,

&

newFile,

/*createIfMissing=*/


true


)


;







//调用函数dexZipExtractEntryToFile从压缩包里解压文件出来




if


(

result

)


{




startWhen

=

dvmGetRelativeTimeUsec

(


)


;



result

=

dexZipExtractEntryToFile

(


&

archive, entry, fd

)


==


0


;



extractWhen

=

dvmGetRelativeTimeUsec

(


)


;




}







//调用函数dvmOptimizeDexFile对Dex文件进行优化处理




if


(

result

)


{




result

=

dvmOptimizeDexFile

(

fd, dexOffset,


dexGetZipEntryUncompLen

(


&

archive, entry

)

,


fileName,


dexGetZipEntryModTime

(


&

archive, entry

)

,


dexGetZipEntryCrc32

(


&

archive, entry

)

,


isBootstrap

)


;




}







//调用函数dvmDexFileOpenFromFd来缓存dex文件




//并分析文件的内容。比如标记是否优化的文件,通过签名检查Dex文件是否合法




if


(

dvmDexFileOpenFromFd

(

fd,

&

pDvmDex

)


!=


0


)


{




ALOGI

(


“Unable to map %s in %s”

, kDexInJarName, fileName

)


;




goto

bail

;




}







//保存文件到缓存里,标记这个文件句柄已经保存到缓存




if


(

locked

)


{





/* unlock the fd */




if


(


!

dvmUnlockCachedDexFile

(

fd

)


)


{





/* uh oh — this process needs to exit or we’ll wedge the system */



ALOGE

(


“Unable to unlock DEX file”


)


;




goto

bail

;




}



locked

=


false


;




}







//设置一些相关信息返回前面的函数处理。




*

ppJarFile

=


(



JarFile



*


)

calloc

(


1

, sizeof

(



JarFile



)


)


;




(


*

ppJarFile

)


->

archive

=

archive

;




(


*

ppJarFile

)


->

cacheFileName

=

cachedName

;




(


*

ppJarFile

)


->

pDvmDex

=

pDvmDex

;



cachedName

=


NULL


;


// don’t free it below



result

=


0


;









}


最后成功的话,填充JarFile。




版权声明:本文为nanzhiwen666原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。