腾讯Android面试:系统如何加载一个dex文件,他的底层原理是怎么实现的(1)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

本系列文章专注分享大型Bat面试知识,后续会持续更新,喜欢的话麻烦点击一个关注

面试官: 系统如何加载一个dex文件,他的底层原理是怎么实现的

心理分析:面试官想知道你是否有过对dex加载相关经验。此题主要为tinker热修复做铺垫。dex加载与热修复是有关系的,求职者一定要注意 面试官后续会面试到tinker

**求职者:**应该从DexClassLoader 加载出发

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

其源码如下:

public classDexClassLoaderextendsBaseDexClassLoader {
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);
}

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

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 suppressedExceptions = new ArrayList();
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 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 }

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

private static Element[] makeDexElements(ArrayList files, File optimizedDirectory,
ArrayList suppressedExceptions) {
ArrayList elements = new ArrayList();
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函数的处理:

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

推荐学习资料

  • Android进阶学习全套手册

  • Android对标阿里P7学习视频

  • BAT TMD大厂Android高频面试题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

图片转存中…(img-Y1pkcAFb-1713357482466)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-wEftp49s-1713357482466)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值