类加载

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

你可能感兴趣的:(类加载)