Expo Application:在 React Native 运行时读取原生应用信息的完整指南
2026/9/10 3:15:11 网站建设 项目流程

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 还是开发构建;
  • 安装/更新时间统计:通过getInstallationTimeAsyncgetLastUpdateTimeAsync计算留存与更新周期。

模块采用 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-application

2.2 Bare React Native 项目

在裸 React Native 项目中,需要先确保已安装并配置好expo包(即完成 Expo Modules 的接入),然后再添加本模块:

npx expo install expo-application

expo-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.plistCFBundleShortVersionString
  • Web:null

3.2 nativeBuildVersion —— 内部构建号

console.log(Application.nativeBuildVersion); // 例如 "114"
  • Android:读取PackageInfo.versionCode(底层经getLongVersionCode兼容 API 28 前后的取值差异),即 app config 中android.versionCode的值;
  • iOS:读取Info.plistCFBundleVersion(独立构建中对应 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 不提供该 APIrejectERR_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):

枚举值数值含义
UNKNOWN0无法判定
SIMULATOR1模拟器构建
ENTERPRISE2企业分发(In-House)
DEVELOPMENT3开发构建
AD_HOC4Ad Hoc 分发
APP_STORE5App Store 正式版

底层判定逻辑在 Objective-C 的 EXProvisioningProfile.m 中,依据嵌入二进制内的embedded.mobileprovision内容逐步判断:

  1. 无 provisioning profile:模拟器返回SIMULATOR,真机返回APP_STORE
  2. profile 解析失败:返回UNKNOWN
  3. ProvisionsAllDevices为 true:企业分发 →ENTERPRISE
  4. 存在ProvisionedDevices(包含 UDID 列表):再按Entitlements.get-task-allow区分,为 true 是DEVELOPMENT(开发),否则是AD_HOC
  5. 其余情况: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;
  • 该信息可用于确认推送通知当前走的是沙箱环境还是生产环境,辅助排查推送配置问题。

五、平台差异速查表

APIAndroidiOSWeb
nativeApplicationVersionversionNameCFBundleShortVersionStringnull
nativeBuildVersionversionCodeCFBundleVersionnull
applicationNameloadLabelCFBundleDisplayNamenull
applicationIdpackageNameCFBundleIdentifiernull
getInstallationTimeAsyncfirstInstallTime✅ Documents 目录创建时间null
getLastUpdateTimeAsynclastUpdateTime❌ 抛 UnavailabilityError
getAndroidIdANDROID_ID❌ 抛 UnavailabilityError
getInstallReferrerAsync✅ Install Referrer API
getIosIdForVendorAsync✅ IDFV
getIosApplicationReleaseTypeAsync✅ 解析 mobileprovision
getIosPushNotificationServiceEnvironmentAsyncaps-environment

整体模式是:双端共用的基础元数据 + 各平台独占的设备标识/发布渠道能力。非目标平台调用时,JS 层统一通过UnavailabilityError(来自expo包)抛出明确错误(见 Application.ts 中各函数的平台守卫)。

六、原生调用链与模块结构一览

从模块结构看,expo-application的调用链清晰分层:

  1. JS 统一 API 层:src/Application.ts 负责导出常量与函数、做平台守卫、把原生返回值包装为Date,并 re-export 类型;
  2. 原生桥接层requireNativeModule('ExpoApplication')获取原生模块(见 src/ExpoApplication.ts);
  3. 平台实现层:Android 的 ApplicationModule.kt(ModuleDefinition中注册名为ExpoApplication的常量与 AsyncFunction)、iOS 的 ApplicationModule.swift;
  4. 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 系列:无用户可见变更,主要为版本同步与内部维护。

八、最佳实践建议

  1. Web 端判空:四个同步常量在 Web 上为null,做版本展示或判断前先判空;
  2. 平台 API 错误兜底getInstallReferrerAsyncgetAndroidId等在受限环境(无 Play Store、模拟器)会抛错,务必 try/catch;
  3. IDFV 不可依赖getIosIdForVendorAsync可能返回null且厂商 App 全卸载后会变化,不适合作为永久设备指纹;
  4. 版本语义区分:对外展示用nativeApplicationVersion,二进制唯一性判断用nativeBuildVersion
  5. 发布渠道判定: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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询