使用 AppMigrationKit 实现应用数据的跨平台迁移(导出或导入)。适用于实现由系统协同调度的 iOS 与 Android 等平台间单次数据迁移、开发 AppMigrationExtension 扩展、使用 ResourcesArchiver 打包待传输资源、在目标设备上导入资源、上报导入进度、处理迁移错误与 App Group 清理、查询 MigrationStatus 状态,以及通过 AppMigrationTester 测试迁移代码等场景。
AppMigrationKit
用于应用资源的单次跨平台数据传输。支持应用在设备初始设置或首次引导(onboarding)过程中,与其它平台(例如 Android)之间互相导出或导入数据。AppMigrationKit API 适用于 iOS 26.0+ / iPadOS 26.0+;数据容器 Entitlement(权限)适用于 iOS 26.1+ / iPadOS 26.1+ / Mac Catalyst 26.1+。基于 Swift 6.3。
Beta 特性提醒: AppMigrationKit 是 iOS 26 的新增框架,在 GM(正式发布)版本前可能会有变动。在依赖具体 API 细节前,请务必核对 Apple 最新官方文档。
AppMigrationKit 采用 App Extension(应用扩展)架构模式,由系统统一协调和调度设备间的数据传输。应用只需提供一个实现导出/导入协议的 Extension,系统会在合适的时间自动调用该 Extension。应用本身无需也不直接管理设备之间的网络连接。
Contents
- 架构概览
- 配置与 Entitlements 权限
- App Migration Extension(迁移扩展)
- 导出资源
- 导入资源
- 迁移状态(Migration Status)
- 进度追踪
- 测试
- 常见错误
- 审查清单
- 参考资料
Architecture Overview
AppMigrationKit 主要由三个层级组成:
- App extension(应用扩展) -- 遵循
AppMigrationExtension协议的具体类型,在迁移过程中由系统调用,负责具体的数据导出与导入逻辑。 - 系统调度(System orchestration) -- 操作系统负责管理设备到设备之间的会话、网络传输以及运行调度。Extension 无法自主控制何时被启动执行。
- 宿主应用(Containing app) -- 迁移完成后,宿主应用在首次启动时检查
MigrationStatus.importStatus,用以确认迁移是否发生以及是否成功。
核心类型一览:
| 类型 | 职责说明 |
|---|---|
AppMigrationExtension |
App Extension 入口点协议 |
ResourcesExportingWithOptions |
支持自定义选项的资源导出协议(通过 archiver 打包) |
ResourcesExporting |
简化的资源导出协议(无自定义选项) |
ResourcesImporting |
目标设备上的资源导入协议 |
ResourcesArchiver |
流式写入文件到导出归档包中 |
MigrationDataContainer |
访问宿主应用的数据目录 |
MigrationStatus |
宿主应用用于检查导入结果状态 |
MigrationPlatform |
标识另一台设备的平台类型(例如 .android) |
MigrationAppIdentifier |
通过 App Store 和 Bundle ID 标识来源应用 |
AppMigrationTester |
仅用于测试的 Actor,可验证导出/导入逻辑 |
Setup and Entitlements
Entitlement 权限设置
App Extension 需要配置 com.apple.developer.app-migration.data-container-access entitlement。其值为一个仅包含单元素的字符串数组,填入宿主应用的 Bundle Identifier:
<key>com.apple.developer.app-migration.data-container-access</key>
<array>
<string>com.example.myapp</string>
</array>
填入其它任何值均无效。此 entitlement 授予 Extension 在导出阶段对宿主应用数据容器的读取权限,以及在导入阶段的写入权限。需要注意的是,尽管 AppMigrationKit 的核心 API 适用于 iOS 26.0+ 和 iPadOS 26.0+,但该 entitlement 本身要求 iOS 26.1+、iPadOS 26.1+ 和 Mac Catalyst 26.1+。
Extension Target 配置
在 Xcode 项目中新增一个 App Extension Target。该 Extension 需要实现一个或多个迁移协议(ResourcesExportingWithOptions、ResourcesExporting、ResourcesImporting)。
App Migration Extension
Extension 的入口点需遵守 AppMigrationExtension 协议。迁移进行期间,系统会阻止启动宿主应用及其其它 Extension,以确保数据访问的独占性。
访问数据容器
Extension 通过 appContainer 访问宿主应用的文件系统:
import AppMigrationKit
struct MyMigrationExtension: ResourcesExporting {
var resourcesSizeEstimate: Int { estimateTotalExportSize() }
var resourcesVersion: String { "1.0" }
var resourcesCompressible: Bool { true }
func exportResources(
to archiver: sending ResourcesArchiver,
request: MigrationRequest
) async throws {
let container = appContainer
// container.bundleIdentifier -- 应用的 Bundle ID
// container.containerRootDirectory -- 应用容器根目录
// container.documentsDirectory -- Documents/ 目录
// container.applicationSupportDirectory -- Application Support/ 目录
}
}
MigrationDataContainer 提供了 containerRootDirectory、documentsDirectory 和 applicationSupportDirectory 等 URL 属性,直接指向宿主应用的沙盒路径。
Exporting Resources
通过遵守 ResourcesExportingWithOptions 协议(无自定义选项时遵守 ResourcesExporting 协议)将待传输的文件进行打包。系统会传入 ResourcesArchiver 和 MigrationRequestWithOptions 并调用 exportResources(to:request:)。
声明导出属性
struct MyMigrationExtension: ResourcesExportingWithOptions {
typealias OptionsType = MigrationDefaultSupportedOptions
var resourcesSizeEstimate: Int {
// 返回预计导出的总字节数
calculateExportSize()
}
var resourcesVersion: String {
"1.0"
}
var resourcesCompressible: Bool {
true // 允许系统在传输过程中压缩文件
}
}
resourcesSizeEstimate-- 预估的总字节数(Byte)。系统以此在 UI 上展示进度并进行磁盘剩余空间检查。resourcesVersion-- 导出数据格式的版本标识字符串。目标导入端会收到此版本号,以便按版本处理数据兼容。resourcesCompressible-- 设为true时,传输归档器可在传输网络数据时自动压缩文件。
实现导出逻辑
func exportResources(
to archiver: sending ResourcesArchiver,
request: MigrationRequestWithOptions<MigrationDefaultSupportedOptions>
) async throws {
let docsDir = appContainer.documentsDirectory
// 根据需要检查目标设备平台
if request.destinationPlatform == .android {
// 针对特定平台的导出逻辑
}
// 逐个追加文件 -- 保持持续的进度更新
let userDataURL = docsDir.appending(path: "user_data.json")
try await archiver.appendItem(at: userDataURL)
// 使用自定义的归档内路径追加文件
let settingsURL = docsDir.appending(path: "settings.plist")
try await archiver.appendItem(at: settingsURL, pathInArchive: "preferences/settings.plist")
// 追加整个目录
let photosDir = docsDir.appending(path: "photos")
try await archiver.appendItem(at: photosDir, pathInArchive: "media/photos")
}
归档器使用流式方式增量处理文件。当每个资源准备好后,连续调用 appendItem(at:pathInArchive:)。系统会在判定 Extension 假死/无响应时将其强制终止,因此务必避免在两次追加调用之间产生过长的等待间隔。
取消操作处理
ResourcesArchiver 会通过抛出取消错误(cancellation errors)来自动处理任务取消。切勿捕获这些取消错误 -- 捕获会导致系统强行杀掉 Extension。
迁移平台(Migration Platform)
MigrationRequestWithOptions 暴露了 destinationPlatform 属性,类型为 MigrationPlatform。可据此针对不同目标平台量身定制导出格式:
if request.destinationPlatform == .android {
// 导出为 Android 端应用期望的数据格式
}
MigrationPlatform 预设了 .android 静态常量。自定义平台可以通过 MigrationPlatform("customPlatform") 进行构造。
Importing Resources
目标设备通过遵守 ResourcesImporting 协议来接收传输过来的文件。系统会在应用安装完成之后、但允许用户启动应用之前调用 importResources(at:request:)。
struct MyMigrationExtension: ResourcesImporting {
func importResources(
at importedDataURL: URL,
request: ResourcesImportRequest
) async throws {
let sourceVersion = request.sourceVersion
let sourceApp = request.sourceAppIdentifier
// sourceApp.platform -- 例如 .android
// sourceApp.bundleIdentifier -- 来源应用的 Bundle ID
// sourceApp.storeIdentifier -- 例如 .googlePlay
// 将导入的文件复制到宿主应用容器中
let docsDir = appContainer.documentsDirectory
let userData = importedDataURL.appending(path: "user_data.json")
if FileManager.default.fileExists(atPath: userData.path()) {
try FileManager.default.copyItem(
at: userData,
to: docsDir.appending(path: "user_data.json")
)
}
}
}
导入期间的错误处理
若导入过程中发生错误,系统将清空宿主应用的数据容器,以防止留有不完整的数据脏状态。然而,App Group(应用组)容器不会被系统自动清空。因此,导入实现逻辑在写入新内容前,应主动清除已有的 App Group 容器数据:
func importResources(
at importedDataURL: URL,
request: ResourcesImportRequest
) async throws {
// 首先清理共享的 App Group 数据
let groupURL = FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: "group.com.example.myapp"
)
if let groupURL {
try? FileManager.default.removeItem(at: groupURL.appending(path: "shared_data"))
}
// 然后执行导入
try await performImport(from: importedDataURL)
}
来源应用标识符(Source App Identifier)
ResourcesImportRequest 提供了 sourceAppIdentifier 属性,其类型为 MigrationAppIdentifier,包含三个核心字段:
platform-- 来源设备的平台(例如.android)bundleIdentifier-- 来源应用的 Bundle IDstoreIdentifier-- 应用商店标识(例如.googlePlay)
Migration Status
迁移完成后,宿主应用在首次启动时检查迁移结果:
import AppMigrationKit
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if let status = MigrationStatus.importStatus {
switch status {
case .success:
showMigrationSuccessUI()
MigrationStatus.clearImportStatus()
case .failure(let error):
showMigrationFailureUI(error: error)
MigrationStatus.clearImportStatus()
}
}
return true
}
- 如果未发生过迁移,
MigrationStatus.importStatus为nil。 - 处理完迁移结果后,务必调用
clearImportStatus(),避免后续每次打开应用时重复触发提示。 - 该 Enum 包含两个枚举值:
.success和.failure(any Error)。
Progress Tracking
导入端通过 resourcesImportProgress 暴露一个 Progress 对象。系统利用该对象向用户展示传输与导入进度。在导入过程中累进更新 completedUnitCount:
struct MyMigrationExtension: ResourcesImporting {
private let importProgress = Progress(totalUnitCount: 100)
var resourcesImportProgress: Progress { importProgress }
func importResources(
at importedDataURL: URL,
request: ResourcesImportRequest
) async throws {
let files = try FileManager.default.contentsOfDirectory(
at: importedDataURL, includingPropertiesForKeys: nil
)
let increment = Int64(100 / max(files.count, 1))
for file in files {
try processFile(file)
importProgress.completedUnitCount += increment
}
importProgres
<!-- truncated for translation batch; full body continues in source -->






