简述:这篇文章仅仅介绍atlas的基本实现原理。
这是atals中bundle加载过程包括class 和 resource
第15步:
if(!installingBundles.containsKey(location) && ((bundleDir!=null&&bundleDir.exists()) || dexPatchDir!=null&&dexPatchDir.exists())){
try {
BundleContext bcontext = new BundleContext();
bcontext.bundle_tag = bundleUniqueTag;
bcontext.location = location;
bcontext.bundleDir = bundleDir;
bcontext.dexPatchDir = dexPatchDir;
bundle = new BundleImpl(bcontext);
if (bundle != null) {
bundle.optDexFile();
}
} catch (Exception e) {
if (e instanceof BundleArchive.MisMatchException) {
if (bundleDir.exists()) {
bundle = null;
}
}
可以看到bundle.optDexFile() 点进去看看
public /*synchronized*/ void optDexFile() {
//getArchive()获取bundle压缩文件的描述返回的是BundleArchive对象
//调用BundleArchive的optDexFile()方法
this.getArchive().optDexFile();
}
接着点
public void optDexFile() {
//BundleArchive 对象仅仅是对 BundleArchiveRevision的一层封装
//BundleArchiveRevision才是对Bundle的so文件的真正描述
//currentRevision 是BundleArchive 的实例指当前Bundle压缩文件对象
currentRevision.optDexFile();
}
接着点
public /*synchronized*/ void optDexFile() {
if (isDexOpted()){
return;
}
optDexFileLocked();
}
接着点入optDexFileLocked()
关键代码 Build.VERSION.SDK_INT>=21
dexFile = (DexFile) RuntimeVariables.sDexLoadBooster.getClass().getDeclaredMethod("loadDex",Context.class,String.class, String.class, int.class, boolean.class).invoke(
RuntimeVariables.sDexLoadBooster,
RuntimeVariables.androidApplication,
bundleFile.getAbsolutePath(),
odexFile.getAbsolutePath(),
0,
interpretOnly);
RuntimeVariables.sDexLoadBooster 是DexLoadBooster() 对象,是在AtlasBridgeApplication 中初始化,在BridgeApplicationDelegate 中赋值给RuntimeVariables.sDexLoadBooster 的。
所以这段反射代码最终执行的方法是DexLoadBooster()的loadDex 方法 代码如下:
public DexFile loadDex(Context context, String sourcePathName, String outputPathName, int flags,
boolean interpretOnly) throws IOException {
return AndroidRuntime.getInstance().loadDex(context, sourcePathName, outputPathName, flags, interpretOnly);
}
接着点
DexFile.loadDex(sourcePathName, outputPathName, flags)
这就是Android代码加载Dex文件的代码了
现在已经将dex文件加载到内存中封装成对象了,接着查BundleClassLoader的代码,这个流程是class文件的加载过程
clazz = ((BundleClassLoader)impl.getClassLoader()).loadOwnClass(classname);
public Class> loadOwnClass(String className) throws ClassNotFoundException {
Class> clazz = findLoadedClass(className);
if (clazz == null) {
clazz = findOwnClass(className);
}
return clazz;
}
private Class> findOwnClass(final String classname) {
try {
Class> clazz = archive.findClass(classname, this);
return clazz;
} catch (Exception e) {
if (e instanceof BundleArchiveRevision.DexLoadException) {
throw (BundleArchiveRevision.DexLoadException)e;
}
}
return null;
}
public Class> findClass(String className, ClassLoader cl) throws ClassNotFoundException {
return currentRevision.findClass(className, cl);
}
clazz = dexFile.loadClass(className, cl);
到这里我们的上面拿到的DexFile对象用上了,并且调用了loadClass方法
查看loadClass方法
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader);
}
点入:
public Class loadClassBinaryName(String name, ClassLoader loader) {
return defineClass(name, loader, mCookie);
}
点入:
private native static Class defineClass(String name, ClassLoader loader, int cookie);
发现这是一个native方法,到此结束
总结:Atlas 通过反射为每个Bundle创建了一个ClassLoader ,将so文件中的dex文件加载到内存中以后最终调用defineClass方法加载class文件。
附属DexFile源码:
package dalvik.system;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
/**
* Manipulates DEX files. The class is similar in principle to
* {@link java.util.zip.ZipFile}. It is used primarily by class loaders.
*
* Note we don't directly open and read the DEX file here. They're memory-mapped
* read-only by the VM.
*/
public final class DexFile {
private int mCookie;
private final String mFileName;
private final CloseGuard guard = CloseGuard.get();
/**
* Opens a DEX file from a given File object. This will usually be a ZIP/JAR
* file with a "classes.dex" inside.
*
* The VM will generate the name of the corresponding file in
* /data/dalvik-cache and open it, possibly creating or updating
* it first if system permissions allow. Don't pass in the name of
* a file in /data/dalvik-cache, as the named file is expected to be
* in its original (pre-dexopt) state.
*
* @param file
* the File object referencing the actual DEX file
*
* @throws IOException
* if an I/O error occurs, such as the file not being found or
* access rights missing for opening it
*/
public DexFile(File file) throws IOException {
this(file.getPath());
}
/**
* Opens a DEX file from a given filename. This will usually be a ZIP/JAR
* file with a "classes.dex" inside.
*
* The VM will generate the name of the corresponding file in
* /data/dalvik-cache and open it, possibly creating or updating
* it first if system permissions allow. Don't pass in the name of
* a file in /data/dalvik-cache, as the named file is expected to be
* in its original (pre-dexopt) state.
*
* @param fileName
* the filename of the DEX file
*
* @throws IOException
* if an I/O error occurs, such as the file not being found or
* access rights missing for opening it
*/
public DexFile(String fileName) throws IOException {
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
/**
* Opens a DEX file from a given filename, using a specified file
* to hold the optimized data.
*
* @param sourceName
* Jar or APK file with "classes.dex".
* @param outputName
* File that will hold the optimized form of the DEX data.
* @param flags
* Enable optional features.
*/
private DexFile(String sourceName, String outputName, int flags) throws IOException {
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie);
}
/**
* Open a DEX file, specifying the file in which the optimized DEX
* data should be written. If the optimized form exists and appears
* to be current, it will be used; if not, the VM will attempt to
* regenerate it.
*
* This is intended for use by applications that wish to download
* and execute DEX files outside the usual application installation
* mechanism. This function should not be called directly by an
* application; instead, use a class loader such as
* dalvik.system.DexClassLoader.
*
* @param sourcePathName
* Jar or APK file with "classes.dex". (May expand this to include
* "raw DEX" in the future.)
* @param outputPathName
* File that will hold the optimized form of the DEX data.
* @param flags
* Enable optional features. (Currently none defined.)
* @return
* A new or previously-opened DexFile.
* @throws IOException
* If unable to open the source or output file.
*/
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
/*
* TODO: we may want to cache previously-opened DexFile objects.
* The cache would be synchronized with close(). This would help
* us avoid mapping the same DEX more than once when an app
* decided to open it multiple times. In practice this may not
* be a real issue.
*/
return new DexFile(sourcePathName, outputPathName, flags);
}
/**
* Gets the name of the (already opened) DEX file.
*
* @return the file name
*/
public String getName() {
return mFileName;
}
/**
* Closes the DEX file.
*
* This may not be able to release any resources. If classes from this
* DEX file are still resident, the DEX file can't be unmapped.
*
* @throws IOException
* if an I/O error occurs during closing the file, which
* normally should not happen
*/
public void close() throws IOException {
guard.close();
closeDexFile(mCookie);
mCookie = 0;
}
/**
* Loads a class. Returns the class on success, or a {@code null} reference
* on failure.
*
* If you are not calling this from a class loader, this is most likely not
* going to do what you want. Use {@link Class#forName(String)} instead.
*
* The method does not throw {@link ClassNotFoundException} if the class
* isn't found because it isn't reasonable to throw exceptions wildly every
* time a class is not found in the first DEX file we look at.
*
* @param name
* the class name, which should look like "java/lang/String"
*
* @param loader
* the class loader that tries to load the class (in most cases
* the caller of the method
*
* @return the {@link Class} object representing the class, or {@code null}
* if the class cannot be loaded
*/
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader);
}
/**
* See {@link #loadClass(String, ClassLoader)}.
*
* This takes a "binary" class name to better match ClassLoader semantics.
*
* @hide
*/
public Class loadClassBinaryName(String name, ClassLoader loader) {
return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);
/**
* Enumerate the names of the classes in this DEX file.
*
* @return an enumeration of names of classes contained in the DEX file, in
* the usual internal form (like "java/lang/String").
*/
public Enumeration entries() {
return new DFEnum(this);
}
/*
* Helper class.
*/
private class DFEnum implements Enumeration<String> {
private int mIndex;
private String[] mNameList;
DFEnum(DexFile df) {
mIndex = 0;
mNameList = getClassNameList(mCookie);
}
public boolean hasMoreElements() {
return (mIndex < mNameList.length);
}
public String nextElement() {
return mNameList[mIndex++];
}
}
/* return a String array with class names */
native private static String[] getClassNameList(int cookie);
/**
* Called when the class is finalized. Makes sure the DEX file is closed.
*
* @throws IOException
* if an I/O error occurs during closing the file, which
* normally should not happen
*/
@Override protected void finalize() throws Throwable {
try {
if (guard != null) {
guard.warnIfOpen();
}
close();
} finally {
super.finalize();
}
}
/*
* Open a DEX file. The value returned is a magic VM cookie. On
* failure, an IOException is thrown.
*/
native private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException;
/*
* Open a DEX file based on a {@code byte[]}. The value returned
* is a magic VM cookie. On failure, a RuntimeException is thrown.
*/
native private static int openDexFile(byte[] fileContents);
/*
* Close DEX file.
*/
native private static void closeDexFile(int cookie);
/**
* Returns true if the VM believes that the apk/jar file is out of date
* and should be passed through "dexopt" again.
*
* @param fileName the absolute path to the apk/jar file to examine.
* @return true if dexopt should be called on the file, false otherwise.
* @throws java.io.FileNotFoundException if fileName is not readable,
* not a file, or not present.
* @throws java.io.IOException if fileName is not a valid apk/jar file or
* if problems occur while parsing it.
* @throws java.lang.NullPointerException if fileName is null.
* @throws dalvik.system.StaleDexCacheError if the optimized dex file
* is stale but exists on a read-only partition.
*/
native public static boolean isDexOptNeeded(String fileName)
throws FileNotFoundException, IOException;
}