资讯动态

Atlas 框架 bundle 加载过程全解析:从触发时机到代码初始化的三条主线

发布时间:2026/9/26 8:20:08 来源:尧图企业网站定制
移动开发原生移动插件系统【免费下载链接】atlasA powerful Android Dynamic Component Framework.项目地址https://gitcode.com/gh_mirrors/atlas/atlas点击查看免费下载本文基于开源仓库 atlas 源码体系围绕动态组件框架中bundle 如何被加载并运行这一核心链路展开。文章以 MainActivity 点击跳转到 secondbundle 的真实场景为线索完整剖析 bundle 加载的**触发时机步骤 1-7、加载过程步骤 8-15、代码初始化步骤 16-19**三个阶段并结合 atlas-core 与 atlas-demo 中的源码实现讲清主线程与 HandlerThread 的协作方式、BundleClassLoader 的创建、资源的注入以及 bundle Application 的启动原理。读完本文你将掌握 Atlas 容器按需异步加载 bundle的完整调用链能够据此定位和分析自己项目中的 bundle 加载问题。概述把加载过程拆成三部分Atlas 中 bundle 的加载并非一次性完成而是贯穿触发 → 安装解析 → 业务初始化三个环节。整个过程可用两张时序图直观描述主流程时序图bundle_load_img.svg第二部分加载过程时序图bundle_load_part_2_img.svg阅读时序图时的关键注意图中的箭头。实线箭头表示该步骤运行在主线程UI 线程虚线箭头表示该步骤运行在HandlerThread中。将整个流程分为三大部分部分说明对应步骤第一部分加载时机的触发逻辑1-7第二部分bundle 加载过程8-15第三部分bundle 代码初始化16-19其中第二部分整体运行在 HandlerThread 中第三部分整体运行在 UI 线程中——这种后台解析安装、前台回调启动的设计正是 Atlas 保证 bundle 首次加载不卡顿 UI 的核心手段。第一部分 触发时机从一次点击到安装任务入队入口MainActivity 中的 switchToActivity以一个真实场景作为起点用户在 MainActivity 中点击导航栏希望跳转到 secondbundle 内的SecondBundleActivity。case R.id.navigation_dashboard: switchToActivity(second,com.taobao.secondbundle.SecondBundleActivity)switchToActivity辗转调用后会执行到第 2 步execStartChildActivityInternal方法。execStartChildActivityInternal反查 bundle 名并判断是否已加载execStartChildActivityInternal是 ActivityGroup 兼容层ActivityGroupDelegate.java提供的入口方法其核心逻辑如下public void execStartChildActivityInternal(ViewGroup container,String key, Intent intent){ String bundleName AtlasBundleInfoManager.instance().getBundleForComponet(componentName); if(!TextUtils.isEmpty(bundleName)){ BundleImpl impl (BundleImpl) Atlas.getInstance().getBundle(bundleName); if(impl!nullimpl.checkValidate()) { // bundle 已加载且校验通过直接启动 } else { // bundle 未加载走异步加载 asyncStartActivity(container,key,bundleName,intent); } } }这段代码做了两件事根据 componentName 反查 bundle 名称。componentName 即com.taobao.secondbundle.SecondBundleActivity。在 Atlas 之启动过程二 中我们了解到AtlasBundleInfoManager中存储了几乎所有 bundle 的信息因此这里返回的 bundleName 就是com.taobao.secondbundle。其底层实现位于 AtlasBundleInfoManager.java遍历BundleListing中每个 bundle 的activities、services、receivers、contentProviders四类组件表命中即返回对应的pkgName。根据 bundleName 查询已加载的 BundleImpl 结构体。由于 secondbundle 此前并未加载过Atlas.getInstance().getBundle(bundleName)返回的impl为 null 或未通过checkValidate()校验于是执行第 3 步asyncStartActivity方法。在仓库的完整实现中asyncStartActivity还会弹出一个bundle 处理中的等待对话框RuntimeVariables.alertDialogUntilBundleProcessed并构造 success/failed 两个CancelableTask保证 bundle 加载完成后能安全回调到performLaunchChildActivity真正启动目标 Activity。checkBundleStateAsync异步触发安装链路asyncStartActivity最终调用了BundleUtil的方法直接看第 4 步checkBundleStateAsyncpublic static boolean checkBundleArrayStateAsync(final String[] bundlesName, final Runnable bundleActivated, final Runnable bundleDisabled){ BundleInstaller installer BundleInstallerFetcher.obtainInstaller(); installer.installTransitivelyAsync(bundlesName, new BundleInstaller.InstallListener() { Override public void onFinished() { boolean success true; BundleImpl tmp; for(String bundleName : bundlesName){ if((tmp((BundleImpl) Atlas.getInstance().getBundle(bundleName)))null || !tmp.checkValidate()){ success false; }else{ tmp.startBundle(); } } } }); return true; }在这一步中开始异步加载 bundle加载成功后会在主线程中回调onFinished后面会提及并调用BundleImpl对象的startBundle方法开启第三部分的初始化过程。值得注意的是installTransitivelyAsync中的 Transitively 意味着该安装是带依赖传递的一个 bundle 所依赖的其他 bundle 也会被一并检查与安装这与第二部分resolveBundle中解析dependencies的逻辑前后呼应。deliveryTask把安装任务投递到 HandlerThread第 5、6 步主要是各种逻辑判断bundle 是否已在安装中、是否重复请求等之后辗转调用到第 7 步deliveryTaskprivate void deliveryTask(boolean sync){ Runnable installTask new Runnable() { Override public void run() { synchronized (BundleInstaller.this) { try{ call(); } catch (Throwable e) { e.printStackTrace(); } finally { if (mListener ! null) { new Handler(Looper.getMainLooper()).post(new Runnable() { Override public void run() { mListener.onFinished(); } }); } } } } }; sBundleHandler.post(installTask); }函数首先创建了一个异步任务installTask之后将任务提交给sBundleHandler。而sBundleHandler实际上关联的是一个HandlerThread所以installTask运行在单独的线程中。在installTask中做了两件事调用了call函数真正的安装逻辑见第二部分向UI 线程提交一个任务用于回调onFinished——这保证了后续的 bundle 启动逻辑一定发生在主线程符合 Android 组件对主线程的要求。至此第一部分触发时机分析完毕进入第二部分加载过程。第二部分 加载过程HandlerThread 中的安装与解析需要注意第二部分整体是运行在 HandlerThread 中的因此所有涉及文件 IO、dexOpt 的耗时操作都不会阻塞 UI。call磁盘空间与内置 bundle 的双重校验call(){ //... if (FileUtils.getUsableSpace(Environment.getDataDirectory()) 5) { //has enough space if(AtlasBundleInfoManager.instance().isInternalBundle(bundleName)) { bundle installBundleFromApk(bundleName); if (bundle ! null) { ((BundleImpl) bundle).optDexFile(); } } } else { throw new LowDiskException(no enough space); } //... }这里有两个判断条件剩余存储空间满足要求FileUtils.getUsableSpace(Environment.getDataDirectory()) 5单位 MB空间不足时抛出LowDiskException定义于 atlas-core是内置 bundleisInternalBundle的判断实现在 AtlasBundleInfoManager.java即该 bundle 随宿主 APK 一起打包在storage目录中而非动态下载的外部 bundle。当两个条件都满足时执行 bundle 的安装和optDexFile操作。installBundleFromApk又调用了installNewBundle方法直接看第 9 步installNewBundle的实现。installNewBundle构造 BundleImplstatic BundleImpl installNewBundle(final String location, final InputStream in) throws BundleException { //... BundleListing.BundleInfo info AtlasBundleInfoManager.instance().getBundleInfo(location); BundleImpl bundle new BundleImpl(bundleDir, location, new BundleContext(), null, file, version, true, -1); return bundle; }函数很简单获取 bundle 的信息getBundleInfo从BundleListing中按名称取出BundleInfo见 AtlasBundleInfoManager.java之后构造一个BundleImpl对象。有两个参数需要注意参数说明bundleDir/data/data/com.taobao.demo/files/storage/com.taobao.secondbundlein指向lib/armeabi/libcom_taobao_secondbundle.so其中bundleDir的根目录对应 Framework.java 中的STORAGE_LOCATION BASEDIR /storage/即宿主应用的files/storage目录每个 bundle 在该目录下拥有独立的子目录目录名即 bundle 的包名。BundleImpl构造函数的两件套BundleImpl(final File bundleDir, final String location, final BundleContext context, final InputStream stream, ...) throws BundleException, IOException{ if (stream ! null) { this.archive new BundleArchive(location, bundleDir, stream, version, dexPatchVersion); } if (autoload) { resolveBundle(); Framework.bundles.put(location, this); } }构造函数首先创建了一个BundleArchive对象。BundleArchive持有bundleDir和InputStream的引用用于后续的 dex 优化dexOpt与版本管理——一个 bundle 在 storage 目录中按版本组织getCurrentRevision().getRevisionDir()指向当前生效的版本目录。随后若autoload为 true内置 bundle 场景即为 true依次执行两件事调用resolveBundle()并将自身注册到Framework.bundles这个全局 map 中对应 Framework.java 的ConcurrentHashMapString, Bundle。resolveBundle创建 BundleClassLoader 并注入资源private synchronized void resolveBundle() throws BundleException { //... if (this.classloader null){ // create the bundle classloader ListString dependencies AtlasBundleInfoManager.instance().getDependencyForBundle(location); String nativeLibDir getArchive().getCurrentRevision().getRevisionDir().getAbsolutePath()/lib: RuntimeVariables.androidApplication.getApplicationInfo().nativeLibraryDir: System.getProperty(java.library.path); if(dependencies!null) { for (String str : dependencies) { BundleImpl impl (BundleImpl) Atlas.getInstance().getBundle(str); if (impl ! null) { nativeLibDir :; File dependencyLibDir new File(impl.getArchive().getCurrentRevision().getRevisionDir(), lib); nativeLibDir dependencyLibDir; } } } this.classloader new BundleClassLoader(this, dependencies, nativeLibDir); } // notify the listeners Framework.notifyBundleListeners(0 /*LOADED*/, this); }resolveBundle为 bundle 创建一个独立的BundleClassLoader在创建 classloader 的过程中指定 bundle 自身依赖 so 的路径revisionDir/lib 系统nativeLibraryDirjava.library.path指定依赖 bundle 所依赖 so 的路径遍历getDependencyForBundle返回的依赖列表实现在 AtlasBundleInfoManager.java从BundleInfo.getDependency()读取把每个已加载依赖 bundle 的revisionDir/lib追加到nativeLibDir中。例如在 demo 中nativeLibDir的值为/data/user/0/com.taobao.demo/files/storage/com.taobao.secondbundle/version.1/lib:/data/app/com.taobao.demo-1/lib/x86:/vendor/lib:/system/lib创建完BundleClassLoader后最后一行Framework.notifyBundleListeners(0 /*LOADED*/, this)进行状态回调这里的回调实现类是BundleLifecycleHandlerpublic void bundleChanged(final BundleEvent event){ switch (event.getType()) { case 0:/* LOADED */ loaded(event.getBundle()); } }这一步对应 Runtime_principle.md 中定义的 bundle 生命周期Installed安装到 storage 目录→Resolvedclassloader 被创建、assetpatch 注入 DelegateResources→Active校验通过、dexOpt 完成、资源注入成功→Startedapplication 的 onCreate 被调用。resolveBundle正是生命周期中Resolved阶段的实现。loaded把 bundle 资源注入全局 Resourcesprivate void loaded(Bundle bundle) { long time System.currentTimeMillis(); BundleImpl b (BundleImpl) bundle; DelegateResources.addBundleResources( b.getArchive().getArchiveFile().getAbsolutePath() ); }loaded的核心动作是把该 bundle 的 APK 文件路径交给DelegateResources.addBundleResources。这正是 Atlas 资源加载机制的关键宿主LoadedApk中的 Resources 已被替换为 Atlas 内部的DelegateResources见 Runtime_principle.md 的资源加载机制一节每个 bundle 安装时其 assets 路径会被更新到 DelegateResources 的 AssetManager 中从而让宿主在查找资源时能命中 bundle 内的资源。这就是 bundle 中资源看起来像宿主资源的根本原因。回到第 10 步BundleImpl构造函数中BundleImpl(final File bundleDir, final String location...) { //... resolveBundle(); Framework.bundles.put(location, this); }在resolveBundle函数执行周期内主要完成了两件事为 bundle 创建了独立的ClassLoader将 bundle 中的资源添加到全局 Resources上。随后第二行Framework.bundles.put(location, this)将 bundle 的信息加入到已加载的列表中——这一步正是第三部分DelegateClassLoader能从已加载列表中反查 bundle 的前提。完成之后回到第 8 步执行optDexFile方法对 bundle 中的 dex 文件进行 dexOptdalvik/ dex2oatART优化生成优化后的 odex/oat 文件为后续类加载做好准备并完成生命周期中的Active状态校验。第三部分 初始化过程UI 线程上的 bundle 启动整个第三部分运行在 UI 线程中。bundle 加载完成后第 7 步的deliveryTask在 UI 线程中回调了完成接口onFinished调用startBundle方法又经过辗转调用到达BundleLifecycleHandler的started方法中。started构造 bundle 的 Application 并执行 onCreateprivate void started(Bundle bundle){ BundleImpl b (BundleImpl) bundle; BundleListing.BundleInfo info AtlasBundleInfoManager.instance().getBundleInfo(b.getLocation()); //... String appClassName info.getApplicationName(); Application app newApplication(appClassName, b.getClassLoader()); app.onCreate(); }第 6 行构造出 bundle 中注册的 application 对象之后执行 application 的onCreate方法。对于 application 来说似乎还差一个关键的attachBaseContext入口函数调用我们接着看构造过程。newApplication加载类、实例化并反射 attachprotected static Application newApplication(String applicationClassName, ClassLoader cl) throws ApplicationInitException { final Class? applicationClass cl.loadClass(applicationClassName); Application app (Application) applicationClass.newInstance(); AtlasHacks.Application_attach.invoke(app, RuntimeVariables.androidApplication); return app; }newApplication做了三步根据类名applicationClassName来自BundleInfo.getApplicationName()通过 bundle 自己的BundleClassLoader加载对应的 class反射newInstance()构造出 Application 对象通过AtlasHacks.Application_attach.invoke(app, RuntimeVariables.androidApplication)反射执行 application 的 attach 方法Hack 工具层位于 atlas-core负责系统层面的注入与校验。在 Atlas 之启动过程二 中解释过application 的 attach 方法最终会调用到attachBaseContext方法——这正是 bundle Application 获得宿主 Context、完成生命周期初始化的关键一步。这也与宿主自身的启动流程AtlasBridgeApplication中先 attach 再 onCreate保持了一致的语义确保 bundle 内 Application 的attachBaseContext→onCreate顺序与普通 Android Application 完全一致。DelegateClassLoaderbundle 类的路由查找最后看一下DelegateClassLoader加载 bundle 中 class 的过程。Atlas 中存在两种 ClassLoader详见 Runtime_principle.md 的类加载机制一节DelegateClassLoader作为类查找的路由器本身不真正加载类启动时被注入LoadedApk替换原有的 PathClassLoaderBundleClassLoader每个 bundle resolve 时分配一个负责该 bundle 的类加载查找顺序为 findOwn自身 dex→ findDependency依赖 bundle→ findPath主 APK。protected Class? findClass(String className) throws ClassNotFoundException { Class? clazz loadFromInstalledBundles(className, false); //... return clazz; } static Class? loadFromInstalledBundles(String className, boolean safe) throws ClassNotFoundException { String bundleName AtlasBundleInfoManager.instance().getBundleForComponet(className); BundleImpl bundle (BundleImpl) Atlas.getInstance().getBundle(bundleName); //... ClassLoader classloader bundle.getClassLoader(); Class? clazz classloader.loadClass(className); return clazz; }可以看到DelegateClassLoader.findClass会优先从 bundle 上去找 class。而loadFromInstalledBundles逻辑如下从AtlasBundleInfoManager的已加载列表中找到对应 bundle即第二部分第 10 步Framework.bundles.put时添加的从对应 bundle 上的BundleClassLoader加载对应的 class。这一先查 bundle、再走系统 ClassLoader的委托顺序保证了 bundle 内的类Activity、Application 等一定由 bundle 自己的 ClassLoader 加载从而让 bundle 之间、bundle 与宿主之间形成清晰、可隔离、可更新的类边界。总结三条主线的闭环至此整个 bundle 的加载、初始化过程分析完毕。将三个部分串成一条完整链路触发主线程点击 →execStartChildActivityInternal反查 bundleName →checkBundleStateAsync提交异步安装 →deliveryTask投递到 HandlerThread加载HandlerThreadcall校验磁盘空间与内置性 →installNewBundle构造 BundleImpl →resolveBundle创建 BundleClassLoader、注入 nativeLib 路径、触发 LOADED 回调完成资源注入 → 注册进Framework.bundles→optDexFile完成 dex 优化初始化主线程onFinished回调 →startBundle→started用 bundle 自己的 ClassLoader 加载 Application → 反射 attach触发attachBaseContext→onCreate此后宿主通过DelegateClassLoader将 bundle 内类的加载请求路由回各自的 BundleClassLoader。理解这条链路的关键在于时刻记住两条线程边界耗时解析与文件操作全部下沉到 HandlerThread而任何涉及 Android 组件生命周期的操作都回到 UI 线程。这种设计让 bundle 的动态加载对用户无感也构成了 Atlas 作为 Android 动态组件框架的核心运行机制。赞分享移动开发原生移动插件系统【免费下载链接】atlasA powerful Android Dynamic Component Framework.项目地址https://gitcode.com/gh_mirrors/atlas/atlas点击查看免费下载相关推荐UITableView高级应用打造IoT-Firstep iOS版设备管理界面终极指南UITableView高级应用打造IoT Firstep iOS版设备管理界面终极指南 在物联网 IoT 开发中设备管理界面是连接用户与智能设备的关键桥梁。Layui 复选框初始化事件触发机制解析Layui 复选框初始化事件触发机制解析 在 Layui 框架开发中表单组件的初始化事件触发是一个常见需求。本文将以复选框组件为例深入探讨如何在页面加载时自前端UI组件Autoenv源码解析从初始化到环境加载的完整流程Autoenv源码解析从初始化到环境加载的完整流程 Autoenv是一个强大的目录环境管理工具它能自动执行特定目录下的环境配置文件。当您 cd 进入包含 .开发工具CLI工作流自动化上一篇3个技巧搞定电脑风扇噪音FanControl免费工具让你的电脑安静如初下一篇5分钟掌握8球台球辅助工具提升瞄准精度的终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑