优化 Android SDK 中的流程与付费墙获取

在 Android 上,一次可靠的流程或付费墙获取需要做到三点:快速渲染、返回针对目标受众的实验变体,以及在网络较慢时优雅地降级到备用方案。以下规则涵盖了实现这些目标所需的时机选择、缓存策略与备用方案。

Tip

以下规则假定 Adapty.activate() 和 Adapty.identify() 均已完成。详情请参阅 Android SDK 调用顺序。

注意事项与常见陷阱

应该这样做不要这样做原因
获取即将显示的版位,或使用 preloadFlows(SDK 4.1+)预热缓存。在启动时自行并发调用多个 getFlow。自己编写的预取请求会阻塞主线程并导致黑屏。preloadFlows 专为此场景设计,会自动并发执行批量请求。
在归因数据有机会解析后再调用 getFlow——例如在 activate 后等待 1–2 秒,或在 setOnProfileUpdatedListener 触发后调用。在 Application.onCreate() 中调用 getFlow。此时归因数据尚未到达,流程会按默认目标受众解析,静默跳过市场细分和 ASA 个性化配置。
设置 loadTimeout 并为每个版位配置备用付费墙。无限等待 getFlow 返回。没有超时限制时,网络较差的用户会看到空白屏幕,直到网络恢复——或者直接关掉应用。

有关 fetchPolicy 和 loadTimeout 参数的说明,请参阅获取付费墙和产品;有关选择合适版位的说明,请参阅版位。

预加载版位

Info

preloadFlows 和 preloadFlowsForDefaultAudience 从 SDK 4.1 版本起可用。

preloadFlows 会提前缓存流程 JSON,每个版位发起一次请求。之后的用法与平时一样:通过 getFlow 获取流程,通过 getFlowConfiguration 获取其视图配置。

fetchPolicy 决定后续 getFlow 优先从哪一层读取数据,而不是能否访问缓存:

  • ReturnCacheDataElseLoad 优先读取预加载的副本,仅在缓存为空时才发起网络请求。ReturnCacheDataIfNotExpiredElseLoad(maxAgeMillis) 行为相同,但仅在副本的存储时间不超过 maxAgeMillis 时生效。
  • 默认策略 ReloadRevalidatingCacheData 优先走网络,仅在请求失败或超时时回退到预加载的副本。

无论哪种方式,预加载都有价值,只是效果不同:缓存优先策略会直接省去网络请求,而默认策略则保留请求,同时获得一个可供回退的热缓存副本。

在您知道本次会话需要哪些版位、但暂时不想展示时使用——例如,在 activate 和 identify 完成之后,为用户尚未点击的按钮背后的流程预加载。

参数:

  • placementIds(必填):需要预加载的版位。空白 ID 和重复 ID 将被忽略。
  • loadTimeout(可选):超时时间作用于批次中的每个版位,而非整个批次。默认为 5 秒,低于 1 秒的值将被自动提升至 1 秒。

以下行为需注意:

  • 该回调仅在所有版位都尝试完毕后才会触发,并统一报告各版位的失败情况。某个版位的失败不会阻止其他版位的处理。
  • 如果某个版位超时、遇到服务器错误或网络错误,SDK 会回退到该版位的备用变体。其他类型的失败则按原样上报。
  • 预加载仅用于预热缓存,不会返回内容——你仍需调用 getFlow 来显示内容。

预加载涵盖的内容

流程到达屏幕的过程分为多个层次。预加载仅覆盖第一层,与 getFlow 的作用完全相同:

层次由谁获取是否被预加载预热
Flow JSON —— 所选实验变体、其产品 ID 及远程配置getFlow是
UI 布局 —— 屏幕的结构、样式和文本getFlowConfiguration否
图片,包括用于替代视频元素的静态帧getFlowConfiguration,在后台处理否
视频文件系统播放器,在屏幕渲染时加载SDK 不缓存

getFlowConfiguration 会等待布局加载完成,因此即使已预加载,首次请求某个布局时仍需要一次完整的网络往返。SDK 随后会将该布局保存到自身的磁盘缓存中,缓存在应用重启后依然有效,并且在发起任何网络请求之前会优先读取缓存,所以这次开销只发生在首次请求时,而非每次请求。一旦 SDK 获取到布局,便会独立启动图片缓存:该过程不会阻塞页面展示,也不提供任何回调或错误来通知缓存完成时机。

查找哪个版位失败了

回调会接收一个覆盖整批请求的 AdaptyPreloadPlacementsError,错误码为 REQUEST_FAILED(2005)。要查看各个版位的失败详情,请读取其 preloadErrors 属性——这是一个以版位 ID 为键的映射:

Adapty.preloadFlows(listOf("onboarding", "main_paywall")) { error ->
    if (error is AdaptyPreloadPlacementsError) {
        error.preloadErrors.forEach { (placementId, placementError) ->
            // log or retry the individual placement
        }
    }
}

preloadErrors 仅存在于 AdaptyPreloadPlacementsError 上,因此请先确认类型——其他 AdaptyError 来自版位级别失败以外的原因。

跳过目标受众细分

如需在完全不等待目标受众细分的情况下预热缓存,请使用默认受众实验变体。该方法不需要 loadTimeout:

Adapty.preloadFlowsForDefaultAudience(listOf("main_paywall")) { error -> }

从应用包中显示第一屏媒体内容

流程会从 Adapty 下载图片和视频。若要在第一屏立即显示媒体内容,可改为从应用包中提供。这样可以复用已经打包在应用中的素材,例如现有原生用户引导界面的视觉资源。

  1. 在 Flow 与付费墙编辑工具中,为图片或视频设置自定义媒体 ID。在那里上传的文件将作为备用文件保留。
  2. 将文件添加到应用的 res/raw 或 assets 文件夹中。
  3. 使用 getFlowView 创建流程视图时,在 customAssets 中传入该 ID 对应的本地文件:
// "welcome_video" is the custom media ID set in the Flow & Paywall Builder
val bundledAssets = AdaptyCustomAssets.of(
    "welcome_video" to
            AdaptyCustomVideoAsset.file(
                FileLocation.fromResId(requireContext(), R.raw.welcome),
                preview = AdaptyCustomImageAsset.file(
                    FileLocation.fromResId(requireContext(), R.drawable.welcome_poster),
                ),
                resolution = AdaptyCustomVideoAsset.Resolution(width = 1080, height = 1920),
            ),
)

val flowView = AdaptyUI.getFlowView(
    activity,
    flowConfiguration,
    products,
    eventListener,
    insets,
    bundledAssets,
)

捆绑文件会增加应用的下载体积,因此只捆绑用户首屏看到的媒体资源。

未捆绑的媒体同样会立即显示:视图配置中为每张图片(包括视频的静帧封面)内置了一份低分辨率预览图,在完整文件加载完成前持续展示。

完整的 customAssets 参考文档,请参阅自定义资源。

针对弱网环境进行调优

对于网络状况持续较差的市场(农村地区、交通沿线、受路由影响的地区):

  • 除首次请求外,每次获取时将 fetchPolicy 设置为 AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad。
  • 在 Adapty 看板中为每个版位配置备用付费墙。
  • 将 loadTimeout 设置为 3–5 秒,并在超时触发时接受备用内容。
  • 不要将流程显示挂起在 getProfile 上。独立调用 getFlow,避免因用户画像加载缓慢而阻塞 UI。