Android Instant Apps:停止服务后的历史架构与迁移方向

(更新于 )

状态更新(2026-09-21):Google Play Instant 已自 2025 年 12 月起停止提供:不能再通过 Google Play 发布 Instant Apps,Play 也不再向用户分发即时应用,相关 Google Play services Instant API 不再工作。下文保留历史模块化与路由设计供理解存量工程使用;新项目应采用常规应用,并按场景使用深层链接引导用户进入目标功能。 官方文档

⁉️ 重要提示:Google Play Instant (Instant Apps / 即时应用)已于 2025 年 12 月停止服务——Instant App 无法再通过 Google Play 发布,相关的 Google Play services Instant API 也已失效,Play 平台不再以任何方式向用户分发 Instant 体验(官方公告:https://developer.android.com/topic/google-play-instant/overview)。官方现在建议开发者直接用 Deep Link 引导用户到完整安装的 App。本文保留作为当时技术方案的历史记录与架构设计思路参考(App Bundle 切片、沙箱启动、URL 意图路由等思路对现今的动态特征交付仍有参考价值),但不要再基于本文方案做新项目开发。

2024 年接手一个电商 SDK 项目时,产品提了一个需求:用户点击商品链接后,不等安装就能完成下单。当时团队第一反应是「小程序」,但客户端体验差异太大。最后选了 Instant Apps,踩了一圈坑才跑通全链路。

App Bundle 切片:Instant App 的物理基础

Instant App 不是独立产物,它是 App Bundle 的一个「切片」。Google Play 通过 dynamic delivery 机制,从 Bundle 中按需抽取模块下发,而不是打包整个 APK。

一个支持 Instant 体验的项目通常包含三个层级:

app/              → 可安装应用(主入口)
feature/order/    → 订单功能模块(Instant 可访问)
base/             → 共享基础代码和资源

硬性约束:Instant 模块大小上限 10 MB。这个限制迫使你剥离一切非必要的东西——base 模块放共享依赖,feature 模块只保留单场景的最小实现。

base/build.gradle 中,旧版 AGP 用 com.android.feature + baseFeature true 声明这是基础切片;新版 AGP 中 base 走普通的 application/library 插件即可,feature 模块改用 com.android.dynamic-feature:

apply plugin: 'com.android.feature'

android {
    baseFeature true
    // ...
}

编译时 AGP 把每个 dynamic-feature 和 base 模块打包成 .apk 子产物,连同元数据写入 AAB。Google Play 运行时根据用户请求的 URL 路径,匹配对应的 feature 模块,只下发这一个切片。

实际编译时我碰到过一个麻烦:base 模块引用了 com.google.android.gms:play-services-location,这一个依赖就 3.2 MB。加上图片资源和 Kotlin 运行时,base 直接超了 6 MB。最后的办法是用 implementation 替代 api 精细化控制依赖传递,把图片资源挪到安装模块里,Instant 模块只用矢量图标,才压进限制。

免安装启动:从 Play 商店到 Activity 的三段跳

Instant App 的启动和普通 APK 路径不同,拆开看是三个阶段。

阶段一:Play 商店解析。 用户点击链接后,Play 商店检查 URL 是否命中某个 Instant App 的 intent-filter。命中后不会直接下载,先校验设备是否支持 Instant 体验——需要 Android 5.0+ 且 Google Play Services 版本足够新。

阶段二:切片下载与沙箱加载。 Play 商店只下载匹配的那个 dynamic-feature 切片加上 base 模块。下载完成后,系统在受限沙箱中启动进程,沙箱的限制包括:

  • 外部存储不可访问(READ_EXTERNAL_STORAGE 权限被忽略)
  • 后台服务启动受限(前台服务除外)
  • 无法创建后台下载任务
  • 应用无需预装,直接在线加载运行

阶段三:Activity 渲染。 系统创建 Application 实例并启动目标 Activity。用户看到界面时,整个流程通常在 3-5 秒内 完成——这是核心体验指标。

踩过的坑:Instant App 的 Application 类初始化时我做了 Firebase 远程配置拉取。在沙箱里 Firebase 初始化直接 crash,缺少 Google Play Services 的部分后台能力。解决方案是把 Firebase 初始化包裹在 InstantApps.isInstantApp(context) 的否定分支里:

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        if (!InstantApps.isInstantApp(this)) {
            FirebaseApp.initializeApp(this)
        }
    }
}

ContentProvider 的 onCreate 比 Application.onCreate 更早执行,Instant 模式下 Provider 里同样不能做重操作,否则启动时间从 3 秒飙升到 8 秒以上。

URL 意图路由:从链接到目标页面的分发中枢

Instant App 的 url-intent 路由是整个架构中最容易被低估的部分,它的设计直接影响免安装体验能否准确触达目标页面。

基础路由声明

在 AndroidManifest 中声明 intent-filter,把 URL pattern 映射到 Activity:

<activity android:name=".feature.order.OrderActivity">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data
            android:scheme="https"
            android:host="shop.example.com"
            android:pathPrefix="/order" />
    </intent-filter>
</activity>

android:autoVerify="true" 触发 Digital Asset Links 验证。需要在 https://shop.example.com/.well-known/assetlinks.json 部署验证文件:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.shop",
    "sha256_cert_fingerprints": ["XX:XX:XX:..."]
  }
}]

验证失败时用户每次点击链接都会弹出选择对话框,从「零等待」变成「先选再等」,体验直接崩塌。

深层路由分发器设计

单个 Activity 只能处理固定路径。实际场景中同一 feature 模块内可能有多个页面目标,需要一套路由分发器:

class InstantRouter {

    fun resolve(uri: Uri): RouteTarget? {
        val path = uri.path ?: return null

        return when {
            path.startsWith("/order/") -> RouteTarget(
                activity = OrderActivity::class.java,
                params = mapOf("orderId" to uri.lastPathSegment)
            )
            path.startsWith("/product/") -> RouteTarget(
                activity = ProductActivity::class.java,
                params = mapOf("productId" to uri.lastPathSegment)
            )
            else -> RouteTarget(activity = MainActivity::class.java)
        }
    }
}

只给 Instant 模式启用的路由,参数传递要精打细算——Instant App 的 Intent 分发链路比完整安装版长,参数过多会影响解析效率。我这里把参数严格控制在 3 个 key 以内。

Instant 到安装版的升级跳转,需要调用官方提供的安装提示 API。需要注意的是,getInstantAppClient().getInstallIntentFilter() 这个写法并不存在——官方提供的是 InstantApps.showInstallPrompt() 这样一个静态方法,传入 Activity、目标 Intent 以及一个请求码,系统会弹出安装引导弹框,用户确认后跳转到 Play 商店完成安装,安装完成后通过 onActivityResult 回调获得结果。调用时机应选在用户触发关键操作前(如下单支付),而不是页面刚打开就彈窗。

实践中的取舍

做完这个项目,我对 Instant App 的判断很明确:适合轻量级交易型场景,不适合内容型或重交互场景。

10 MB 限制是最硬的约束。 ProGuard/R8 不是可选项,是必须的。测试时一个中等复杂度的订单页面,引入 Retrofit + Gson + OkHttp 后,DEX 已经占了 2.8 MB。加几张 WebP 图片轻松破壁。最后拆了一个极薄的网络层,只用 HttpURLConnection + 手动 JSON 解析,省了近 1.6 MB。

测试链路长。 Instant App 必须通过 Play Console 的内部测试轨道分发,不能直接用 adb install。每次调试都要走「修改代码 → 打 AAB → 上传 Play Console → 生成预发布链接 → 手机扫码测试」。我把 CI 流程抽象成三步:自动打 aab → 用 bundletool 本地提取 Instant APK → bundletool build-apks 生成设备专用包,本地调试直接拿这个包,省掉上传 Play 的等待。

Instant 模块是独立 APK 但共享进程。 多 feature 模块之间的数据传递不能依赖单例——不同切片在不同时机进入进程,单例状态不可靠。最终选了 SharedPreferences + 内存 LRU 缓存的组合,不做跨进程假设。

做完 Instant App 适配后,用户从点链接到看到订单页的时间从「下载完整 APK」的 40+ 秒压到了 4 秒左右,转化率从 8% 提升到 19%。代价是模块拆分增加了约 20% 的架构设计工作量。如果你维护的是交易链路核心页面,这点投入值得;如果只是做一个产品介绍页,Deep Link 够用了。

Android 动态模块化交付全链路:从 App Bundle 构建到 SplitCompat 运行时加载的按需分发架构

深入解析 Android 动态模块化交付全链路,涵盖 App Bundle 构建拆分、SplitCompat ClassLoader 注入机制、Play Feature Delivery 按需分发,以及包体积优化的实战踩坑与收益。

SmartDependency 源码/AAR 双模式依赖体系:让模块化工程既快又稳

在大型 Android 工程中,模块数量增长后依赖方式直接影响研发效率。本文介绍一种源码/AAR 双模式依赖体系,通过统一注册、配置切换、版本治理和 CI 约束,让开发者按需打开源码模块,同时保证发布时回归二进制真实形态。

深入 Android Gradle Version Catalog 与 Convention Plugins 全链路:从 buildSrc 单体到声明式依赖治理的模块化构建架构演进

介绍 Android 30+ 模块项目从 buildSrc 向 Version Catalog + Convention Plugins 迁移的实践经验,涵盖声明式依赖治理、预编译脚本分发及踩坑总结。

大型 App 架构演进与模块化、组件化实践

随着业务的飞速发展和团队规模的扩张,许多成功的 Android 应用从最初的小型项目逐渐演变成拥有数百万行代码、由数十甚至数百名开发者共同维护的庞然大物。在这种规模下,曾经简单有效的单体架构(Monolithic Architecture)会逐渐暴露出其固有的弊端,成为制约开发效率、代码质量和业务迭代速度的瓶颈。构建时间指数级增长、代码耦合日益严重、牵一发而动全身的恐惧、团队协作的冲突与等待...