Android 通知
Android 应用接收 Firebase Cloud Messaging(FCM)数据消息。中继(relay)通过 FCM HTTP v1 直接发送这些消息;不需要 Expo Push 账号。
Android 兼容性与自动化检查
应用的最低要求是 Android 7.0(API 24),在 app.config.ts 中声明,并由中继(relay)的设备注册 schema 强制执行。编译/目标 SDK 版本跟随已锁定的 Expo/React Native 工具链(当前为 API 36)。通知渠道从 API 26 开始;通知权限提示从 API 33 开始。实时更新(Live Update)提升需要 API 36,并且仍然受系统设置和设备支持的限制。警报和普通活动卡片在 API 36 以下仍然可用。
在 Android 16 及以上版本中,打开 T3 Code 设置 → 实时更新设置,以允许状态栏芯片。Android 将此项与通知权限分开控制。芯片在工作期间显示 Working,审批时显示 Approve,输入请求时显示 Answer;工作完成后恢复为普通通知。请使用 Android 16 QPR2 或更高版本的模拟器镜像,验证已随版本发布的提升行为。
API 24–25 使用一次不精确的系统闹钟,在进程退出后使卡片过期,并且不使用精确闹钟权限。Android 可以在省电模式下延迟该闹钟。API 26 及以上使用通知超时。禁用活动、关闭通知、账号变更和退出登录都会取消这一旧版闹钟。过期的到期广播不能移除较新一次运行的卡片。
原生通知测试使用 Robolectric,覆盖 API 24、26、33 和 36(另加 API 25,用于旧版到期)。要编译该模块、运行这些测试并运行 Android lint,请在已生成的 apps/mobile/android 项目中使用下面的命令(command),且需有可用的 JDK 21。不需要 Firebase、签名或中继密钥:
./gradlew :t3-agent-notifications:testDebugUnitTest :t3-agent-notifications:lintRelease -Pandroid.lint.useK2Uast=falseRobolectric 的 API 36 运行时需要 JDK 21;模块编译仍然使用 Expo 的 Java 17 工具链。现有的 Mobile Native Static Analysis 作业会另行运行 ktlint 和 detekt。原生指纹检查会将此项变更标记为需要新的二进制文件;生产工作流无法通过 OTA 将它交付给较旧的二进制文件。如果已安装的原生模块缺少所需方法,设置会禁用 Android 通知。
lint 命令使用 K1 前端,因为 AGP 的 K2 前端在分析 Worklets 0.10 的 Gradle Kotlin 脚本时会崩溃。这并不会禁用 lint 检查。实时更新的资格必须在设备上验证:Robolectric 的 API 36 镜像实现的是较旧的提升规则,这些规则要求对通知着色;而随版本发布的实时更新要求使用未着色的通知。
Firebase 与应用构建
- 创建一个 Firebase 项目,并注册每一个打算构建的 Android 应用标识符:
com.t3tools.t3code.dev、com.t3tools.t3code.preview或com.t3tools.t3code。 - 下载
google-services.json。运行 Expo prebuild 以及构建应用时,将T3CODE_ANDROID_GOOGLE_SERVICES_FILE设为该文件的路径。该 JSON 必须包含所选变体的包标识符。 - 创建一份有权为该 Firebase 项目发送 FCM 消息的服务账号密钥。将这份私有 JSON 保存在仓库和应用包之外。
- 如果 Google 项目中尚未启用 Firebase Cloud Messaging API,请启用它。对于托管送达,将中继(relay)的
FCM_SERVICE_ACCOUNT密钥设置为该服务账号 JSON。 - 构建一个新的 Android 二进制文件。仅 JavaScript 的更新无法安装原生通知处理器或 Firebase 配置。托管送达还需要中继数据库迁移和更新后的中继部署;本地验证可以使用下面的监视器(watcher)。
对于本地开发构建,从 apps/mobile 执行:
APP_VARIANT=development \
T3CODE_ANDROID_GOOGLE_SERVICES_FILE=/absolute/path/google-services.json \
vp run android:dev对于 EAS 构建,请通过每一个选中的构建环境提供同一套配置,并使用名为 T3CODE_ANDROID_GOOGLE_SERVICES_FILE 的 EAS 文件变量来提供 Google services 文件。该文件必须对指纹生成和原生构建都可用。FCM 服务账号凭据属于中继,不属于 EAS 的应用环境。如果要部署单独的托管中继,请按照 T3 Connect 中的说明,为该中继和 Clerk 应用配置构建的 T3 Connect 公开设置。
在对私有二进制文件执行 prebuild 和打包之前设置 T3CODE_MOBILE_UPDATES_ENABLED=0,以禁用仓库中已配置的 Expo OTA 更新源。调试用的 development-client APK 需要 Metro;要在不使用 Expo 开发启动器的情况下验证冷启动时的通知点击,需要已打包的发布构建。
私有构建的 Clerk 登录
Clerk 的原生 Android 登录使用 clerk://<applicationId>.callback。在由构建的可发布密钥选定的 Clerk 实例中,其管理员必须在 原生应用 > 移动 SSO 重定向允许列表 下允许这一精确回调。对于开发包,请添加:
clerk://com.t3tools.t3code.dev.callback应用已经声明了匹配的回调接收器。出现 “redirect url ... does not match an authorized redirect URI” 错误时,需要更改 Clerk 配置;重新构建同一个 APK 并不能修复该问题。管理员保存该条目后,请重新打开登录。其他变体请参阅 Android 原生登录重定向。
使用 T3 现有的生产可发布密钥会选定维护者的 Clerk 实例。这并不授予更改该实例允许列表的权限。所选包的回调必须已经被允许,或由该实例的管理员添加。Android 设备注册和托管送达另外还需要下面的中继(relay)部署。直接配对或 FCM 冒烟测试成功,并不能验证托管登录或设备注册。
使用 APP_VARIANT=production 构建会选定 com.t3tools.t3code 及其对应的 Clerk 回调。请在 prebuild 和打包期间设置同一变体,并提供包含该包的 Google services 文件。私有二进制文件请保持 OTA 更新禁用。使用此包的本地签名构建不能更新由维护者签名的官方安装,也不能与之共存;移除该安装也会删除其应用本地数据。开发包仍然是一个独立的应用。
专项送达检查
infra/relay/scripts/android-push-smoke.ts 通过生产环境的 FCM 客户端实现发送一条消息,而无需预配中继(relay)的数据库、Clerk 集成或 Cloudflare 队列。它只验证从 Firebase 到设备的送达。
提供一份私有设备 JSON 文件,其中包含应用的原生 FCM token、已注册的 deviceId、已登录的 userId,以及 Android 的 packageName。可选的 deepLink 可以指向一条现有对话线程(thread),用于点击验证。应用必须已经注册其本地原生通知处理器,并拥有通知权限。从 infra/relay 执行:
vp run push:android:smoke /path/service-account.json /path/device.json running
vp run push:android:smoke /path/service-account.json /path/device.json approval
vp run push:android:smoke /path/service-account.json /path/device.json completed支持的状态为 running、approval、input、completed、failed 和 end。Firebase 接受消息并不能证明设备已经显示该消息。请检查实际通知,将应用切换到后台,并测试通知点击。还应测试关闭通知、禁用进行中的活动、退出登录、令牌轮换,以及应用进程退出后的送达。Android 设置中的 强制停止 会有意阻止送达,直到应用再次被打开。
应用处于前台时,Android 只抑制当前屏幕上那条对话线程的警报,这与 iOS 的通知呈现一致;其他对话线程的警报仍会显示。活动卡片在前台仍会更新,并静默保留已完成的结果。请确认:其对话线程打开时,完成保持安静;另一个屏幕打开时会发出警报;进入后台后会发出警报;重试被抑制的警报之后不会再显示它。这里依据的是接收端手机上的应用生命周期和路由,而不是其他客户端上该对话线程是否可见。
启用进行中的活动后,请验证两个对话线程同时进入审批/输入请求时只产生一条 2 agents need attention 警报,并且两个被观察的活跃对话线程同时完成或失败时只产生一条 2 agents finished 警报。正文会列出它们的标题。中继与 iOS 共用转换选择,并在工作结束时保留其已送达基线;再次发布相同状态不得再产生另一条警报。分组警报会打开该聚合的优先对话线程;单独的警报会保留各自的对话线程链接。
请验证一张展开的卡片,其中包含五个对话线程、关注/失败优先级、项目名称和状态。当所有工作完成时,卡片应显示 智能体工作已完成 或 智能体工作失败,去掉进行中/提升标志,并在最新显示的结果之后 15 分钟过期。重放不得延长该截止时间。安静运行的工作使用中继的两小时状态有效期;审批/输入请求状态使用 24 小时。重新打开同一个已登录的应用,确认现有警报和已关闭状态仍然保留,并在冷启动时,或至少 60 秒后回到前台时,进行一次静默的聚合重放。还应检查空重放会移除孤立卡片,并且完成已超过两分钟的工作永远不会发出警报,即使禁用了进行中的活动也是如此。
完成 Android prebuild 之后,从 apps/mobile/android 运行原生呈现回归测试:
./gradlew :t3-agent-notifications:testDebugUnitTest --tests expo.modules.t3agentnotifications.AgentNotificationsTest中继部署
使用现有 T3 服务进行本地验证
开发 Android 推送时,不必复制 T3 Connect 的托管基础设施。保留平时的 Clerk 登录和环境(environment)连接。infra/relay/scripts/android-push-watch.ts 订阅一个已配对环境的 shell 流,使用共享的智能体感知投影(projection),并通过新的 FCM 客户端发送更新。它将瞬态状态保存在内存中,不需要托管数据库或 Clerk 密钥。
创建一份私有的 connection.json,其中包含 wsUrl(该环境的 /ws URL)和 bearerToken(一条普通的已配对环境访问令牌)。请为这个监视器(watcher)使用单独的配对凭据。提供上文所述的同一份设备文件,然后从 infra/relay 运行:
vp run push:android:watch /path/service-account.json /path/device.json /path/connection.jsonAndroid 原生处理器必须已经配置了该设备和账号,并且必须允许通知。原生插桩测试框架可以在测试前配置一台一次性模拟器;已登录的开发应用会在设备注册期间配置该处理器。这个监视器是开发用传输:它观察其所配对环境中的全部未归档对话线程(thread),启用所有警报类型,不保留持久队列,并且必须保持运行。它不会向现有的托管中继(relay)注册 Android 设备。托管中继需要先完成下面的变更,其通知设置和送达才能端到端工作。
托管送达
现有的 Alchemy 部署默认会预配 Cloudflare Workers、送达队列、Hyperdrive、隧道/DNS 资源、PlanetScale Postgres 以及 Axiom 可观测性。它需要已启用服务的凭据和私有 Clerk 配置;仓库的公开应用设置并不授予部署权限。在仅 Android 的开发中继中设置 APNS_ENABLED=false,即可跳过 Apple 送达及其凭据要求。APNs 默认保持启用。
现有部署账号中的个人阶段
能够访问现有 Alchemy 状态和部署凭据的维护者,可以将 Android 变更部署到个人阶段(stage)。非生产阶段会引用由 prod 阶段拥有的保留数据库和 DNS 区域,创建单独的 PlanetScale 分支,并将迁移应用到该分支。因此,个人阶段并不是部署到无关账号中的独立部署。
将 Android 变更应用到一份带有现有部署凭据的检出。使用现有的 Cloudflare、PlanetScale、Axiom、域名和 Clerk 配置创建私有的
infra/relay/.env.android-dev,这些配置见 中继 README。使CLERK_PUBLISHABLE_KEY、CLERK_SECRET_KEY和CLERK_JWT_AUDIENCE与测试客户端(client)使用同一个 Clerk 实例。将 Firebase 服务账号 JSON 添加为
FCM_SERVICE_ACCOUNT,并为这个仅 Android 的阶段设置APNS_ENABLED=false。保持RELAY_DOMAIN不设置,以便部署为个人阶段派生主机名,而不是使用生产主机名。从仓库根目录查看部署计划,然后部署同一个阶段:
shvp run --filter t3code-relay deploy --stage dev_ryan_android --env-file .env.android-dev --dry-run vp run --filter t3code-relay deploy --stage dev_ryan_android --env-file .env.android-dev将已部署的中继 URL 和匹配的公开 Clerk 配置交给测试者。部署包装器还会将中继 URL 和公开追踪配置写入该检出根目录的
.env。使用这份T3CODE_RELAY_URL和现有的 Firebase Android 文件重新构建私有 APK,并禁用 OTA 更新。如果使用单独的开发包,请按上文为其 Clerk 回调授权。使用同一个中继 URL 配置一台隔离的 T3 服务器,并通过新中继链接该测试环境。现有的生产中继链接不会自动迁移到个人阶段。为测试环境启用活动发布,在手机上启用通知,并验证手机锁定时,一次真实的智能体回合(turn)会产生运行中更新和完成警报。
维护者可以自行完成部署,并只返回公开的客户端配置;测试者不需要他们的托管凭据或 Clerk 服务器凭据副本。完全独立的部署需要自己的初始 Cloudflare 技术栈、PostgreSQL 数据库、Firebase 项目,以及一个操作者可以配置的 Clerk 实例。其 Alchemy 部署需要 PlanetScale 和 Axiom 凭据。
使用相同的中继 URL 和 Clerk 公开配置构建主机客户端和移动应用。源服务器或桌面开发构建都可以托管该测试环境;请将其 T3 主目录与现有安装分开。仅在手机上登录并不会链接主机环境。请使用主机客户端的 T3 Connect 设置来链接它,并启用活动发布。私有 Clerk 实例在使用 t3 connect login 之前,还需要自己的 CLI OAuth 应用;仓库的生产 CLI 客户端 ID 属于维护者的实例。
要通过 GitHub Actions 部署,请将 FCM_SERVICE_ACCOUNT 添加到 production 环境的密钥中。中继工作流会将其传递给 Alchemy。维护者还必须在原生构建环境中,为生产 Android 包提供 google-services.json;仅更改中继密钥,无法将已安装的应用转移到另一个 Firebase 项目。
Android 送达使用 RelayFcmDeliveryQueue 以及一条单独的死信队列。失败的请求会重试;消息在五分钟后过期。发送之前,消费者会重新检查设备令牌、当前偏好、环境链接和当前对话线程状态。UNREGISTERED 响应只会使匹配的设备令牌失效。OAuth 令牌在 FCM 服务内缓存,并在授权失败后刷新。