1.基本概念
1.1 类加载器的作用
类的加载需要类加载器完成,在JVM中,一个类的唯一性是需要这个类本身和类加载一起才能确定的,每个类加载器都有一个独立的命名空间。
不同的类加载器,即使是同一个类字节码文件,最后再JVM里的类对象也不是同一个。
1.2 双亲委派模型
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,
因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是Java类随着它的类加载器一起具备了一种带有优先级的层次关系。
例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,
因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,
并放在程序的Class Path中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。
1.3 Java与Android类加载器比较
JVM加载的是class文件,ART加载的是dex文件。
Java中四种类加载器:Bootstrap ClassLoader <- Extension ClassLoader <- Application ClassLoader<- Custom ClassLoader
Android中类加载器:BootClassLoader,BaseDexClassLoader <- PathClassLoader, DexClassLoader
2. Android类加载器
2.1 类加载器
ClassLoader:对Java的ClassLoader代码做了精简,其中loadClass()核心逻辑不变,仍采用双亲委派模型。
BootClassLoader:继承于ClassLoader,是ClassLoader的内部类。是SystemClassLoader的父加载器,同时也是Android中所有ClassLoader的最终parent。
//ClassLoader.java
private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}
//Class.java
public ClassLoader getClassLoader() {
return (classLoader == null) ? BootClassLoader.getInstance() : classLoader;
}
BaseDexClassLoader:继承于ClassLoader,是PathClassLoader和DexClassLoader的父类。
BaseDexClassLoader重写了ClassLoader的findClass()方法,核心在于维护了DexPathList这个类对象。后面会着重介绍。
//BaseDexClassLoader.java
private final DexPathList pathList;
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
...
Class c = pathList.findClass(name, suppressedExceptions);
...
return c;
}
PathClassLoader:继承于BaseDexClassLoader,用来加载系统类和apk中的类。
DexClassLoader:继承于BaseDexClassLoader,可以用来加载外置的dex文件或者apk,jar等。
在Android8.1之后,optimizedDirectory参数已废弃,默认传入null。也就是说Android8.1版本及以后,PathClassLoader和DexClassLoader没有区别。
2.2 DexPathList功能
DexPathList核心功能是维护dexElements数组,用来记录dex文件集合。在BaseDexClassLoader构造函数中将dexPath传入,在DexPathList的splitDexPath()方法解析文件路径,
在makeDexElements()方法添加到dexElements数组中。
//DexPathList.java
private Element[] dexElements;
static class Element {
private final File path;
private final Boolean pathIsDirectory;
private final DexFile dexFile;
public Class> findClass(String name, ClassLoader definingContext,List suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null;
}
}
在上文提到BaseDexClassLoader重写了ClassLoader的findClass()方法,其实现是直接调用DexPathList的findClass()方法,
其核心逻辑是遍历所有dex文件,调用DexFile的loadClassBinaryName()方法加载类。也就是说,Apk中所有类都是通过DexFile加载的。
//DexPathList.java
public Class> findClass(String name, List suppressed) {
for (Element element : dexElements) {
Class> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
return null;
}
此外,从DexPathList的findClass()方法我们可以看到,在尝试加载一个类的时候,会遍历所有dex文件,只要找到就会直接返回,不会再继续遍历dexElements。
也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面。
2.3 应用代码是如何加载的?
在代码中打印类加载器
Log.d(TAG, "ClassLoader: " + getClassLoader());
Log.d(TAG, "SystemClassLoader: " + ClassLoader.getSystemClassLoader());
输出:
ClassLoader: dalvik.system.PathClassLoader[DexPathList[[zip file "/system_ext/priv-app/MiuiSystemUI/MiuiSystemUI.apk"],nativeLibraryDirectories=[/system_ext/priv-app/MiuiSystemUI/lib/arm64, /system_ext/priv-app/MiuiSystemUI/MiuiSystemUI.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64, /system/lib64, /system_ext/lib64]]]
SystemClassLoader: dalvik.system.PathClassLoader[DexPathList[[directory "."],nativeLibraryDirectories=[/system/lib64, /system_ext/lib64, /system/lib64, /system_ext/lib64]]]
可以看到,应用类的默认ClassLoader是PathClassLoader,接下来就介绍一下这个PathClassLoader是何时创建的。
在启动应用创建进程后,会调用ActivityThread.main()方法,在main()方法中ActivityThread与AMS建立交互,
然后AMS调用ApplicationThread.bindApplication()方法,在bindApplication()方法中发送BIND_APPLICATION消息,执行handleBindApplication()方法,
//ActivityThread.java
public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
}
private void attach(boolean system, long startSeq) {
...
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
}
private class ApplicationThread extends IApplicationThread.Stub {
public final void bindApplication(String processName, ApplicationInfo appInfo,
...
sendMessage(H.BIND_APPLICATION, data);
}
}
private void handleBindApplication(AppBindData data) {
...
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
...
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
}
在handleBindApplication()方法中会调用getPackageInfoNoCheck()方法,其过程中会创建LoadedApk对象,这里需要注意的是构建LoadedApk传入的baseLoader为null,
//ActivityThread.java
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
...
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode
&& (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
...
return packageInfo;
}
}
至此,LoadedApk对象已经创建,其mBaseClassLoader为null。接下来继续makeApplication()的逻辑,
这里会调用getClassLoader()方法,此时mClassLoader为null,调用createOrUpdateClassLoaderLocked()方法,
此时mDefaultClassLoader也为null,调用ApplicationLoaders创建DefaultClassLoader,
最后初始化mClassLoader,这里mDefaultClassLoader==mClassLoader,两者是相同的。
//LoadedApk.java
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
...
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
final java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
initializeJavaContextClassLoader();
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
}
...
mApplication = app;
...
if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
}
}
return app;
}
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
}
return mClassLoader;
}
}
private void createOrUpdateClassLoaderLocked(List addedPaths) {
...
if (mDefaultClassLoader == null) {
mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoaderWithSharedLibraries(
zip, mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader,
mApplicationInfo.classLoaderName, sharedLibraries);
mAppComponentFactory = createAppFactory(mApplicationInfo, mDefaultClassLoader);
}
...
if (mClassLoader == null) {
mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
new ApplicationInfo(mApplicationInfo));
}
}
接下来看ApplicationLoaders的逻辑,在getClassLoaderWithSharedLibraries()方法中会调用getClassLoader()方法,
//ApplicationLoaders.java
private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent, String cacheKey,
String classLoaderName, List sharedLibraries) {
...
ClassLoader classloader = ClassLoaderFactory.createClassLoader(
zip, librarySearchPath, libraryPermittedPath, parent,
targetSdkVersion, isBundled, classLoaderName, sharedLibraries);
return classloader;
...
}
接着看ClassLoaderFactory的逻辑,这里classloaderName为null,因此创建的是PathClassLoader,也就是上文中日志打印的。
//ClassLoaderFactory.java
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName,
List sharedLibraries) {
ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
? null
: sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
if (isPathClassLoaderName(classloaderName)) {
return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries);
} else if (isDelegateLastClassLoaderName(classloaderName)) {
return new DelegateLastClassLoader(dexPath, librarySearchPath, parent,
arrayOfSharedLibraries);
}
throw new AssertionError("Invalid classLoaderName: " + classloaderName);
}
public static boolean isPathClassLoaderName(String name) {
return name == null || PATH_CLASS_LOADER_NAME.equals(name) ||
DEX_CLASS_LOADER_NAME.equals(name);
}
到这里你是否发现了一个惊喜?竟然还有一种类加载器,DelegateLastClassLoader,继承于PathClassLoader,其核心逻辑如下:
//DelegateLastClassLoader.java
@Override
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
// First, check whether the class has already been loaded. Return it if that's the
// case.
Class> cl = findLoadedClass(name);
if (cl != null) {
return cl;
}
// Next, check whether the class in question is present in the boot classpath.
try {
return Object.class.getClassLoader().loadClass(name);
} catch (ClassNotFoundException ignored) {
}
// Next, check whether the class in question is present in the dexPath that this classloader
// operates on, or its shared libraries.
ClassNotFoundException fromSuper = null;
try {
return findClass(name);
} catch (ClassNotFoundException ex) {
fromSuper = ex;
}
// Finally, check whether the class in question is present in the parent classloader.
try {
return getParent().loadClass(name);
} catch (ClassNotFoundException cnfe) {
// The exception we're catching here is the CNFE thrown by the parent of this
// classloader. However, we would like to throw a CNFE that provides details about
// the class path / list of dex files associated with *this* classloader, so we choose
// to throw the exception thrown from that lookup.
throw fromSuper;
}
}
首先,判断该类是否已经被加载,如果已加载直接返回。
然后,尝试由BootClassLoader加载。
接着,尝试由DelegateLastClassLoader自身加载。
最后,尝试由DelegateLastClassLoader父加载器加载,默认也是BootClassLoader。
这里可以看到谷歌对热更新技术的谨慎,使用DelegateLastClassLoader可以做到热更新,不能做到热修复。
回到LoadedApk.makeApplication()的逻辑,接下来会调用ContextImpl.createAppContext()方法创建AppContext,
//ContextImpl.java
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
return createAppContext(mainThread, packageInfo, null);
}
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
String opPackageName) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, null,
0, null, opPackageName);
context.setResources(packageInfo.getResources());
context.mIsSystemOrSystemUiContext = isSystemOrSystemUI(context);
return context;
}
private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
@NonNull LoadedApk packageInfo, @Nullable String attributionTag,
@Nullable String splitName, @Nullable IBinder activityToken, @Nullable UserHandle user,
int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) {
...
mPackageInfo = packageInfo;
...
}
接下来将创建的AppContext作为参数传入,创建Application,调用attach()方法,
//Instrumentation.java
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
app.attach(context);
return app;
}
这里的className默认是android.app.Application,同样是通过上文的ClassLoader创建而来。
//AppComponentFactory.java
public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
@NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Application) cl.loadClass(className).newInstance();
}
在Application.attach()方法中,会调用父类ContextWrapper的attachBaseContext()方法,
//Application.java
/* package */ final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
这里是将上文创建的AppContext赋值给mBase。
//ContextWrapper.java
protected void attachBaseContext(Context base) {
if (mBase != null) {
throw new IllegalStateException("Base context already set");
}
mBase = base;
}
以上就是应用PathClassLoader的创建流程以及应用代码是如何被加载的。
从上述流程看,有两个可以hook的点,可用于实现热修复功能:
1.在Manifest中指定DelegateLastClassLoader,替换其parent ClassLoader。但有局限性,只能加载新增的类。
2.替换掉LoadedApk中的mClassLoader。
3. RePlugin
项目地址:https://github.com/Qihoo360/RePlugin
其核心是在应用Application创建的时候,通过hook替换掉应用Context中的mPackageInfo中的mClassLoader,在自定义的DexClassLoader中优先加载插件类。
RePluginClassLoader:用于替换Context中的mPackageInfo中的mClassLoader。
PluginDexClassLoader:处理类加载逻辑,优先加载插件类。
PatchClassLoaderUtils:通过hook实现替换Context中的mPackageInfo中的mClassLoader的功能。
参考文档:
彻底搞懂JVM类加载器:基本概念
类加载器中的双亲委派模型详解
Android类加载器ClassLoader