2.插件化之Android进程启动流程及类加载机制

1.安卓应用启动过程以及classloader的创建

下面我们基于安卓源码了解一下这三个classloader的实际使用,这对我们后续了解插件化中类的动态加载十分重要。

我们先了解一下安卓apk安装流程:

  1. 应用安装时首先应用安装请求被发送给系统,通常是通过PackageInstaller接口
  2. PMS解析APK文件,提取出清单文件(AndroidManifest.xml)中的信息,包括应用的权限、组件(Activities、Services、Broadcast Receivers、Content Providers)等。
  3. PMS为应用分配唯一的用户ID(UID)和组ID(GID),用于隔离应用数据和权限。
  4. 应用的APK文件及相关资源会被复制到系统指定的目录,通常是/data/app/或其细分目录下。
  5. DEX文件(包含Java字节码)会被优化为机器码,并存放在dalvik-cache目录下,以提高运行效率。
  6. PMS会在系统数据库中记录应用的安装信息,包括包名、版本、安装路径、权限等,以便后续管理和查询。
  7. 安装完成后,PMS会发送一个安装成功的广播(ACTION_PACKAGE_ADDED),通知系统和其他应用有新应用安装。

从上述简易流程可以看到AndroidManifest.xml是在应用安装时就已经解析并记录,所以插件的AndroidManifest.xml配置无法生效,每个APK安装都是独享空间的,不同APK、同一个APK的不同时间安装都是完全独立的

我们再了解一下应用启动流程:

  1. Launcher发起请求
    • 用户在Launcher(即主屏幕应用)上点击一个App图标。
    • Launcher通过Binder接口向系统的服务端组件——Activity Manager Service (AMS) 发送一个请求,要求启动一个新的Activity(即目标App的入口Activity)。
  2. AMS处理请求
    • AMS接收到启动请求后,首先检查目标App的进程是否已经运行。
    • 如果目标App的进程尚未启动,AMS会调用startProcessLocked函数,向Zygote进程发送请求创建一个新的应用进程。Zygote是一个预初始化的Android进程,用于孵化所有新应用进程。
    • Zygote通过fork自身来创建一个新的进程,并在这个新进程中执行目标App的ActivityThread类的main方法,从而初始化App的主线程。
  3. App进程初始化
    • 在新创建的App进程中,ActivityThreadmain方法中调用attach()方法,初始化了App的运行环境,包括加载库、设置Looper和Handler等。
    • ActivityThread随后调用attachApplication方法,向AMS进行绑定,告知AMS这个进程已经准备好接收任务。
  4. Activity的启动
    • AMS收到attachApplication消息后,通过IApplicationThread接口的bindApplication方法通知App进程创建Application对象,并初始化应用的全局状态。
    • 紧接着,AMS通过scheduleTransaction方法调度启动Activity的事务,实际上调用ActivityThread的相应方法来实例化目标Activity。
    • App进程中,Activity的生命周期方法开始被调用,依次是onCreateonStartonResume等,构建UI并显示给用户。

ActivityThread中创建Application是通过调用LoadedApk的makeApplication方法来创建的,我们来跟一下makeApplication的代码

public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    if (mApplication != null) {
        return mApplication;
    }

    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

    Application app = null;

    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }

    try {
        final java.lang.ClassLoader cl = getClassLoader();//反射创建Application所使用的ClassLoader
        ......
}

再看一下getClassLoader的代码:

public ClassLoader getClassLoader() {
    synchronized (this) {
        if (mClassLoader == null) {
            createOrUpdateClassLoaderLocked(null /*addedPaths*/);
        }
        return mClassLoader;
    }
}

继续看createOrUpdateClassLoaderLocked

private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
    ........
    if (!mIncludeCode) {
        if (mDefaultClassLoader == null) {
            StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
            mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader(
                    "" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
                    librarySearchPath, libraryPermittedPath, mBaseClassLoader,
                    null /* classLoaderName */);
            setThreadPolicy(oldPolicy);
            mAppComponentFactory = AppComponentFactory.DEFAULT;
        }

        if (mClassLoader == null) {
            mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
                    new ApplicationInfo(mApplicationInfo));
        }

        return;
    }
    
    ........
}

继续看ApplicationLoaders.getDefault().getClassLoader最终调用到了这个方法:

private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
                                   String librarySearchPath, String libraryPermittedPath,
                                   ClassLoader parent, String cacheKey,
                                   String classLoaderName, List<ClassLoader> sharedLibraries) {

  ........
    
            ClassLoader classloader = ClassLoaderFactory.createClassLoader(
                    zip,  librarySearchPath, libraryPermittedPath, parent,
                    targetSdkVersion, isBundled, classLoaderName, sharedLibraries);

 .........
    }
}

mLoaders是一个arraymap,它的key为dexPath,value为ClassLoaderFactory.createClassLoader返回的ClassLoader,继续跟ClassLoaderFactory.createClassLoader方法

public static ClassLoader createClassLoader(String dexPath,
        String librarySearchPath, ClassLoader parent, String classloaderName,
        List<ClassLoader> 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);
}

综上,应用安装时安卓会把我们的apk解压到指定目录,当进程启动时,会通过上述一系列操作把dexpath传给PathClassLoader,所以我们一个应用使用的classloader都是这一个PathClassLoader实例,我们也可以直接在自己的代码中打印任意类型的getClassLoader()验证

2.Android中的classloader

DexClassLoader、PathClassLoader、BaseDexClassLoader是安卓中特有的ClassLoader,其中DexClassLoader、PathClassLoader都是继承自BaseDexClassLoader。

我们直接来看DexClassLoader和PathClassLoader的源码 如下

//最新版本DexClassLoader构造
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}

//8.0以前DexClassLoader构造
public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}
public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    @SystemApi(client = MODULE_LIBRARIES)
    public PathClassLoader(
            @NonNull String dexPath, @Nullable String librarySearchPath, @Nullable ClassLoader parent,
            @Nullable ClassLoader[] sharedLibraryLoaders) {
        this(dexPath, librarySearchPath, parent, sharedLibraryLoaders, null);
    }

    @SystemApi(client = MODULE_LIBRARIES)
    public PathClassLoader(
            @NonNull String dexPath, @Nullable String librarySearchPath,
            @Nullable ClassLoader parent, @Nullable ClassLoader[] sharedLibraryLoaders,
            @Nullable ClassLoader[] sharedLibraryLoadersAfter) {
        super(dexPath, librarySearchPath, parent, sharedLibraryLoaders, sharedLibraryLoadersAfter);
    }
}

从源码中可以看到DexClassLoader构造函数相比PathClassLoader多了一个optimizedDirectory,即优化后的odex路径,但是新版DexClassLoader(安卓8.0以后)并不会把这个路径传给BaseDexClassLoader,所以这两个classloader均可用于加载DEX文件,区别更多在于应用场景的历史习惯和描述,而非本质功能。至于网上常见的 ”PathClassLoader 是用来加载已经安装的 apk 的,DexClassLoader 是用来加载存储空间的 dex / apk 文件的” 的说法其实是有问题的,至于插件化、热修复框架也不一定非要使用DexClassLoader,看到很多框架都是继承PathClassLoader或者BaseDexClassLoader重写findclass方法来实现加载自己的类。

我们再看一下BaseDexClassLoader的源码

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // First, check whether the class is present in our shared libraries.
        if (sharedLibraryLoaders != null) { 
            for (ClassLoader loader : sharedLibraryLoaders) {
                try {
                    return loader.loadClass(name);
                } catch (ClassNotFoundException ignored) {
                }
            }
        }
        // Check whether the class in question is present in the dexPath that
        // this classloader operates on.
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);//
        if (c != null) {
            return c;
        }
        // Now, check whether the class is present in the "after" shared libraries.
        if (sharedLibraryLoadersAfter != null) {
            for (ClassLoader loader : sharedLibraryLoadersAfter) {
                try {
                    return loader.loadClass(name);
                } catch (ClassNotFoundException ignored) {
                }
            }
        }
        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;
    }

可以看到调用了pathList的findClass方法返回class对象,继续跟代码:

public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

可以看到遍历PathList类中dexElements数组, 调用Element.findClass==>DexFile.loadClassBinaryName==>defineClassNative(native方法),返回class

总结:

  1. ClassLoader种类:重点介绍了DexClassLoaderPathClassLoader以及它们的基类BaseDexClassLoader。这些类加载器负责从指定路径加载DEX文件,进而加载类到内存中执行。
  2. DexClassLoader与PathClassLoader差异:早期版本中,DexClassLoader额外支持指定优化后的DEX文件存放目录,但后来版本中这一差异已不明显。两者均可用于加载DEX文件,区别更多在于应用场景的历史习惯和描述,而非本质功能。
  3. BaseDexClassLoader工作原理:深入到BaseDexClassLoaderfindClass方法,展示了类加载的逻辑,包括检查共享库、遍历dexElements数组(由各种DEX文件或包含类定义的路径组成),最终调用到DexFile.loadClassBinaryName并通过JNI调用到本地方法加载类。

3. 双亲委派机制

最后再简单介绍一下双亲委派机制,一些插件化框架会利用双亲委派机制指定class的加载优先级。

双亲委派机制是Java类加载机制中非常重要的一环,它描述了类加载器在加载类时的一种协作策略。该机制的基本思想是,当一个类加载器收到类加载请求时,首先不会自己尝试加载这个类,而是将这个请求委派给它的父类加载器去完成。只有当父类加载器无法加载该类时(即在父类加载器的搜索范围内未找到所需的类),子加载器才会尝试自己加载该类。这一过程自顶向下传递,直至顶层的启动类加载器(Bootstrap ClassLoader)。如果所有加载器都无法加载,则抛出ClassNotFoundException异常。

双亲委派机制的目的
  1. 安全性:防止恶意代码篡改系统类。因为用户自定义的类加载器无法加载已经被父类加载器加载过的类,这样就确保了像Java API这样的核心类库不会被篡改或替换。
  2. 稳定性:确保类加载的唯一性。不同类加载器加载同一个类时,最终都是由顶层的启动类加载器来加载,保证了即使在复杂的类加载器层级结构中,相同的类也不会被重复加载。
  3. 隔离性:不同应用的类加载器可以加载同名的类而互不影响,这为应用的模块化设计提供了可能,每个模块可以有自己的类加载器,负责加载本模块的类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值