Expo Application:在 React Native 运行时读取原生应用信息的完整指南
【免费下载链接】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
导读
本文围绕 Expo 官方原生模块expo-application(仓库位置:packages/expo-application)展开,讲解如何在 Android、iOS 与 Web 三端运行时获取原生应用信息,包括应用 ID、应用名、版本号、构建号、安装时间,以及各平台特有的设备标识与发布渠道判定等能力。读完本文,你将掌握该模块的安装方式、全部 API 的用法与返回值语义、各平台的差异与底层实现原理,并能在分析、埋点、灰度与合规场景中直接落地使用。
一、模块定位:运行时读取原生应用元数据
expo-application是一个"通用模块"(universal module),其官方定位一句话即可概括:在运行时获取原生应用信息,例如应用 ID、应用名称与构建版本(见 package.json 中的描述:"A universal module that gets native application information such as its ID, app name, and build version at runtime")。
它解决的典型问题是:JS 侧无法直接读取 AndroidPackageInfo或 iOSInfo.plist中的元数据,而这些数据在以下场景中不可或缺:
- 版本兼容判断:读取
nativeApplicationVersion,与服务端下发的能力阈值比较,决定是否展示升级引导; - 渠道与归因分析:通过
getInstallReferrerAsync(Android)获取安装来源; - 灰度与发布渠道识别:通过 iOS 的
getIosApplicationReleaseTypeAsync判断当前是 App Store、Ad Hoc 还是开发构建; - 安装/更新时间统计:通过
getInstallationTimeAsync、getLastUpdateTimeAsync计算留存与更新周期。
模块采用 Expo Modules 标准架构,三端实现分别位于:
- TypeScript 统一入口与类型声明:src/Application.ts、src/Application.types.ts;
- Android 原生实现:android/src/main/java/expo/modules/application/ApplicationModule.kt;
- iOS 原生实现:ios/ApplicationModule.swift;
- Web 降级实现:src/ExpoApplication.web.ts。
二、安装与前置条件
2.1 Managed Expo 项目
在受管的 Expo 托管项目中,直接使用npx expo install安装即可,该命令会自动选取与当前 SDK 匹配的版本,并同步安装其 peer 依赖expo:
npx expo install expo-application2.2 Bare React Native 项目
在裸 React Native 项目中,需要先确保已安装并配置好expo包(即完成 Expo Modules 的接入),然后再添加本模块:
npx expo install expo-applicationexpo-application的 peer 依赖为expo("expo": "*",见 package.json),自身没有其他第三方运行时依赖(dependencies为空),接入成本很低。在 Expo 自动链接机制(autolinking)下,原生工程构建时会自动注册该模块,无需手动改动 Android/iOS 工程文件。
三、同步常量:一行代码读取基础应用信息
expo-application暴露了四个同步常量,它们都是模块加载时从原生层一次性读取的模块常量(Module Constant),类型均为string | null。在 Web 平台上这四个值恒为null(见 src/ExpoApplication.web.ts)。
3.1 nativeApplicationVersion —— 面向用户展示的版本号
import * as Application from 'expo-application'; console.log(Application.nativeApplicationVersion); // 例如 "2.11.0"- Android:读取
PackageInfo.versionName,即 app config 中version字段在构建时写入的值; - iOS:读取
Info.plist的CFBundleShortVersionString; - Web:
null。
3.2 nativeBuildVersion —— 内部构建号
console.log(Application.nativeBuildVersion); // 例如 "114"- Android:读取
PackageInfo.versionCode(底层经getLongVersionCode兼容 API 28 前后的取值差异),即 app config 中android.versionCode的值; - iOS:读取
Info.plist的CFBundleVersion(独立构建中对应 app config 的ios.buildNumber); - 注意:Android 与 iOS 上返回类型均为
string; - Web:
null。
3.3 applicationName —— 桌面/主屏显示的应用名
console.log(Application.applicationName); // 例如 "Expo"、"Yelp"、"Instagram"- Android:通过
applicationInfo.loadLabel(packageManager)读取(见 ApplicationModule.kt); - iOS:读取
CFBundleDisplayName; - 若名称无法获取则返回
null;Web 恒为null。
3.4 applicationId —— 应用唯一标识
console.log(Application.applicationId); // 例如 "com.cocoacasts.scribbles"- Android:即 application ID(
packageName); - iOS:即 bundle ID(
CFBundleIdentifier); - Web:
null。
从源码结构看,这四个常量在 Android 侧均通过
getPackageInfoCompat统一封装获取PackageInfo(内部按 API 33 以上使用PackageInfoFlags新 API,旧版本走废弃的getPackageInfo(packageName, flags),见 ApplicationModule.kt),失败时抛出带明确错误文案的ApplicationPackageNameNotFoundException。
四、异步 API:安装信息、设备标识与归因
4.1 getInstallationTimeAsync —— 首次安装时间(双端支持)
const installTime = await Application.getInstallationTimeAsync(); // 2019-07-18T18:08:26.121Z返回Date对象,语义为"应用安装到设备的时间,不包含后续更新";若应用被卸载后重装,则返回重装时间。
- Android:底层读取
PackageInfo.firstInstallTime; - iOS:读取应用 Documents 根目录文件的创建时间(
NSFileCreationDate,见 ApplicationModule.swift),并以毫秒时间戳返回后在 JS 层包装为Date(见 Application.ts); - Web:
null。
4.2 getLastUpdateTimeAsync —— 最近更新时间(仅 Android)
const lastUpdate = await Application.getLastUpdateTimeAsync(); // 2019-07-18T21:20:16.887Z底层读取PackageInfo.lastUpdateTime,语义为"最近一次从 Google Play 更新的时间"。该 API 标注@platform android,在 iOS 上调用会因原生方法缺失而抛出UnavailabilityError。
4.3 getAndroidId —— ANDROID_ID(仅 Android)
const androidId = Application.getAndroidId(); // "dd96dec43fb81c97"同步方法,返回Settings.Secure.ANDROID_ID。这是一个十六进制字符串,对"应用签名密钥 + 用户 + 设备"的组合唯一,因此:
- 设备恢复出厂设置、或 APK 签名密钥变化时,该值可能改变;
- Android 8.0(API 26)及以上,
ANDROID_ID的取值规则受平台隐私策略影响(不同签名、不同用户会得到不同值); - Android 8.0 以下,该值在设备生命周期内保持恒定;
- 在非 Android 平台(iOS/Web)调用会抛出
UnavailabilityError(见 Application.ts)。
4.4 getInstallReferrerAsync —— 安装来源归因(仅 Android)
const referrer = await Application.getInstallReferrerAsync(); // "utm_source=google-play&utm_medium=organic"通过 Google Play 的 [Install Referrer API] 获取应用安装时的 referrer 信息。返回值不一定是一个完整的绝对 URL,通常是utm_*参数串,可直接用于渠道归因分析。
底层实现值得展开:模块通过InstallReferrerClient建立异步连接,并根据响应码分情况处理(见 ApplicationModule.kt):
| 响应码 | 含义 | 处理 |
|---|---|---|
OK | 连接成功且收到响应 | 解析installReferrer并 resolve |
FEATURE_NOT_SUPPORTED | 当前 Play Store 不提供该 API | rejectERR_APPLICATION_INSTALL_REFERRER_UNAVAILABLE |
SERVICE_UNAVAILABLE | 连接建立失败 | rejectERR_APPLICATION_INSTALL_REFERRER |
| 其他 | 通用错误 | reject 并附带响应码 |
连接中断(onInstallReferrerServiceDisconnected) | 服务失联 | rejectERR_APPLICATION_INSTALL_REFERRER_SERVICE_DISCONNECTED |
连接期间若抛RemoteException,则 rejectERR_APPLICATION_INSTALL_REFERRER_REMOTE_EXCEPTION。可见该 API 在模拟器或缺少 Play Store 的环境下会失败,落地时务必做好错误兜底。
4.5 getIosIdForVendorAsync —— IDFV 厂商标识(仅 iOS)
const idfv = await Application.getIosIdForVendorAsync(); // "68753A44-4D6F-1226-9C60-0050E4C00067"返回 iOS 的 identifierForVendor(IDFV),语义要点:
- 同一厂商(vendor)的所有 App 返回相同 ID,可用于跨 App 的用户关联;
- 偶尔会返回
null,典型场景是设备重启后、用户尚未解锁之前,此时应稍等再重试; - 若该厂商的所有 App 均被卸载,系统会更换 vendor ID,因此不宜将其作为永久设备标识持久化。
4.6 getIosApplicationReleaseTypeAsync —— 发布渠道判定(仅 iOS)
import { ApplicationReleaseType } from 'expo-application'; const releaseType = await Application.getIosApplicationReleaseTypeAsync(); if (releaseType === ApplicationReleaseType.APP_STORE) { // 商店正式包 }返回枚举ApplicationReleaseType(定义见 src/Application.types.ts):
| 枚举值 | 数值 | 含义 |
|---|---|---|
UNKNOWN | 0 | 无法判定 |
SIMULATOR | 1 | 模拟器构建 |
ENTERPRISE | 2 | 企业分发(In-House) |
DEVELOPMENT | 3 | 开发构建 |
AD_HOC | 4 | Ad Hoc 分发 |
APP_STORE | 5 | App Store 正式版 |
底层判定逻辑在 Objective-C 的 EXProvisioningProfile.m 中,依据嵌入二进制内的embedded.mobileprovision内容逐步判断:
- 无 provisioning profile:模拟器返回
SIMULATOR,真机返回APP_STORE; - profile 解析失败:返回
UNKNOWN; ProvisionsAllDevices为 true:企业分发 →ENTERPRISE;- 存在
ProvisionedDevices(包含 UDID 列表):再按Entitlements.get-task-allow区分,为 true 是DEVELOPMENT(开发),否则是AD_HOC; - 其余情况:
APP_STORE。
该枚举在原生侧(EXProvisioningProfile.h)与 JS 侧保持同步定义,方便做灰度或调试态逻辑分流。
4.7 getIosPushNotificationServiceEnvironmentAsync —— APNs 环境(仅 iOS)
const apnsEnv = await Application.getIosPushNotificationServiceEnvironmentAsync(); // "development" | "production" | null返回类型PushNotificationServiceEnvironment,取值为'development'、'production'或null(见 src/Application.types.ts)。它映射到原生 target 注册的 entitlements 中的aps-environment键:底层直接读取 provisioning profile 的Entitlements字典(见 EXProvisioningProfile.m)。
- 在模拟器上运行会返回
null,因为模拟器不支持向 APNs 注册,也没有对应的 entitlements; - 该信息可用于确认推送通知当前走的是沙箱环境还是生产环境,辅助排查推送配置问题。
五、平台差异速查表
| API | Android | iOS | Web |
|---|---|---|---|
nativeApplicationVersion | ✅versionName | ✅CFBundleShortVersionString | null |
nativeBuildVersion | ✅versionCode | ✅CFBundleVersion | null |
applicationName | ✅loadLabel | ✅CFBundleDisplayName | null |
applicationId | ✅packageName | ✅CFBundleIdentifier | null |
getInstallationTimeAsync | ✅firstInstallTime | ✅ Documents 目录创建时间 | null |
getLastUpdateTimeAsync | ✅lastUpdateTime | ❌ 抛 UnavailabilityError | ❌ |
getAndroidId | ✅ANDROID_ID | ❌ 抛 UnavailabilityError | ❌ |
getInstallReferrerAsync | ✅ Install Referrer API | ❌ | ❌ |
getIosIdForVendorAsync | ❌ | ✅ IDFV | ❌ |
getIosApplicationReleaseTypeAsync | ❌ | ✅ 解析 mobileprovision | ❌ |
getIosPushNotificationServiceEnvironmentAsync | ❌ | ✅aps-environment | ❌ |
整体模式是:双端共用的基础元数据 + 各平台独占的设备标识/发布渠道能力。非目标平台调用时,JS 层统一通过UnavailabilityError(来自expo包)抛出明确错误(见 Application.ts 中各函数的平台守卫)。
六、原生调用链与模块结构一览
从模块结构看,expo-application的调用链清晰分层:
- JS 统一 API 层:src/Application.ts 负责导出常量与函数、做平台守卫、把原生返回值包装为
Date,并 re-export 类型; - 原生桥接层:
requireNativeModule('ExpoApplication')获取原生模块(见 src/ExpoApplication.ts); - 平台实现层:Android 的 ApplicationModule.kt(
ModuleDefinition中注册名为ExpoApplication的常量与 AsyncFunction)、iOS 的 ApplicationModule.swift; - Web 降级层:src/ExpoApplication.web.ts 将所有属性置为
null、异步方法返回null,保证 Web 端不崩溃。
这一分层也符合 Expo Modules 的通用架构约定,参考仓库内 Expo Module Infrastructure 文档即可进一步了解模块编写规范。
七、版本信息与维护
当前仓库中expo-application版本为57.0.2(见 package.json),MIT 协议开源。近期版本迭代要点(见 CHANGELOG.md):
- 57.0.2(2026-07):iOS 侧新增暴露 embedded provisioning profile 的
expirationDate; - 57.0.1 / 57.0.0 / 56.x 系列:无用户可见变更,主要为版本同步与内部维护。
八、最佳实践建议
- Web 端判空:四个同步常量在 Web 上为
null,做版本展示或判断前先判空; - 平台 API 错误兜底:
getInstallReferrerAsync、getAndroidId等在受限环境(无 Play Store、模拟器)会抛错,务必 try/catch; - IDFV 不可依赖:
getIosIdForVendorAsync可能返回null且厂商 App 全卸载后会变化,不适合作为永久设备指纹; - 版本语义区分:对外展示用
nativeApplicationVersion,二进制唯一性判断用nativeBuildVersion; - 发布渠道判定:iOS 灰度/测试包识别优先使用
getIosApplicationReleaseTypeAsync,配合ApplicationReleaseType枚举分流,无需自维护构建标记。
结语
expo-application虽然 API 数量不多,但覆盖了应用元数据、安装信息、设备标识与发布渠道四大类运行时信息,三端行为语义清晰、实现可读性高。结合本文给出的平台差异与底层源码解读,你可以在版本判断、渠道归因、灰度与合规统计等场景中放心使用它。
【免费下载链接】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),仅供参考