Expo unimodules-app-loader:Expo 后台应用加载器模块的版本演进与源码剖析
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
unimodules-app-loader是 Expo 单模块(Unimodules)体系中的基础原生模块,服务于expo-task-manager、unimodules-react-native-adapter和 Expo Client,用于让 Expo 应用以"后台应用"(background app)形式在原生进程中启动与运行。本文以该模块的官方变更日志 CHANGELOG.md 为主线,完整梳理其自 1.1.0 到 57.0.1 的版本演进、各平台的部署基线调整(iOS/tvOS/macOS/Android),并结合 ios/UMAppLoader 源码剖析"应用加载器注册—查找—实例化"的核心机制,帮助读者理解 Expo 后台应用加载的底层原理与各版本升级的约束条件。
模块定位:专为后台运行设计的 App Loader
该模块的 README 对其职责只有一句话,但信息密度很高:
This module is to be used only by
expo-task-manager,unimodules-react-native-adapterand Expo Client to run Expo applications in background.
也就是说,它不是面向业务开发者的 API,而是 Expo 内部运行时的一部分:当 React Native 侧需要把另一个 Expo 应用作为后台任务拉起来时(典型场景是expo-task-manager的后台任务、更新后的后台重载等),原生端需要一个统一的入口去"按名字找到对应的加载器类并创建实例"。package.json中的描述同样印证了这一点——package.json 的description字段为 "App loader for background applications"。
模块的元数据也体现其当前形态:
- expo-module.config.json 声明
"platforms": ["apple", "android"],即同时打包 Apple 与 Android 两个平台的原生实现; - package.json 当前版本为
57.0.1,与 Expo SDK 57 对齐(见下文版本体系一节)。
iOS 核心实现:注册缓存 + 单例 Provider
iOS 侧实现集中在 ios/UMAppLoader 目录,由两个接口协议、一个 Provider 和一个 CocoaPods 规格文件组成。
1. 两个协议:加载器与应用记录
UMAppLoaderInterface.h 定义了加载器必须实现的唯一方法:
@protocol UMAppLoaderInterface <NSObject> - (nonnull id<UMAppRecordInterface>)loadAppWithUrl:(nonnull NSString *)url options:(nullable NSDictionary *)options callback:(nullable void(^)(BOOL success, NSError * _Nullable error))callback; @end加载器的职责是:给定一个 URL 和可选参数,把对应的 Expo 应用加载起来,并通过回调上报成功与否;返回值是一个实现了 UMAppRecordInterface 的"应用记录"对象,该协议只有一个方法- (void)invalidate;,用于在不再需要时回收这个后台应用实例。
2. 宏驱动的类注册
UMAppLoaderProvider.h 除了声明 Provider 接口外,还提供了一对编译期宏:
#define UM_REGISTER_APP_LOADER_WITH_CUSTOM_LOAD(loader_name, _custom_load_code) \ extern void UMRegisterAppLoader(NSString *, Class); \ + (void)load { \ UMRegisterAppLoader(@#loader_name, self); \ _custom_load_code \ } #define UM_REGISTER_APP_LOADER(loader_name) \ UM_REGISTER_APP_LOADER_WITH_CUSTOM_LOAD(loader_name,)机制是:任何 Expo 加载器类只需在类实现处调用UM_REGISTER_APP_LOADER(MyLoader),ObjC 的+load运行时钩子就会在进程启动时把「名字 → Class」写入全局注册表,_custom_load_code参数则允许调用方在同一步中附加自定义初始化代码。
3. 注册表与查找逻辑
UMAppLoaderProvider.m 给出了完整的注册与查找实现:
UMRegisterAppLoader(NSString *loaderName, Class loaderClass)(第 9–19 行)首先校验该 Class 是否遵循UMAppLoaderInterface协议,符合才写入静态字典UMProvidedAppLoaderClasses(NSMutableDictionary<NSString *, Class>),不符合则打印NSLog警告并拒绝注册——这保证了注册表中永远是"可用的加载器";createAppLoader:(第 23–27 行)按名字从字典取 Class 并直接new一个实例返回,找不到时返回nil(接口声明为nullable);sharedInstance(第 31–40 行)用dispatch_once保证全局单例,调用方统一通过[UMAppLoaderProvider sharedInstance]访问。
这套"启动时注册 + 运行时按名查找"的设计,让后台加载器对宿主进程保持零耦合:加载器类自己声明名字,Provider 无需硬编码任何具体类。
4. 测试与构建配置
- 单元测试 UMAppLoaderProviderTests.swift 基于 Swift Testing(
@Suite/@Test),当前验证了sharedInstance单例的非空性;测试目标通过 podspec 的test_spec挂接ExpoModulesTestCore依赖。 - UMAppLoader.podspec 声明
s.static_framework = true、DEFINES_MODULE => YES,并把平台基线写死为ios => '16.4'、tvos => '16.4'——这正是变更日志中 56.0.0 版本"Raise minimum iOS/tvOS version to 16.4"这一破坏性变更在构建配置里的落地。 - spm.config.json 则描述了对应的 Swift Package 构建形态:单一
UMAppLoader产品、ObjC target(源文件匹配**/*.m、头文件**/*.h),链接Foundation与UIKit,平台标注为iOS("16.4"),与 podspec 保持一致。
Android 侧形态
Android 端的 AndroidManifest.xml 是空 manifest(只有<manifest>根节点),说明该模块在 Android 上不声明组件、权限或合并逻辑,其价值主要在于作为"平台包"存在,使 autolinking 与构建流程对双平台保持一致——这一点也可以从 expo-module.config.json 的平台声明得到印证。结合变更日志中"移除不兼容的旧 Gradle 设置"、"开始使用 Expo Modules Gradle 插件"等条目,可以推断 Android 侧的实质变化都发生在构建脚本层面,而非本包内的源码。
版本演进:从 CHANGELOG 看平台基线收紧史
通读 CHANGELOG.md,这个模块的发布历史几乎就是一部 Expo 平台部署基线收紧史。下面把其中有破坏性或实质性变化的版本完整列出(版本均标注了发布日期)。
iOS / Apple 平台部署目标演进
| 版本 | 日期 | 变更 |
|---|---|---|
| 2.0.0 | 2021-01-15 | 放弃 iOS 10 支持(破坏性变更) |
| 3.0.0 | 2021-09-28 | 放弃 iOS 11 支持(破坏性变更) |
| 4.0.0 | 2022-10-25 | iOS 部署目标提升至 13.0,iOS 12 标记为弃用(破坏性变更) |
| 4.5.0 | 2023-11-14 | iOS 部署目标提升至 13.4(破坏性变更) |
| 5.0.0 | 2024-10-22 | iOS 部署目标提升至 15.1(破坏性变更) |
| 6.0.0 | 2025-08-13 | 新增 Apple TV(tvOS)支持 |
| 56.0.0 | 2026-05-05 | 最低 iOS/tvOS 版本提升至 16.4,macOS 提升至 13.4(破坏性变更) |
这条时间线显示一个稳定节奏:几乎每个大版本都会上移一档部署目标,从 iOS 10 一路推进到当前的 16.4,并在 6.0.0 把能力边界从 iPhone/iPad 扩展到 Apple TV。当前 UMAppLoader.podspec 与 spm.config.json 中16.4的基线,即对应最新的 56.0.0 变更。
Android 构建基线演进
| 版本 | 日期 | 变更 |
|---|---|---|
| 2.1.0 | 2021-03-10 | 构建目标升至 Android 11(支持 Android SDK 30) |
| 3.1.0 | 2022-04-18 | compileSdkVersion/targetSdkVersion升至 31,Java 升至 11 |
| 4.1.0 | 2023-02-03 | compileSdkVersion/targetSdkVersion升至 33 |
| 4.4.0 | 2023-10-17 | 放弃 Android SDK 21 与 22 支持(破坏性变更) |
| 4.5.0 | 2023-11-14 | compileSdkVersion/targetSdkVersion升至 34(破坏性变更) |
| 5.1.0 | 2025-04-04 | 开始使用 Expo Modules Gradle 插件 |
Android 侧的变化主要是 SDK 版本跟随 Android 系统节奏上调,同时最低支持版本从 API 23 起步(API 21/22 在 4.4.0 被移除)。
其他值得关注的变更
- 3.0.1(2022-02-01):修复 Android Gradle 7 下的
Plugin with id 'maven' not found构建错误——这是 Expo 生态从 Gradle 6 迁移到 7 时的一次典型修复。 - 4.2.0(2023-06-21):修复 Gradle 8 的 Android 构建告警。
- 4.3.0(2023-09-04):新增对 React Native 0.73 的支持。
- 4.5.0(2023-11-14):
unimodule.json正式更名为expo-module.config.json——本次更名之后,各模块(包括本包的 expo-module.config.json)统一采用新文件名;同版本还迁移了剩余expo-module.config.json到统一平台语法(见 5.1.0 条目)。 - 4.6.0(2024-04-18):移除向后兼容的旧 Gradle 设置,为构建脚本"瘦身"。
- 55.0.3(2026-04-02):iOS 头文件中补充缺失的
Foundation.himport,修复头文件独立编译问题。 - 1.1.0(2020-05-27):修复
appLoaderRegisteredForName只检查名字是否在缓存中、却不校验缓存类名与当前类名一致的缺陷——从源码角度看,这正对应 UMAppLoaderProvider.m 中UMProvidedAppLoaderClasses缓存的早期版本:在从托管工作区(managed)迁移到 bare workflow 时,缓存中的类名必须被刷新,否则会出现"名字在、类已变"的不一致。
版本号的两次跃迁
CHANGELOG 中存在两段明显的版本号断层:5.x之后直接跳到6.0.x(2025-08-13 的 6.0.0),随后 2026-01-21 又跳到55.0.0,此后一路跟随 55.0.x → 56.0.x → 57.0.x 发布。从版本对应关系看,55.0.0起与该包的57.0.1(package.json)均与 Expo SDK 版本号保持一致,可以推断模块在 55.0.0 起采用了与 Expo SDK 对齐的版本命名策略,而 5.1.3–6.0.8 区间则是过渡期的独立版本线。对于使用者而言,选择依赖版本时应以当前项目的 Expo SDK 大版本为准,保持同版本对齐。
如何在当前仓库中定位与验证
- 查阅完整发布记录:CHANGELOG.md;
- 阅读 iOS 注册/查找实现:UMAppLoaderProvider.m、UMAppLoaderProvider.h;
- 确认平台基线:UMAppLoader.podspec(CocoaPods,iOS/tvOS 16.4)与 spm.config.json(SwiftPM,iOS 16.4);
- 查看平台声明与模块归属:expo-module.config.json、package.json;
- 查看单测:UMAppLoaderProviderTests.swift。
小结
unimodules-app-loader是 Expo 后台应用加载链路中原生端的关键一环:iOS 侧用宏驱动的+load注册、UMAppLoaderProvider单例与UMAppLoaderInterface协议,构成了一套轻量而稳定的"按名查找加载器"机制;版本历史上,它的每一次大版本几乎都对应一次 iOS/tvOS/macOS 部署目标或 Android SDK 基线的上调。对维护混合原生工程的团队来说,这份 CHANGELOG 的价值在于:升级 Expo SDK 前,先核对目标版本的平台基线(当前为 iOS/tvOS 16.4、macOS 13.4)与破坏性变更条目,再决定升级节奏。
【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考