PythonIDE Docs
中文
简体中文

background_download

原生后台下载、上传与持久任务中心。

background_download 是 iOS 原生后台文件传输与持久任务中心。它既能下载,也能把本地文件复制到宿主持有的安全 spool 后后台上传;任务离开 MiniApp 页面、结束本次 Python 运行,甚至 App 因系统事件重启后,仍可由同一个 MiniApp 查询和控制。

诚实的恢复边界:可恢复的是宿主的 URLSession 文件任务,不是 Python 调用栈、局部变量、Timer 或回调。新运行通过保存的 task_idbackground_download.list()miniapp_runtime.durable_tasks() 重新连接。旧运行会在记录中显示 origin_run_state="interrupted",宿主不会伪装继续执行旧 Python 代码。

声明式 miniapp.json 通常声明:

json
{
  "capabilities": ["network", "file_system"],
  "allowedNetworkHosts": ["downloads.example.com", "uploads.example.com"]
}

从系统分享页启动时,miniapp_runtime.launch_request() 返回的文件拥有本次运行的只读 host grant。upload() 会在该授权仍有效时立即复制到宿主 spool,因此无需长期持有外部文件 URL;联网能力和目标 host 仍必须通过检查。普通工作区文件和下载目标继续要求 file_system

#能力概览

能力行为
后台下载原生 background URLSessionDownloadTask,完成后校验并原子写入目标
后台上传只接受文件;先复制到宿主持有、禁止备份且受文件保护的 spool
暂停与恢复下载使用持久化 resumeData;服务端不支持时明确失败为 resume_unavailable
重试复用不可变能力租约与持久请求元数据;敏感 header 必须重新提供
任务中心同 MiniApp 可 status() / list();其他 MiniApp 即使猜中 ID 也只能得到 unknown
完整性可选 SHA-256;下载在落盘前校验,上传在 spool 提交前校验
响应元数据返回 HTTP status、安全响应 header、收发字节数;不保存响应体和 Cookie

状态机:

text
running ── pause() ──> pausing ── resumeData ──> paused ── resume() ──> running
   │                         └── 无 resumeData ──> failed(resume_unavailable)
   ├── 收完 ──> finalizing(校验/原子提交)──> completed
   ├── 错误 ──> failed ── retry() ──> running
   └── cancel() ──> cancelled

#快速开始:下载、暂停与恢复

python
import os
import time
import background_download

destination = os.path.join(os.path.expanduser("~/Documents"), "package.zip")
task_id = background_download.download(
    "https://downloads.example.com/package.zip",
    destination,
    headers={"Accept": "application/zip"},
    sha256="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
)

while True:
    task = background_download.status(task_id)
    print(task["state"], task["progress"], task["bytes_received"])
    if task["state"] in ("completed", "failed", "cancelled"):
        break
    time.sleep(0.5)

# pause() 是请求;轮询到 paused 后才代表 resumeData 已安全持久化。
# background_download.pause(task_id)
# background_download.resume(task_id)

#分享文件直接后台上传

python
import background_download
import miniapp_runtime

request = miniapp_runtime.launch_request(required=True)
item = request.first

task_id = background_download.upload(
    "https://uploads.example.com/import",
    item.path,
    method="POST",
    headers={"Authorization": "Bearer current-secret"},
)
print("后台任务:", task_id)

upload() 返回前,文件已复制到宿主 spool。之后分享授权失效、原文件移动或本次 MiniApp 退出,都不会让已提交上传失去源文件。成功或取消会删除 spool;失败时保留它供 retry() 使用,终态记录过期时再统一清理。

#重新连接持久任务

python
import miniapp_runtime
import background_download

for task in miniapp_runtime.durable_tasks(limit=50):
    print(
        task["task_id"],
        task["kind"],
        task["state"],
        task["origin_run_state"],
    )

failed = background_download.list(state="failed")
for task in failed:
    if task["can_resume"]:
        background_download.resume(task["task_id"])
    elif task["can_retry"] and not task["retry_requires_headers"]:
        background_download.retry(task["task_id"])

如果 retry_requires_headers 非空,表示这些自定义或敏感值从未写入 App 的任务记录:

python
background_download.retry(
    task_id,
    headers={"Authorization": "Bearer refreshed-secret"},
)

#API 参考

#download()

text
download(
    url,
    destination_path,
    task_id=None,
    *,
    method="GET",
    headers=None,
    sha256=None,
) -> str
  • 保留原有 download(url, destination_path, task_id) 写法。
  • method 支持 GETHEADPOSTPUTPATCHDELETE;模块不接受内存 request body。
  • headers 最多 64 项、总计 32 KiB;禁止 HostContent-Length 等 URLSession 管理字段。
  • sha256 是可选的 64 位十六进制摘要。校验失败不会替换已有目标文件。
  • 目标必须位于当前 MiniApp 可写范围。写入使用同目录 staging 与原子替换,并拒绝符号链接目标。

#upload()

text
upload(
    url,
    source_path,
    task_id=None,
    *,
    method="POST",
    headers=None,
    sha256=None,
) -> str
  • method 支持 POSTPUTPATCH
  • 只上传常规文件,不接受目录、符号链接或内存 bytes
  • source_path 在原始 capability lease 下复验并复制;spool 由宿主持有。
  • iOS 后台上传要求 file-backed body,因此这是正确的系统级实现,不是前台 async 请求伪装。

#status()list()

text
status(task_id) -> dict
list(*, state=None, kind=None, limit=100) -> list[dict]

status() 含最多 32 条 eventslist() 返回紧凑摘要。任务只对相同 owner_app_id 可见。终态记录保留约 30 天,最多保留 256 条;同时活动任务最多 64 条。

省略 task_id 会生成安全的 UUID,通常应采用这种方式。若业务必须自定义 ID,请使用足够唯一的值;宿主不会允许另一个 MiniApp 以同名 ID 覆盖现存记录。

主要字段:

字段说明
task_id / provider / kind逻辑任务 ID;任务中心 provider 为 background_transfer;类型为 downloadupload
state / progress当前状态;含校验/原子提交中的 finalizing;进度为 0.0–1.0,未知长度时可保持 0
owner_app_id任务所属 MiniApp;宿主任务为 None
origin_session_id创建任务的运行票据,不会因重试或重启变化
origin_run_statecurrentinterruptedhostinterrupted 明确表示旧 Python 栈已结束
path下载目标;上传不会暴露宿主 spool 路径
can_pause / can_resume / can_retry当前可用控制能力
retry_requires_headers重试前必须重新提供的自定义或敏感 header 名称;仅少量内容协商字段会持久化
http_status / response_headers最终响应元数据;Cookie、认证 challenge 等敏感字段被过滤
bytes_received / bytes_sent已收/发字节;也提供对应 expected 字段
expected_sha256 / sha256期望值与实际校验值(启用校验时)
attempt / retry_count / resume_count持久化执行统计
created_at / updated_at / last_retry_atUnix 时间戳
error_code / error稳定错误码与去敏后的说明
events有序、有界的状态历史,仅 status() 返回

#pause() / resume() / retry() / cancel()

text
pause(task_id) -> dict
resume(task_id) -> dict
retry(task_id, *, headers=None) -> dict
cancel(task_id) -> None
  • pause() 仅支持下载,并先进入 pausing。只有生成并持久化非空 resumeData 后才进入 paused
  • resume() 仅接受有可用 resumeData 的 paused / failed 下载;失效时抛出 resume_unavailable 并更新任务状态。
  • retry() 发起全新 attempt。上传失败会复用宿主 spool;下载失败或取消后重新开始。已取消上传释放了 spool,若要再次上传需创建新任务。敏感 header 不落盘,必须按提示重新提供。
  • cancel() 保持旧 API 的幂等语义:未知、跨 MiniApp、已终止或已经进入不可中断原子提交的 ID 是 no-op。

#安全与系统行为

  • 每个重定向都在创建任务时的不可变 capability lease 下重新验证。
  • HTTPS 到 HTTP 的降级重定向会被拒绝;跨 scheme/host/port 的重定向只保留少量内容协商字段,其余自定义或凭据 header 全部移除。
  • 持久 JSON 只保存少量可移植内容协商 request header,不含其他自定义/敏感值,不含上传 spool 路径的公开返回,也不保存响应体。
  • 为了能够在重启后重试,请求 URL(包括 query)会持久化。不要把长期凭据放进 URL;把凭据放在敏感 header 中,并在重试时重新提供。
  • HTTP 非 2xx 明确为 http_error;安全响应 header 和 status 仍可诊断。
  • iOS 决定后台调度时机、蜂窝网络和电量策略;“后台”不等于保证立即运行。
  • 模拟器可以验证 API 和状态机,但挂起、系统杀进程后恢复、网络切换与大文件必须在真机验收。

常见稳定错误码:

错误码含义与处理
network_denied / redirect_denied初始地址或重定向不符合原始网络授权;修改声明/host,不要盲目重试
http_error / network_error服务端非 2xx 或网络失败;查看 http_status,再决定 retry()
checksum_mismatch文件与期望 SHA-256 不一致;下载不会覆盖旧目标,上传不会启动
resume_unavailable服务端/系统没有可用 resumeData;改用 retry() 重新下载
sensitive_headers_required自定义或敏感 header 未落盘;按 retry_requires_headers 重新提供
upload_spool_unavailable / finalization_missing宿主持有的恢复文件已不存在;需要重新创建任务
task_missing重启时找不到原 URLSession 任务;记录诚实进入失败态
persistence_failed / insufficient_storage任务记录或 spool 无法安全落盘;释放空间后重试
reconciliation_pending冷启动正在与 iOS 后台会话重连;稍后再次调用控制 API
duplicate_task / too_many_tasksID 冲突或达到 64 个活动任务上限

#AppUI 使用建议

在按钮回调里创建和控制任务;不要在 body() 里发起副作用。把 task_id 存入业务状态,用 0.5 秒或更慢的 Timer 轮询;页面重建时也可从 miniapp_runtime.durable_tasks() 恢复展示。

#相关文档

文档用途
network前台 REST、流式响应和普通文件请求
background有限后台执行时间与 BGTask 调度,不是文件传输
MiniApp 原生能力capability、host allowlist、外部输入与运行隔离
storage保存业务侧 task_id 和 UI 偏好