DexClassLoader动态加载分析

转载自:http://www.blogfshare.com/dexclassloader.html

看到原来有把原始的dex文件加密保存,然后解密后使用DexClassLoader加载文件的方法,就来分析下DexClassLoader的加载流程:

源码地址:http://androidxref.com/4.4_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

该class加载器是加载包含classes.dex文件的jar文件或者apk文件,需要一个应用私有的,可写的目录去缓存优化的classes。可以用使用File dexoutputDir = context.getDir(“dex”,0);创建一个这样的目录,不要使用外部缓存,以保护你的应用被代码注入。

构造方法如下:

public class DexClassLoader extends BaseDexClassLoader {
    下面这段注释详细地说明了这个构造函数中各个参数地意义,不作阐述了,希望大家能够认真阅读,思考;

public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

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函数:

public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
    ………..    
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);
    ………..
}

前面是一些对于传入参数的验证,然后调用了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,跟进这个函数:

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

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

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

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

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

所以最后都是调用openDexFile,跟进这个函数:

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———-

代码地址:http://androidxref.com/4.4_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp

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。

 

参考文章:

http://bbs.pediy.com/showthread.php?t=199230

http://0nly3nd.sinaapp.com/?p=688

http://blog.csdn.net/roland_sun/article/details/47183119

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值