先說(shuō)結(jié)論
1仲翎、DexClassLoader可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk
2拉岁、PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過(guò)的apk
PathClassLoader 源碼
以下源碼全部來(lái)自Android6.0.1
package dalvik.system;
public class PathClassLoader extends BaseDexClassLoader {
/** 有興趣的可以看看注釋坷剧,故意沒(méi)刪
* Creates a {@code PathClassLoader} that operates on a given list of files
* and directories. This method is equivalent to calling
* {@link #PathClassLoader(String, String, ClassLoader)} with a
* {@code null} value for the second argument (see description there).
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
/**
* Creates a {@code PathClassLoader} that operates on two given
* lists of files and directories. The entries of the first list
* should be one of the following:
JAR/ZIP/APK files, possibly containing a "classes.dex" file as
* well as arbitrary resources.
*
Raw ".dex" files (not inside a zip file).
*
*
* The entries of the second list should be directories containing
* native library files.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
}
DexClassLoader 源碼
package dalvik.system;
import java.io.File;
/**
* A class loader that loads classes from {@code .jar} and {@code .apk} files
* containing a {@code classes.dex} entry. This can be used to execute code not
* installed as part of an application.
* * <p>This class loader requires an application-private, writable directory to
* cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
* such a directory: <pre> {@code
* File dexOutputDir = context.getCodeCacheDir();
* }</pre>
* * <p><strong>Do not cache optimized classes on external storage.</strong>
* External storage does not provide access controls necessary to protect your
* application from code injection attacks.
*/
public class DexClassLoader extends BaseDexClassLoader {
/**
* Creates a {@code DexClassLoader} that finds interpreted and native
* code. Interpreted classes are found in a set of DEX files contained
* in Jar or APK files.
*
* <p>The path lists are separated using the character specified by the
* {@code path.separator} system property, which defaults to {@code :}.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; must not be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}
父類BaseDexClassLoader源碼
/**
* Base class for common functionality between various dex-based
* {@link ClassLoader} implementations.
*/
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
/**
* Constructs an instance.
*
* @param dexPath the list of jar/apk files containing classes and
* resources, delimited by {@code File.pathSeparator}, which
* defaults to {@code ":"} on Android
* @param optimizedDirectory directory where optimized dex files
* should be written; may be {@code null}
* @param libraryPath the list of directories containing native
* libraries, delimited by {@code File.pathSeparator}; may be
* {@code null}
* @param parent the parent class loader
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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;
}
@Override
protected URL findResource(String name) {
return pathList.findResource(name);
}
@Override
protected Enumeration<URL> findResources(String name) {
return pathList.findResources(name);
}
@Override
public String findLibrary(String name) {
return pathList.findLibrary(name);
}
/**
* Returns package information for the given package.
* Unfortunately, instances of this class don't really have this
* information, and as a non-secure {@code ClassLoader}, it isn't
* even required to, according to the spec. Yet, we want to
* provide it, in order to make all those hopeful callers of
* {@code myClass.getPackage().getName()} happy. Thus we construct
* a {@code Package} object the first time it is being requested
* and fill most of the fields with dummy values. The {@code
* Package} object is then put into the {@code ClassLoader}'s
* package cache, so we see the same one next time. We don't
* create {@code Package} objects for {@code null} arguments or
* for the default package.
*
* <p>There is a limited chance that we end up with multiple
* {@code Package} objects representing the same package: It can
* happen when when a package is scattered across different JAR
* files which were loaded by different {@code ClassLoader}
* instances. This is rather unlikely, and given that this whole
* thing is more or less a workaround, probably not worth the
* effort to address.
*
* @param name the name of the class
* @return the package information for the class, or {@code null}
* if there is no package information available for it
*/
@Override
protected synchronized Package getPackage(String name) {
if (name != null && !name.isEmpty()) {
Package pack = super.getPackage(name);
if (pack == null) {
pack = definePackage(name, "Unknown", "0.0", "Unknown",
"Unknown", "0.0", "Unknown", null);
}
return pack;
}
return null;
}
/**
* @hide
*/
public String getLdLibraryPath() {
StringBuilder result = new StringBuilder();
for (File directory : pathList.getNativeLibraryDirectories()) {
if (result.length() > 0) {
result.append(':');
}
result.append(directory);
}
return result.toString();
}
@Override public String toString() {
return getClass().getName() + "[" + pathList + "]";
}
}
原因
DexClassLoader構(gòu)造函數(shù)
//dexPath :dex路徑
//optimizedDirectory :制定輸出dex優(yōu)化后的odex文件惰爬,可以為null
//libraryPath:動(dòng)態(tài)庫(kù)路徑(將被添加到app動(dòng)態(tài)庫(kù)搜索路徑列表中)
//parent:制定父類加載器喊暖,以保證雙親委派機(jī)制從而實(shí)現(xiàn)每個(gè)類只加載一次。
public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
PathClassLoader構(gòu)造函數(shù)
//dexPath :dex路徑
//optimizedDirectory :制定輸出dex優(yōu)化后的odex文件撕瞧,可以為null
//libraryPath:動(dòng)態(tài)庫(kù)路徑(將被添加到app動(dòng)態(tài)庫(kù)搜索路徑列表中)
//parent:制定父類加載器陵叽,以保證雙親委派機(jī)制從而實(shí)現(xiàn)每個(gè)類只加載一次。
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String libraryPath,
ClassLoader parent) {
super(dexPath, null, libraryPath, parent);
}
DexClassLoader 與 PathClassLoader 構(gòu)造函數(shù)區(qū)別就是多了個(gè)optimizedDirectory參數(shù)丛版。
繼續(xù)查看父類構(gòu)造函數(shù):
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath,optimizedDirectory);
}
和關(guān)鍵方法findClass
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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;
}
Class c = pathList.findClass(name, suppressedExceptions);
中的pathList是在構(gòu)造函數(shù)中new出來(lái)的巩掺,繼續(xù)看DexPathList的findClass方法:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
DexPathList會(huì)執(zhí)行findClass時(shí),會(huì)先將所有的dex包進(jìn)行遍歷页畦,從每個(gè)包查找相對(duì)應(yīng)name的class胖替,只要找到,馬上返回豫缨。
這里有關(guān)于熱修復(fù)實(shí)現(xiàn)的知識(shí)點(diǎn)独令,就是將補(bǔ)丁 dex 文件放到 dexElements 數(shù)組靠前位置,這樣在加載 class 時(shí)好芭,優(yōu)先找到補(bǔ)丁包中的 dex 文件燃箭,加載到 class 之后就不再尋找,從而原來(lái)的 apk 文件中同名的類就不會(huì)再使用舍败,從而達(dá)到修復(fù)的目的
至于這個(gè)dexElements怎么創(chuàng)造出來(lái)的呢招狸,在DexPathList構(gòu)造方法里面創(chuàng)造的:
public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
//.....
//省略其他不重要代碼
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
//.....
//省略部分代碼
}
繼續(xù)看 makePathElements 方法:
private static Element[] makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions) {
List<Element> elements = new ArrayList<>();
for (File file : files) {
File zip = null;
File dir = new File("");
DexFile dex = null;
String path = file.getPath();
String name = file.getName();
if (path.contains(zipSeparator)) {
String split[] = path.split(zipSeparator, 2);
zip = new File(split[0]);
dir = new File(split[1]);
} else if (file.isDirectory()) {
// We support directories for looking up resources and native libraries.
// Looking up resources in directories is useful for running libcore tests.
elements.add(new Element(file, true, null, null));
} else if (file.isFile()) {
if (name.endsWith(DEX_SUFFIX)) {
// 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 {
zip = file;
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
}
} else {
System.logW("ClassLoader referenced unknown path: " + file);
}
if ((zip != null) || (dex != null)) {
elements.add(new Element(dir, false, zip, dex));
}
}
return elements.toArray(new Element[elements.size()]);
}
通過(guò)遍歷files(就是傳入的dexPath)敬拓,執(zhí)行l(wèi)oadDexFile轉(zhuǎn)變?yōu)閐ex,然后添加到elements中裙戏。
到這里乘凸,依然沒(méi)有看出有和無(wú)optimizedDirectory這個(gè)參數(shù)的區(qū)別,那就繼續(xù)看loadDexFile這個(gè)方法:
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);
}
}
尼瑪累榜,終于找到區(qū)別的了...
1. optimizedDirectory如果為null翰意,loadDexFile返回的是new DexFile(file)
2. optimizedDirectory 不為null,返回的是DexFile.loadDex(file.getPath(), optimizedPath, 0)
繼續(xù)查DexFile信柿,看這兩個(gè)的區(qū)別,先是new DexFile(File):
public DexFile(File file) throws IOException {
this(file.getPath());
}
public DexFile(String fileName) throws IOException {
mCookie = openDexFile(fileName, null, 0);
mFileName = fileName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
}
private DexFile(String sourceName, String outputName, int flags) throws IOException {
if (outputName != null) {
try {
String parent = new File(outputName).getParent();
if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
throw new IllegalArgumentException("Optimized data directory " + parent
+ " is not owned by the current user. Shared storage cannot protect"
+ " your application from code injection attacks.");
}
} catch (ErrnoException ignored) {
// assume we'll fail with a more contextual error later
}
}
mCookie = openDexFile(sourceName, outputName, flags);
mFileName = sourceName;
guard.open("close");
//System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
}
最終指向了DexFile(String sourceName, String outputName, int flags)
這個(gè)方法冀偶。
繼續(xù)看上面的DexFile.loadDex(file.getPath(), optimizedPath, 0)
方法:
static public DexFile loadDex(String sourcePathName, String outputPathName,
int flags) throws IOException {
return new DexFile(sourcePathName, outputPathName, flags);
}
同樣指向的是new DexFile(sourcePathName, outputPathName, flags);
上面兩者區(qū)別是,outputPathName參數(shù)為不為空渔嚷。
好了进鸠,現(xiàn)在PathClassLoader 和 DexClassLoader的區(qū)別就變成了看這個(gè)outputPathName參數(shù)為不為空的區(qū)別了。
而outputPathName為不為空形病,結(jié)果會(huì)有什么影響呢客年?繼續(xù)看DexFile中構(gòu)造函數(shù)openDexFile的源碼:
private static Object openDexFile(String sourceName, String outputName, int flags) throws IOException {
return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags);
}
openDexFileNative是個(gè)Native方法,在源碼art/runtime/native/dalvik_system_DexFile.cc中找到(不要問(wèn)我怎么找到的漠吻,我也是看別人才找到的):
static jobject DexFile_openDexFileNative(
JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
ScopedUtfChars sourceName(env, javaSourceName);
ScopedUtfChars sourceName(env, javaSourceName);
if (sourceName.c_str() == nullptr) {
return 0;
}
NullableScopedUtfChars outputName(env, javaOutputName);
if (env->ExceptionCheck()) {
return 0;
}
ClassLinker* linker = Runtime::Current()->GetClassLinker();
std::vector<std::unique_ptr<const DexFile>> dex_files;
std::vector<std::string> error_msgs;
dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
//省略部分代碼
//......
}
}
代碼太多我只列出了追蹤javaOutputName這個(gè)參數(shù)的關(guān)鍵代碼量瓜,javaOutputName被轉(zhuǎn)換成了NullableScopedUtfChars類型的 outputName
NullableScopedUtfChars outputName(env, javaOutputName);
然后這個(gè)outputName被linker的對(duì)象執(zhí)行了OpenDexFilesFromOat方法使用:
ClassLinker* linker = Runtime::Current()->GetClassLinker();
//省略部分代碼
//.....
dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
有興趣的可以在art\runtime\class_linker.cc文件中查找OpenDexFilesFromOat方法:
std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat(
const char* dex_location, const char* oat_location,
std::vector<std::string>* error_msgs) {
//代碼太多,只列出部分....
//注意這里傳入了oat_location途乃,生成了OatFileAssistant 對(duì)象
OatFileAssistant oat_file_assistant(dex_location, oat_location, kRuntimeISA,
!Runtime::Current()->IsAotCompiler());
//省略部分代碼...
// If we didn't have an up-to-date oat file open, try to load one from disk.
//source_oat_file就是從已經(jīng)加載過(guò)的ota文件中查找到的目標(biāo)dex文件
if (source_oat_file == nullptr) {
// Update the oat file on disk if we can. This may fail, but that's okay.
// Best effort is all that matters here.
//source_oat_file為空的話绍傲,會(huì)通過(guò)oat_file_assistant.MakeUpToDate(&error_msg)方法在其他路徑嘗試加載
if (!oat_file_assistant.MakeUpToDate(&error_msg)) {
LOG(WARNING) << error_msg;
}
// Get the oat file on disk.
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
if (oat_file.get() != nullptr) {
// Take the file only if it has no collisions, or we must take it because of preopting.
bool accept_oat_file = !HasCollisions(oat_file.get(), &error_msg);
//省略部分代碼
// Fall back to running out of the original dex file if we couldn't load any
// dex_files from the oat file.
if (dex_files.empty()) {
if (oat_file_assistant.HasOriginalDexFiles()) {
if (Runtime::Current()->IsDexFileFallbackEnabled()) {
if (!DexFile::Open(dex_location, dex_location, &error_msg, &dex_files)) {
LOG(WARNING) << error_msg;
error_msgs->push_back("Failed to open dex files from " + std::string(dex_location));
}
} else {
error_msgs->push_back("Fallback mode disabled, skipping dex files.");
}
} else {
error_msgs->push_back("No original dex files found for dex location "
+ std::string(dex_location));
}
}
return dex_files;
}
主要看核心代碼oat_file_assistant.MakeUpToDate
,在oat_file_assistant.cc這個(gè)文件里:
bool OatFileAssistant::MakeUpToDate(std::string* error_msg) {
switch (GetDexOptNeeded()) {
case kNoDexOptNeeded: return true;
case kDex2OatNeeded: return GenerateOatFile(error_msg);
case kPatchOatNeeded: return RelocateOatFile(OdexFileName(), error_msg);
case kSelfPatchOatNeeded: return RelocateOatFile(OatFileName(), error_msg);
}
UNREACHABLE();
}
網(wǎng)上很多分析都不是基于Android6.0耍共,外加因?yàn)閏不太熟烫饼,這個(gè)方法分析理解如下(說(shuō)的不對(duì)請(qǐng)指出啊):
case kNoDexOptNeeded: return true;
是指已經(jīng)在中加載過(guò)的dex文件试读,只要apk安裝過(guò)杠纵,運(yùn)行過(guò),都可以直接可以找到钩骇,不需要再加載了比藻;
case kDex2OatNeeded: return GenerateOatFile(error_msg);
內(nèi)存中沒(méi)有的,就進(jìn)行第一次加載dex倘屹,轉(zhuǎn)換成oat文件银亲。這個(gè)時(shí)候,如果之前傳入的outputName為空的話唐瀑,就不能加載了群凶。
下面兩個(gè)case就看不太懂了。不過(guò)文章標(biāo)題的問(wèn)題似乎已經(jīng)找到答案了哄辣。
再次總結(jié)
1请梢、DexClassLoader可以加載jar/apk/dex赠尾,可以從SD卡中加載未安裝的apk
2、PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過(guò)的apk