本文档于 2022 年 8 月存档,不会收到任何进一步更新。请改用 EAS 更新。了解更多
¥This doc was archived in August 2022 and will not receive any further updates. Please use EAS Update instead. Learn more
expo-updates
模块提供了用于在 React Native 应用中加载更新的客户端实现。更新允许你将新的 JavaScript 和资源部署到应用的现有版本,而无需构建新的二进制文件。
¥The expo-updates
module provides a client-side implementation for loading updates in React Native apps. Updates allow you to deploy new JavaScript and assets to existing builds of your app without building a new binary.
在本指南中,更新是指单个原子更新,它可能包含 JavaScript 包、其他资源(例如图片或字体)以及有关更新的元数据。
¥In this guide, an update refers to a single, atomic update, which may consist of a JavaScript bundle, other assets (such as images or fonts), and metadata about the update.
¥Setup
如果可能,我们强烈建议从已安装 expo-updates
库的样板项目开始,例如,通过运行:npx create-expo-app -t bare-minimum
。
¥If possible, we highly recommend starting with a boilerplate project that has the expo-updates
library already installed, for example, by running: npx create-expo-app -t bare-minimum
.
要在现有的裸工作流程应用中安装 expo-updates
模块,请遵循 安装说明。
¥To install the expo-updates
module in an existing bare workflow app, follow the installation instructions.
此外,你需要将更新及其各自的资源(JavaScript 包、图片、字体等)托管在已部署的客户端应用可以访问的服务器上。expo-cli
为此提供了几个简单的选项:(1)expo export
创建预构建的更新包,你可以将其上传到任何静态托管站点(例如 GitHub Pages),并且 (2) expo publish
将你的更新打包并部署到 Expo 的更新服务,这是我们提供的服务的一部分。
¥Additionally, you'll need to host your updates and their respective assets (JavaScript bundles, images, fonts, and so on) on a server somewhere that deployed client apps can access. expo-cli
provides a couple of easy options for this: (1) expo export
creates prebuilt update packages that you can upload to any static hosting site (for example, GitHub Pages), and (2) expo publish
packages and deploys your updates to Expo's updates service, which is part of the services we offer.
你还可以运行自己的服务器来托管更新,前提是它符合 expo-updates
期望的协议。你可以在下面阅读有关这些要求的更多信息。
¥You can also run your own server to host your updates, provided it conforms to the protocol expo-updates
expects. You can read more about these requirements below.
¥Served update requirements
如果你使用的是
expo export
或expo publish
,欢迎你跳过本节,因为我们会为你处理好!¥If you're using
expo export
orexpo publish
, you're welcome to skip this section as it will be taken care of for you!
expo-updates
实现需要一个 URL(在构建时提供),它将向该 URL 发出新更新请求。当用户在生产中启动你的应用(取决于你的应用的配置设置)以及你的应用调用 Updates.fetchUpdateAsync()
时,可能会发生这些请求。请求将使用以下标头发送:
¥The expo-updates
implementation requires a single URL (provided at build-time) to which it will make requests for new updates. These requests may happen when users launch your app in production (depending on your app's configuration settings) and when your app calls Updates.fetchUpdateAsync()
. Requests will be sent with the following headers:
对这些请求的响应应该是一个清单 JSON 对象,其中包含有关与请求应用二进制文件兼容的最新更新的元数据。(下面有更多关于兼容性的信息。)清单应至少包含以下字段:
¥The response to these requests should be a manifest JSON object with metadata about the latest update that's compatible with the requesting app binary. (More on compatibility below.) The manifest should have at least the following fields:
密钥 | 类型 | 描述 |
---|---|---|
releaseId | string | 唯一标识此更新的 UUID。 |
commitTime | string | 表示提交/发布此更新的时间的 JavaScript 日期字符串。这用于比较两个更新以确定哪个是最新的。 |
runtimeVersion | object | 具有键 ios 和 android 的对象,其对应值是与此更新兼容的 运行时版本。仅当未提供 sdkVersion 时才需要。 |
sdkVersion | string | 此更新使用的 Expo SDK 版本。仅当未提供 runtimeVersion 时才需要。 |
bundleUrl | string | 指向此元数据所代表的 JavaScript 包的 URL。 |
bundledAssets | array | 作为此更新的一部分要下载的资源文件名数组。 |
assetUrlOverride | string | 用于解析 bundledAssets 中列出的所有文件名的基本 URL。 |
expo-updates
假设资源和 JavaScript 包的 URL 是不可变的;也就是说,如果它已经在给定 URL 下载了资源或打包包,则不会尝试重新下载。因此,如果你更改更新中的任何资源,则必须将它们托管在不同的 URL 上。
¥expo-updates
assumes that URLs for assets and JavaScript bundles are immutable; that is, if it has already downloaded an asset or bundle at a given URL, it will not attempt to re-download. Therefore, if you change any assets in your updates, you must host them at a different URL.
如果你使用 expo export
创建更新的预构建包,则 ios-index.json
和 android-index.json
中的清单满足这些要求。如果你使用 expo publish
,你可以发布到 Expo 的更新服务,该服务会根据每个更新请求动态创建这些清单对象。
¥If you use expo export
to create a prebuilt package of your update, the manifests in ios-index.json
and android-index.json
satisfy these requirements. Expo's update service, which you publish to if you use expo publish
, dynamically creates these manifest objects upon each update request.
¥Update compatibility
更新的一个关键考虑因素是 JavaScript 包和原生运行时(给定二进制文件中存在的原生模块及其导出的方法)之间的兼容性。为了说明这一点,请考虑以下示例:
¥A critical consideration with updates is compatibility between the JavaScript bundle and the native runtime (the native modules present in a given binary and the methods they export). To illustrate, consider the following example:
假设你的应用有一个现有版本(版本 A)在生产环境中运行。构建 A 运行 JavaScript 包版本 1,一切顺利。在应用的下一个版本中,你需要一些新功能,因此在开发过程中你需要安装一个新的原生模块(例如 expo-media-library
),并使用它的一些功能。你创建应用的构建 B,其中包含 MediaLibrary
原生模块。构建 B 运行调用 MediaLibrary.getAlbumsAsync()
的 JavaScript 包版本 2,并且可以正常工作。
¥Say you have an existing build, build A, of your app running in production. Build A runs JavaScript bundle version 1 and everything works smoothly. In the next version of your app, you need some new functionality, so in development you install a new native module like expo-media-library
, and use some of its functions. You create build B of your app which includes the MediaLibrary
native module. Build B runs JavaScript bundle version 2 which calls MediaLibrary.getAlbumsAsync()
, and this works.
但是,如果应用的构建 A 获取 JavaScript 版本 2 作为更新并尝试运行它,则在 MediaLibrary.getAlbumsAsync()
方法调用上将会出错,因为构建 A 中不存在 MediaLibrary
原生模块。如果你的 JavaScript 没有捕获此错误,它将传播并且你的应用将崩溃,从而导致 JavaScript 版本 2 在应用的构建 A 上无法使用。
¥However, if build A of your app fetches JavaScript version 2 as an update and tries to run it, it will error on the MediaLibrary.getAlbumsAsync()
method call because the MediaLibrary
native module is not present in build A. If your JavaScript doesn't catch this error, it will propagate and your app will crash, rendering JavaScript version 2 unusable on build A of your app.
我们需要某种方法来阻止 JavaScript 版本 2 被部署到构建 A - 或者,一般来说,控制将哪些更新部署到应用的特定版本。expo-updates
提供了两种控制方式:运行时版本和发布渠道。
¥We need some way of preventing JavaScript version 2 from being deployed to build A - or, in general, controlling which updates are deployed to specific builds of your app. expo-updates
provides two ways to control this: Runtime Version and Release Channels.
¥Runtime version
托管在你自己的服务器上的更新可以利用称为运行时版本的概念。运行时版本表示原生 JavaScript 接口或原生模块及其导出的方法的版本控制方案。换句话说,每当你对原生模块层进行更改(例如添加、删除或更新原生模块)时,你都会增加运行时版本号。
¥Updates hosted on your own server can make use of a concept called Runtime Version. Runtime Version represents a versioning scheme for the native-JavaScript interface, or the native modules and the methods they export. In other words, anytime you make a change to your native module layer, such as adding, removing, or updating a native module, you would increment the Runtime Version number.
特定二进制文件的运行时版本应在构建时配置(参见下面的 配置选项)。配置的运行时版本将包含在从该二进制文件发送的每个更新请求的标头中。服务器应使用此标头来选择适当的更新来提供响应。
¥The Runtime Version of a particular binary should be configured at build time (see Configuration Options below). The configured Runtime Version will be included in the header of every update request sent from that binary. The server should use this header to select an appropriate update to serve in response.
给定更新所需的运行时版本还必须作为返回给 expo-updates
的清单中的字段 (runtimeVersion
) 提供。expo-updates
跟踪其已下载的所有更新的运行时版本;这样,如果用户通过 App Store 更新其应用二进制文件,它就不会尝试运行之前下载的且新近不兼容的更新。
¥The Runtime Version expected by a given update must also be provided as a field (runtimeVersion
) in the manifest returned to expo-updates
. expo-updates
keeps track of the Runtime Version of all updates it has downloaded; this way, if a user updates their app binary through the App Store, it will not attempt to run a previously downloaded and newly incompatible update.
¥Release channels
由于 Expo 更新服务的当前实现在很大程度上依赖于 SDK 版本(托管工作流概念),因此如果你使用 expo publish
,则还无法使用运行时版本来管理更新和二进制文件的兼容性。相反,你可以使用 发布渠道。典型的工作流程是通过使用 expo publish --release-channel <channel-name>
发布到新的发布通道,为你构建的每个新二进制文件(或至少每个在原生 JavaScript 接口中进行不兼容更改的新二进制文件)创建一个新的发布通道。创建配置了此发布通道名称的构建后,你可以继续将未来的更新发布到同一发布通道,只要它们与该构建保持兼容即可。只有配置为使用该发布通道的构建才会收到这些更新。
¥Because the current implementation of the Expo updates service relies heavily on SDK version (a managed-workflow concept), if you're using expo publish
you cannot yet use Runtime Version to manage compatibility of your updates and binaries. Instead, you can use release channels. A typical workflow would be to create a new release channel for each new binary you build (or at least every new binary with an incompatible change in the native-JavaScript interface) by publishing to that new release channel with expo publish --release-channel <channel-name>
. After creating a build with this release channel name configured, you can continue to publish future updates to this same release channel as long as they remain compatible with that build. Only builds that were configured to use that release channel will receive those updates.
¥Statically hosted updates
由于 expo-updates
在请求中发送的标头不会影响静态托管的更新(例如 expo export
创建的更新包),因此你必须在不同的静态 URL 托管不兼容的更新以控制兼容性。
¥Since headers sent in requests by expo-updates
do not affect statically hosted updates (such as update packages created by expo export
), you must host incompatible updates at different static URLs to control compatibility.
¥Embedding assets
除了从远程服务器加载更新之外,安装了 expo-updates
的应用还包括加载嵌入在应用二进制文件中的更新的必要功能。这对于确保你的应用可以在安装后立即为所有用户离线启动而无需互联网连接至关重要。
¥In addition to loading updates from remote servers, apps with expo-updates
installed also include the necessary capability to load updates embedded in the app binary. This is critical to ensure that your app can launch offline for all users immediately upon installation, without needing an internet connection.
当你对应用进行发布构建时,构建过程会将你的 JavaScript 源代码打包到一个缩小的包中,并将其与你的应用导入的任何其他资源一起嵌入到二进制文件中(使用 require
或 import
或在 app.json 中使用) 。expo-updates
在每个平台上都包含一个额外的脚本,用于嵌入一些有关嵌入资源的附加元数据 - 即用于更新的最小清单 JSON 对象。
¥When you make a release build of your app, the build process will bundle your JavaScript source code into a minified bundle and embed this in the binary, along with any other assets your app imports (with require
or import
or used in app.json). expo-updates
includes an extra script on each platform to embed some additional metadata about the embedded assets -- namely, a minimal manifest JSON object for the update.
¥Including assets in updates
你在 JavaScript 源中导入的资源也可以作为已发布更新的一部分自动下载。expo-updates
不会考虑更新 "ready",并且不会启动更新,除非它已经下载了所有必需的资源。
¥Assets that you import in your JavaScript source can also be atomically downloaded as part of a published update. expo-updates
will not consider an update "ready" and will not launch the update unless it has downloaded all required assets.
如果你在项目中使用 expo-asset
(如果安装了 expo
软件包,则默认包含),你可以通过使用 app.json 中的 assetBundlePatterns
键提供路径列表来控制将哪些导入的资源包含在此原子更新中 在你的项目目录中:
¥If you use expo-asset
in your project (included by default if you have the expo
package installed), you can control which imported assets will be included as part of this atomic update by using the assetBundlePatterns
key in app.json to provide a list of paths in your project directory:
路径与给定模式匹配的资源将在使用它们的更新启动之前由客户端预先下载。如果你有一个资源应该在运行时而不是在 JavaScript 评估之前延迟下载,你可以使用 assetBundlePatterns
来排除它,同时仍然将其导入到 JavaScript 源中。
¥Assets with paths matching the given patterns will be pre-downloaded by clients before the update that uses them will launch. If you have an asset that should be lazily downloaded at runtime rather than before your JavaScript is evaluated, you can use assetBundlePatterns
to exclude it while still importing it in your JavaScript source.
请注意,要成功使用 expo-asset
,你必须在创建 JavaScript 打包包时使用 --assetPlugins
选项为 Metro 打包程序提供 node_modules/expo-asset/tools/hashAssetFiles
插件。如果你使用 expo export
或 expo publish
创建更新,系统会自动为你完成此操作。
¥Note that to use expo-asset
successfully, you must use the --assetPlugins
option to provide the Metro bundler with the node_modules/expo-asset/tools/hashAssetFiles
plugin when you create your JavaScript bundle. If you use expo export
or expo publish
to create your update, this will be done automatically for you.
¥Configuration options
一些构建时配置选项可用于控制 expo-updates
库的各种行为。你可以设置托管应用的 URL、设置兼容性/版本信息,以及选择应用是否应在启动时自动更新。
¥Some build-time configuration options are available to control various behaviors of the expo-updates
library. You can set the URL where your app is hosted, set compatibility/version information, and choose whether your app should update automatically on launch.
在 iOS 上,这些属性在 Expo.plist 中设置为键,在 Android 上设置为 AndroidManifest.xml 中的 meta-data
标记,与安装期间添加的标记相邻。
¥On iOS, these properties are set as keys in Expo.plist and on Android as meta-data
tags in AndroidManifest.xml, adjacent to the tags added during installation.
在 Android 上,你还可以在运行时通过传递 Map
作为 UpdatesController.initialize()
的第二个参数来定义这些属性。如果提供,此 Map 中的值将覆盖 AndroidManifest.xml 中指定的任何值。在 iOS 上,你可以在运行时通过在调用 start
或 startAndShowLaunchScreen
之前调用 [UpdatesController.sharedInstance setConfiguration:]
来设置这些属性,并且该字典中的值将覆盖 Expo.plist。
¥On Android, you may also define these properties at runtime by passing a Map
as the second parameter of UpdatesController.initialize()
. If provided, the values in this Map will override any values specified in AndroidManifest.xml. On iOS, you may set these properties at runtime by calling [UpdatesController.sharedInstance setConfiguration:]
at any point before calling start
or startAndShowLaunchScreen
, and the values in this dictionary will override Expo.plist.
iOS plist/字典键 | Android 地图键 | Android 元数据名称 | 默认 | 必需的? |
---|---|---|---|---|
EXUpdatesEnabled | enabled | expo.modules.updates.ENABLED | true | ❌ |
是否启用更新。将其设置为 false
将禁用所有更新功能、所有模块方法,并强制应用加载打包到应用二进制文件中的清单和资源。
¥Whether updates are enabled. Setting this to false
disables all update functionality, all module methods, and forces the app to load with the manifest and assets bundled into the app binary.
iOS plist/字典键 | Android 地图键 | Android 元数据名称 | 默认 | 必需的? |
---|---|---|---|---|
EXUpdatesURL | updateUrl | expo.modules.updates.EXPO_UPDATE_URL | (没有任何) | ✅ |
应用应检查更新的远程服务器的 URL。对此 URL 的请求应返回最新可用更新的有效清单对象,该对象告诉 expo-updates 如何获取包含更新的 JS 包和其他资源。(例如:对于使用 expo publish
发布的应用,此 URL 将为 https://exp.host/@username/slug
。)
¥The URL to the remote server where the app should check for updates. A request to this URL should return a valid manifest object for the latest available update that tells expo-updates how to fetch the JS bundle and other assets that comprise an update. (Example: for apps published with expo publish
, this URL would be https://exp.host/@username/slug
.)
iOS plist/字典键 | Android 地图键 | Android 元数据名称 | 默认 | 必需的? |
---|---|---|---|---|
EXUpdatesSDKVersion | sdkVersion | expo.modules.updates.EXPO_SDK_VERSION | (没有任何) | (需要 sdkVersion 或 runtimeVersion 之一) |
要在清单请求中的 Expo-SDK-Version
标头下发送的 SDK 版本字符串。对于 Expo 服务器上托管的应用是必需的。
¥The SDK version string to send under the Expo-SDK-Version
header in the manifest request. Required for apps hosted on Expo's server.
iOS plist/字典键 | Android 地图键 | Android 元数据名称 | 默认 | 必需的? |
---|---|---|---|---|
EXUpdatesRuntimeVersion | runtimeVersion | expo.modules.updates.EXPO_RUNTIME_VERSION | (没有任何) | (需要 sdkVersion 或 runtimeVersion 之一) |
要在清单请求中的 Expo-Runtime-Version
标头下发送的运行时版本字符串。
¥The Runtime Version string to send under the Expo-Runtime-Version
header in the manifest request.
iOS plist/字典键 | Android 地图键 | Android 元数据名称 | 默认 | 必需的? |
---|---|---|---|---|
EXUpdatesReleaseChannel | releaseChannel | expo.modules.updates.EXPO_RELEASE_CHANNEL | default | ❌ |
要在清单请求中的 Expo-Release-Channel
标头下发送的发布通道字符串。
¥The release channel string to send under the Expo-Release-Channel
header in the manifest request.
iOS plist/字典键 | Android 地图键 | Android 元数据名称 | 默认 | 必需的? |
---|---|---|---|---|
EXUpdatesCheckOnLaunch | checkOnLaunch | expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH | ALWAYS | ❌ |
在这种情况下,expo-updates
应在应用启动时自动检查(并下载,如果存在)更新。可能的值为 ALWAYS
、NEVER
(如果你想通过此模块的 JS API 专门控制更新)、WIFI_ONLY
(如果你希望应用仅在设备启动时处于不限流量的 Wi-Fi 连接上时自动下载更新),或 ERROR_RECOVERY_ONLY
(如果你希望应用仅在启动时遇到致命错误时自动下载更新)。
¥The condition under which expo-updates
should automatically check for (and download, if one exists) an update upon app launch. Possible values are ALWAYS
, NEVER
(if you want to exclusively control updates via this module's JS API), WIFI_ONLY
(if you want the app to automatically download updates only if the device is on an unmetered Wi-Fi connection when it launches), or ERROR_RECOVERY_ONLY
(if you want the app to automatically download updates only if it encounters a fatal error when launching).
无论此设置的值如何,只要启用了更新,你的应用始终可以在应用运行时使用 JS API 在后台手动检查和下载更新。
¥Regardless of the value of this setting, as long as updates are enabled, your app can always use the JS API to manually check for and download updates in the background while your app is running.
iOS plist/字典键 | Android 地图键 | Android 元数据名称 | 默认 | 必需的? |
---|---|---|---|---|
EXUpdatesLaunchWaitMs | launchWaitMs | expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS | 0 | ❌ |
毫秒数 expo-updates
应延迟应用启动并在尝试下载更新时停留在启动屏幕上,然后再回退到之前下载的版本。将此设置为 0
将导致应用始终使用先前下载的更新启动,并导致应用启动速度最快。
¥The number of milliseconds expo-updates
should delay the app launch and stay on the splash screen while trying to download an update, before falling back to a previously downloaded version. Setting this to 0
will cause the app to always launch with a previously downloaded update and will result in the fastest app launch possible.
一些常见的配置模式解释如下:
¥Some common configuration patterns are explained below:
¥Automatic updates
默认情况下,当用户从关闭状态打开你的应用时,expo-updates
将立即启动你的应用以及之前下载(或嵌入)的更新。它还会在后台异步检查更新,并尝试获取最新发布的版本。如果有新的更新可用,expo-updates
将尝试下载它并使用 events 通知正在运行的 JavaScript 其成功或失败。当用户下次滑动关闭并重新打开应用时,将启动新获取的更新;如果你想更快地运行它,你可以在适当的时候在应用代码中调用 Updates.reloadAsync
。
¥By default, expo-updates
will immediately launch your app with a previously downloaded (or embedded) update when a user opens your app from being closed. It will additionally check for updates asynchronously in the background, and will try to fetch the latest published version. If a new update is available, expo-updates
will try to download it and notify the running JavaScript of its success or failure using events. A newly fetched update will be launched next time the user swipes closed and reopens the app; if you want to run it sooner, you can call Updates.reloadAsync
in your application code at an appropriate time.
你还可以使用 launchWaitMs
设置将 expo-updates
配置为在用户打开应用时等待特定的时间来启动。如果在此时间内可以下载新的更新,则新的更新将立即启动,而不是等待用户滑动关闭并重新打开应用。(但请注意,如果用户的网络连接速度较慢,你的应用可能会在启动屏幕上延迟 launchWaitMs
毫秒,因此我们建议对此设置持保守态度,除非对于用户来说拥有最新更新至关重要 每次触发时。)如果没有可用的更新,一旦 expo-updates
能够确定这一点,就会启动之前下载的更新。
¥You may also configure expo-updates
to wait a specific amount of time to launch when a user opens the app by using the launchWaitMs
setting. If a new update can be downloaded within this time, the new update will be launched right away, rather than waiting for the user to swipe closed and reopen the app. (Note, however, that if users have a slow network connection, your app can be delayed on the launch screen for as many milliseconds as launchWaitMs
, so we recommend being conservative with this setting unless it's critically important for users to have the most recent update on each launch.) If no update is available, a previously downloaded update will be launched as soon as expo-updates
is able to determine this.
如果你希望仅当用户使用 Wi-Fi 连接时才发生此自动更新行为,你可以将 checkOnLaunch
设置设置为 WIFI_ONLY
。
¥If you want this automatic update behavior to occur only when your users are on a Wi-Fi connection, you can set the checkOnLaunch
setting to WIFI_ONLY
.
¥Manual updates
还可以关闭这些自动更新,并完全在 JS 代码中控制更新。如果你想要一些有关获取更新的自定义逻辑(例如,仅当用户在 UI 中执行特定操作时),这是理想的选择。
¥It's also possible to turn off these automatic updates, and to instead control updates entirely within your JS code. This is desirable if you want some custom logic around fetching updates (for example, only when users take a specific action in your UI).
将 checkOnLaunch
设置为 NEVER
将阻止 expo-updates
在每次启动应用时自动获取最新更新。仅加载打包包的最新缓存版本。
¥Setting checkOnLaunch
to NEVER
will prevent expo-updates
from automatically fetching the latest update every time your app is launched. Only the most recent cached version of your bundle will be loaded.
然后,你可以使用此库中包含的 expo-updates
模块下载新更新,并在适当的情况下通知用户并重新加载体验。
¥You can then use the expo-updates
module included with this library to download new updates and, if appropriate, notify the user and reload the experience.
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
// ... notify user of update ...
Updates.reloadAsync();
}
} catch (e) {
// handle or log error
}