` 标签,使应用提供最终值并告知清单合并工具替换库中的值:
```xml
...
```
如果某个 SDK 也设置了 `android:allowBackup`,请将其一并加入 `tools:replace`:
```xml
tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules"
```
#### 3. 创建合并后的备份规则文件 \{#3-create-merged-backup-rules-files\}
在 `app/src/main/res/xml/` 目录下创建 XML 文件,将 Adapty 的规则与其他 SDK 的规则合并在一起。由于 Android 在不同系统版本中使用不同的备份规则格式,同时创建两个文件可确保与你的应用所支持的所有 Android 版本兼容。
:::note
以下示例以 AppsFlyer 作为第三方 SDK 的示例。请根据你在应用中使用的其他 SDK,替换或添加相应的规则。
:::
**适用于 Android 12 及更高版本**(使用新的数据提取规则格式):
```xml title="sample_data_extraction_rules.xml"
```
**适用于 Android 11 及以下版本**(使用旧版完整备份内容格式):
```xml title="sample_backup_rules.xml"
```
完成此配置后:
- Adapty 的备份排除项(`AdaptySDKPrefs.xml`)得以保留。
- 其他 SDK 的排除项(例如 `appsflyer-data`)同样生效。
- Manifest 合并器将使用您应用的配置,不再因备份属性冲突而报错。
#### 从其他应用返回后购买失败 \{#purchases-fail-after-returning-from-another-app\}
如果启动购买流程的 Activity 使用了非默认的 `launchMode`,当用户从 Google Play、银行应用或浏览器返回时,Android 可能会错误地重建或复用该 Activity,导致购买结果丢失或被视为已取消。
为确保购买流程正常运行,请仅对启动购买流程的 Activity 使用 `standard` 或 `singleTop` 启动模式,避免使用其他模式。
在 `AndroidManifest.xml` 中,确保启动购买流程的 Activity 设置为 `standard` 或 `singleTop`:
```xml
```
---
# File: android-quickstart-paywalls
---
---
title: "在 Android SDK 中使用 Flow Builder 启用购买功能"
description: "使用 Adapty Flow Builder 启用应用内购买的快速入门指南。"
---
要启用应用内购买,你需要了解三个核心概念:
- [**产品**](product) – 用户可购买的任何内容(订阅、消耗型商品、永久授权)
- [**流程**](adapty-flow-builder) – 向用户展示产品的屏幕序列,使用无代码的 Flow Builder 构建,SDK 通过 `getFlow` 获取。如果你更倾向于用自己的代码构建 UI,请改用付费墙——参见[手动实现付费墙](android-quickstart-manual)。
- [**版位**](placements) – 在应用中展示流程的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中将流程绑定到版位,然后在代码中通过版位 ID 请求对应流程。这样可以轻松进行 A/B 测试,并向不同用户展示不同的流程。
Adapty 为您提供三种在应用中启用购买的方式。请根据您的应用需求选择其中一种:
| 实现方式 | 复杂度 | 适用场景 |
|---|---|---|
| Adapty Flow Builder | ✅ 简单 | 您[在无代码编辑工具中创建完整的、可直接购买的流程](quickstart-paywalls)。Adapty 自动渲染并在后台处理所有复杂的购买流程、收据验证和订阅管理。 |
| 手动创建付费墙 | 🟡 中等 | 您在应用代码中实现付费墙 UI,但仍从 Adapty 获取流程对象,以保持产品offerings的灵活性。请参阅[指南](android-quickstart-manual)。 |
| 观察者模式 | 🔴 复杂 | 您已有自己的购买处理基础设施,并希望继续使用。请注意,观察者模式在 Adapty 中存在一定限制。请参阅[文章](observer-vs-full-mode)。 |
:::important
**以下步骤介绍如何实现在 Adapty Flow Builder 中创建的流程。**
如果你更倾向于自行构建付费墙 UI,请参阅[手动实现付费墙](android-quickstart-manual)。
:::
要在应用中显示在 Adapty Flow Builder 中创建的流程,你只需在代码中完成以下步骤:
1. **获取流程**:从 Adapty 获取流程。
2. **显示流程,Adapty 将自动处理购买**:在应用中展示视图。
3. **处理按钮操作**:将用户交互与应用的响应逻辑关联起来,例如在用户点击按钮时打开链接或关闭流程。
## 开始之前 \{#before-you-start\}
开始之前,请完成以下步骤:
1. 在 Adapty 看板中[将应用连接到 Google Play](initial-android)。
2. 在 Adapty 中[创建产品](create-product)。
3. [创建流程并添加产品](create-paywall)。
4. [创建版位并添加流程](create-placement)。
5. 在应用代码中[安装并激活 Adapty SDK](sdk-installation-android)。本指南使用 Adapty Android SDK v4 API。
:::tip
完成这些步骤最快的方式是参考[快速入门指南](quickstart),或使用 [Developer CLI](developer-cli-quickstart) 创建流程和版位。
:::
## 1. 获取流程 \{#1-get-the-flow\}
你的流程与在看板中配置的版位相关联。通过版位,你可以针对不同的目标受众运行不同的流程,或进行 [A/B 测试](ab-tests)。
要获取在 Adapty 流程编辑器中创建的流程,需要:
1. 使用 `getFlow` 方法,通过[版位](placements) ID 获取 `flow` 对象,并检查它是否包含视图配置。
2. 使用 `getFlowConfiguration` 方法获取视图配置。视图配置包含显示流程所需的 UI 元素和样式信息。
:::important
要获取视图配置,必须在 Flow Builder 中开启 **Show on device** 开关。否则将获得空的视图配置,流程将无法显示。
:::
```kotlin showLineNumbers
Adapty.getFlow("YOUR_PLACEMENT_ID") { result ->
if (result is AdaptyResult.Success) {
val flow = result.value
if (!flow.hasViewConfiguration) {
return@getFlow
}
AdaptyUI.getFlowConfiguration(flow) { configResult ->
if (configResult is AdaptyResult.Success) {
val flowConfiguration = configResult.value
}
}
}
}
```
```java showLineNumbers
Adapty.getFlow("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyFlow flow = ((AdaptyResult.Success) result).getValue();
if (!flow.hasViewConfiguration()) {
return;
}
AdaptyUI.getFlowConfiguration(flow, configResult -> {
if (configResult instanceof AdaptyResult.Success) {
AdaptyUI.FlowConfiguration flowConfiguration =
((AdaptyResult.Success) configResult).getValue();
// use loaded configuration
}
});
}
});
```
## 2. 展示流程 \{#display-the-flow\}
获取到流程配置后,只需添加几行代码即可展示你的流程。
要在设备屏幕上渲染可视化流程,必须先对其进行配置。调用 `AdaptyUI.getFlowView()` 方法或直接创建 `AdaptyFlowView`:
```kotlin showLineNumbers
val flowView = AdaptyUI.getFlowView(
activity,
flowConfiguration,
null, // products = null means auto-fetch
eventListener,
)
```
```kotlin showLineNumbers
val flowView =
AdaptyFlowView(activity) // or retrieve it from xml
...
with(flowView) {
showFlow(
flowConfiguration,
null, // products = null means auto-fetch
eventListener,
)
}
```
```java showLineNumbers
AdaptyFlowView flowView = AdaptyUI.getFlowView(
activity,
flowConfiguration,
null, // products = null means auto-fetch
eventListener
);
```
```java showLineNumbers
AdaptyFlowView flowView =
new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
flowView.showFlow(flowConfiguration, products, eventListener);
```
```xml showLineNumbers
```
视图成功创建后,你可以将其添加到视图层级中,并在设备屏幕上显示出来。
:::tip
有关如何显示 flow 的更多详情,请参阅我们的[指南](android-present-paywalls)。
:::
## 3. 处理按钮操作 \{#handle-button-actions\}
当用户点击流程中的按钮时,Android SDK 会自动处理购买、恢复、关闭流程和打开链接等操作。
不过,其他按钮具有自定义或预定义的 ID,需要在代码中处理相应操作,或者你可能希望覆盖其默认行为。
例如,以下是关闭按钮的默认行为。你不需要在代码中添加它,但如果有需要,可以参考这里的实现方式。
:::tip
阅读我们关于如何处理按钮[操作](android-handle-paywall-actions)和[事件](android-handling-events)的指南。
:::
```kotlin showLineNumbers title="Kotlin"
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior
}
}
```
```java showLineNumbers
@Override
public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) {
if (action instanceof AdaptyUI.Action.Close) {
if (context instanceof Activity) {
((Activity) context).onBackPressed();
}
}
}
```
## 下一步 \{#next-steps\}
:::tip
有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助!
:::
您的流程已准备好在应用中展示。[在 Google Play Store 中测试购买](testing-on-android),确保您能从流程中完成测试购买。
接下来,您需要[检查用户的访问等级](android-check-subscription-status),确保向正确的用户展示流程或开放付费功能。
## 完整示例 \{#full-example\}
以下是如何将所有步骤整合到你的应用中的完整示例。
```kotlin showLineNumbers title="Kotlin"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Adapty.getFlow("YOUR_PLACEMENT_ID") { flowResult ->
if (flowResult is AdaptyResult.Success) {
val flow = flowResult.value
if (!flow.hasViewConfiguration) {
// Use custom logic
return@getFlow
}
AdaptyUI.getFlowConfiguration(flow) { configResult ->
if (configResult is AdaptyResult.Success) {
val flowConfiguration = configResult.value
val flowView = AdaptyUI.getFlowView(
this,
flowConfiguration,
null, // products = null means auto-fetch
object : AdaptyFlowDefaultEventListener() {
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
is AdaptyUI.Action.Close -> {
(context as? Activity)?.onBackPressed()
}
}
}
}
)
setContentView(flowView)
}
}
}
}
}
}
```
```java showLineNumbers
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Adapty.getFlow("YOUR_PLACEMENT_ID", flowResult -> {
if (flowResult instanceof AdaptyResult.Success) {
AdaptyFlow flow = ((AdaptyResult.Success) flowResult).getValue();
if (!flow.hasViewConfiguration()) {
// Use custom logic
return;
}
AdaptyUI.getFlowConfiguration(flow, configResult -> {
if (configResult instanceof AdaptyResult.Success) {
AdaptyUI.FlowConfiguration flowConfiguration =
((AdaptyResult.Success) configResult).getValue();
AdaptyFlowView flowView = AdaptyUI.getFlowView(
this,
flowConfiguration,
null, // products = null means auto-fetch
new AdaptyFlowDefaultEventListener() {
@Override
public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) {
if (action instanceof AdaptyUI.Action.Close) {
if (context instanceof Activity) {
((Activity) context).onBackPressed();
}
}
}
}
);
setContentView(flowView);
}
});
}
});
}
}
```
---
# File: android-check-subscription-status
---
---
title: "在 Android SDK 中检查订阅状态"
description: "了解如何使用 Adapty 在您的 Android 应用中检查订阅状态。"
---
要决定用户是否可以访问付费内容或查看付费墙,您需要检查用户画像中的[访问等级](access-level)。
本文介绍如何访问用户画像状态,以决定向用户展示什么内容——是显示付费墙还是授予付费功能的访问权限。
## 获取订阅状态 \{#get-subscription-status\}
当您决定是否向用户显示付费墙或付费内容时,需要检查其用户画像中的[访问等级](access-level)。您有两种选择:
- 如果需要立即获取最新的用户画像数据(例如在应用启动时)或想要强制更新,请调用 `getProfile`。
- 设置**自动用户画像更新**,以保存一份本地副本,该副本会在订阅状态发生变化时自动刷新。
### 获取用户画像 \{#get-profile\}
获取订阅状态最简单的方法是使用 `getProfile` 方法访问用户画像:
```kotlin showLineNumbers
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
// check the access
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
// check the access
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
### 监听订阅更新 \{#listen-to-subscription-updates\}
要在应用中自动接收用户画像更新:
1. 使用 `Adapty.setOnProfileUpdatedListener()` 监听用户画像变化——每当用户的订阅状态发生变化时,Adapty 会自动调用此方法。
2. 在此方法被调用时存储更新后的用户画像数据,以便在整个应用中使用,而无需发起额外的网络请求。
```kotlin
class SubscriptionManager {
private var currentProfile: AdaptyProfile? = null
init {
// Listen for profile updates
Adapty.setOnProfileUpdatedListener { profile ->
currentProfile = profile
// Update UI, unlock content, etc.
}
}
// Use stored profile instead of calling getProfile()
fun hasAccess(): Boolean {
return currentProfile?.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true
}
}
```
```java
public class SubscriptionManager {
private AdaptyProfile currentProfile;
public SubscriptionManager() {
// Listen for profile updates
Adapty.setOnProfileUpdatedListener(profile -> {
this.currentProfile = profile;
// Update UI, unlock content, etc.
});
}
// Use stored profile instead of calling getProfile()
public boolean hasAccess() {
if (currentProfile == null) {
return false;
}
AdaptyAccessLevel premiumAccess = currentProfile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
return premiumAccess != null && premiumAccess.isActive();
}
}
```
:::note
Adapty 会在应用启动时自动调用用户画像更新监听器,即使设备处于离线状态,也能提供缓存的订阅数据。
:::
## 将用户画像与付费墙逻辑关联 \{#connect-profile-with-paywall-logic\}
当您需要立即决定是否显示付费墙或授予付费功能访问权限时,可以直接检查用户的用户画像。此方法适用于应用启动、进入付费专区或显示特定内容之前等场景。
```kotlin
private fun initializePaywall() {
loadPaywall { paywallView ->
checkAccessLevel { result ->
when (result) {
is AdaptyResult.Success -> {
if (!result.value && paywallView != null) {
setContentView(paywallView) // Show paywall if no access
}
}
is AdaptyResult.Error -> {
if (paywallView != null) {
setContentView(paywallView) // Show paywall if access check fails
}
}
}
}
}
}
private fun checkAccessLevel(callback: ResultCallback) {
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val hasAccess = result.value.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true
callback.onResult(AdaptyResult.Success(hasAccess))
}
is AdaptyResult.Error -> {
callback.onResult(AdaptyResult.Error(result.error))
}
}
}
}
```
```java
private void initializePaywall() {
loadPaywall(paywallView -> {
checkAccessLevel(result -> {
if (result instanceof AdaptyResult.Success) {
boolean hasAccess = ((AdaptyResult.Success) result).getValue();
if (!hasAccess && paywallView != null) {
setContentView(paywallView); // Show paywall if no access
}
} else if (result instanceof AdaptyResult.Error) {
if (paywallView != null) {
setContentView(paywallView); // Show paywall if access check fails
}
}
});
});
}
private void checkAccessLevel(ResultCallback callback) {
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
AdaptyAccessLevel premiumAccess = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
boolean hasAccess = premiumAccess != null && premiumAccess.isActive();
callback.onResult(AdaptyResult.success(hasAccess));
} else if (result instanceof AdaptyResult.Error) {
callback.onResult(AdaptyResult.error(((AdaptyResult.Error) result).getError()));
}
});
}
```
## 后续步骤 \{#next-steps\}
现在您已经了解如何追踪订阅状态,接下来请学习如何[使用用户画像](android-quickstart-identify),以确保用户能够访问他们已付费的内容。
---
# File: android-quickstart-identify
---
---
title: "在 Android SDK 中识别用户"
description: "在 Android 中设置 Adapty 进行应用内订阅管理的快速入门指南。"
---
:::important
如果您有自己的身份验证系统,本指南适合您。在这里,您将学习如何在 Adapty 中管理用户画像,以确保其与您现有的身份验证系统保持一致。
:::
您如何管理用户购买取决于您的应用的身份验证模型:
- 如果您的应用不使用后端身份验证且不存储用户数据,请参阅[匿名用户部分](#anonymous-users)。
- 如果您的应用已有(或将有)后端身份验证,请参阅[已识别用户部分](#identified-users)。
**核心概念**:
- **用户画像** 是 SDK 运行所需的实体。Adapty 会自动创建它们。
- 它们可以是匿名的**(无 customer user ID)**或已识别的**(有 customer user ID)**。
- 您提供 **customer user ID** 是为了将 Adapty 中的用户画像与您内部的身份验证系统进行关联。
以下是匿名用户和已识别用户的区别:
| | 匿名用户 | 已识别用户 |
|-------------------------|---------------------------------------------------|-------------------------------------------------------------------------|
| **购买管理** | 应用商店级别的购买恢复 | 通过 customer user ID 在设备间维护购买历史 |
| **用户画像管理** | 每次重新安装时创建新的用户画像 | 跨会话和设备使用同一用户画像 |
| **数据持久性** | 匿名用户的数据与应用安装绑定 | 已识别用户的数据在应用安装间持续保存 |
## 匿名用户 \{#anonymous-users\}
如果您没有后端身份验证,**您无需在应用代码中处理身份验证**:
1. 当 SDK 在应用首次启动时激活,Adapty **为用户创建一个新的用户画像**。
2. 当用户在应用中购买任何内容时,该购买将**与其 Adapty 用户画像和应用商店账户关联**。
3. 当用户**重新安装**应用或在**新设备**上安装时,Adapty **在激活时创建一个新的匿名用户画像**。
4. 如果用户之前在您的应用中进行过购买,默认情况下,他们的购买记录会在 SDK 激活时自动从 App Store 同步。
因此,对于匿名用户,每次安装都会创建新的用户画像,但这不是问题,因为在 Adapty 分析中,您可以[配置什么将被视为新安装](general#4-installs-definition-for-analytics)。
对于匿名用户,您需要按**设备 ID** 统计安装量。在这种情况下,设备上的每次应用安装(包括重新安装)都计为一次安装。
## 已识别用户 \{#identified-users\}
您有两种方式在应用中识别用户:
- [**在登录/注册时:**](#during-loginsignup) 如果用户在您的应用启动后登录,请在用户完成身份验证时使用 customer user ID 调用 `identify()`。
- [**在 SDK 激活时:**](#during-the-sdk-activation) 如果应用启动时您已有存储的 customer user ID,请在调用 `activate()` 时传入它。
:::important
默认情况下,当 Adapty 收到一个当前与另一个 Customer User ID 关联的 Customer User ID 的购买时,访问等级将被共享,因此两个用户画像都具有付费访问权限。您可以配置此设置,将付费访问从一个用户画像转移到另一个,或完全禁用共享。详情请参阅[此文章](general#6-sharing-paid-access-between-user-accounts)。
:::
### 在登录/注册时 \{#during-loginsignup\}
如果您在应用启动后识别用户(例如,在他们登录或注册后),请使用 `identify` 方法设置他们的 customer user ID。
- 如果您**之前从未使用过此 customer user ID**,Adapty 将自动将其与当前用户画像关联。
- 如果您**之前已使用此 customer user ID 识别过用户**,Adapty 将切换到与该 customer user ID 关联的用户画像。
:::important
每位用户的 customer user ID 必须唯一。如果将该参数值硬编码,所有用户将被视为同一人。
:::
等待 `identify` 的回调触发后,再调用其他 SDK 方法。并发调用可能会落到匿名用户画像上,而非已识别的用户画像。详见 [Android SDK 的调用顺序](android-sdk-call-order)。
```kotlin showLineNumbers
Adapty.identify("YOUR_USER_ID") { error -> // 每个用户的 ID 必须唯一
if (error == null) {
// 识别成功
}
}
```
```java showLineNumbers
// 每个用户的 ID 必须唯一
Adapty.identify("YOUR_USER_ID", error -> {
if (error == null) {
// 识别成功
}
});
```
### 在 SDK 激活时 \{#during-the-sdk-activation\}
如果您在激活 SDK 时已知晓 customer user ID,可以在 `activate` 方法中传入,而无需单独调用 `identify`。
如果您知道 customer user ID 但仅在激活后才设置它,这意味着在激活时,Adapty 将创建一个新的匿名用户画像,只有在您调用 `identify` 后才会切换到现有用户画像。
您可以传入现有的 customer user ID(之前已使用过的)或新的 customer user ID。如果传入新的,激活时创建的新用户画像将自动与该 customer user ID 关联。
:::note
默认情况下,创建匿名用户画像不会影响分析看板,因为安装次数基于设备 ID 统计。
设备 ID 代表从应用商店在设备上的一次应用安装,仅在应用重新安装后才会重新生成。
它不取决于是首次安装还是重复安装,也不取决于是否使用了现有的 customer user ID。
创建用户画像(在 SDK 激活或退出登录时)、登录或在不重新安装应用的情况下升级应用,都不会产生额外的安装事件。
如果您希望按唯一用户而非设备统计安装量,请前往 **App settings** 并配置 [**Installs definition for analytics**](general#4-installs-definition-for-analytics)。
:::
```kotlin showLineNumbers
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one.
.build()
```
```java showLineNumbers
new AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one.
.build();
```
### 退出登录用户 \{#log-users-out\}
如果您有用于用户退出登录的按钮,请使用 `logout` 方法。
:::important
退出登录会为用户创建一个新的匿名用户画像。
:::
```kotlin showLineNumbers
Adapty.logout { error ->
if (error == null) {
// successful logout
}
}
```
```java showLineNumbers
Adapty.logout(error -> {
if (error == null) {
// successful logout
}
});
```
:::info
要让用户重新登录应用,请使用 `identify` 方法。
:::
### 允许未登录时购买 \{#allow-purchases-without-login\}
如果您的用户可以在登录前后都进行购买,您需要确保他们登录后仍能保留访问权限:
1. 当未登录用户进行购买时,Adapty 将购买与其匿名用户画像 ID 绑定。
2. 当用户登录账户时,Adapty 切换到其已识别的用户画像。
- 如果是新的 customer user ID(例如,购买发生在注册之前),Adapty 将 customer user ID 分配给当前用户画像,从而保留所有购买历史。
- 如果是现有的 customer user ID(该 customer user ID 已与一个用户画像关联),您需要在用户画像切换后获取实际的访问等级。您可以在识别完成后立即调用 [`getProfile`](android-check-subscription-status),或[监听用户画像更新](android-check-subscription-status)以便数据自动同步。
## 后续步骤 \{#next-steps\}
恭喜!您已在应用中实现了应用内支付逻辑!祝您的应用变现一切顺利!
为了从 Adapty 中获取更多价值,您可以探索以下主题:
- [**测试**](troubleshooting-test-purchases):确保一切按预期运行
- [**用户引导**](android-onboardings):通过用户引导吸引用户并提升留存率
- [**集成**](configuration):只需一行代码即可与营销归因和分析服务集成
- [**设置自定义用户画像属性**](android-setting-user-attributes):为用户画像添加自定义属性并创建市场细分,以便针对不同用户发起 A/B 测试或展示不同付费墙
---
# File: adapty-sdk-integration-skill-android
---
---
title: "使用 SDK 集成技能将 Adapty 接入 Android 应用"
description: "使用 adapty-sdk-integration 技能,通过 AI 编码工具将 Adapty SDK 端到端集成到你的 Android 应用中。"
---
[adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。
**支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。
安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。
**Claude Code**
```
claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill
claude plugin install adapty-sdk-integration@adapty
```
**GitHub Copilot CLI**
```
gh skill install adaptyteam/adapty-sdk-integration-skill
```
**Gemini CLI**
```
gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill
```
**OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新):
```
npx skills add adaptyteam/adapty-sdk-integration-skill
```
也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。
安装完成后,在项目中运行该 skill:
```
/adapty-sdk-integration
```
skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。
:::important
该技能目前处于测试阶段。如果出现卡顿或异常行为,请改用[分步集成指南](adapty-cursor-android)——它会引导您的 AI 工具逐步完成每个阶段所需的文档。
:::
[adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill) 可以端到端地自动完成 Adapty 集成:看板配置、SDK 安装、付费墙设置以及各阶段验证。它会自动检测你的平台,并在每个阶段获取相关的 Adapty 文档。
**支持的工具**:Claude Code、GitHub Copilot CLI、OpenAI Codex、Gemini CLI。
安装时,请选择适合你所用工具的方式。完整列表请参阅 [skill README](https://github.com/adaptyteam/adapty-sdk-integration-skill)。
**Claude Code**
```
claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill
claude plugin install adapty-sdk-integration@adapty
```
**GitHub Copilot CLI**
```
gh skill install adaptyteam/adapty-sdk-integration-skill
```
**Gemini CLI**
```
gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill
```
**OpenAI Codex 或其他工具** — 使用 [skills CLI](https://skills.sh)(注意:通过此方式安装的 skill 不会自动更新):
```
npx skills add adaptyteam/adapty-sdk-integration-skill
```
也可以克隆仓库,将 `skills/adapty-sdk-integration/` 复制到你所用工具的 skills 目录中。
安装完成后,在项目中运行该 skill:
```
/adapty-sdk-integration
```
skill 会提出几个配置问题,然后逐步引导你完成看板配置、SDK 安装、付费墙设置和验证。
---
# File: adapty-cursor-android
---
---
title: "借助 AI 将 Adapty 集成到您的 Android 应用"
description: "使用 Cursor、Context7、ChatGPT、Claude 或其他 AI 工具将 Adapty 集成到 Android 应用的分步指南。"
---
本指南将带你一步步将 Adapty 集成到 Android 应用中,借助 AI 编程工具完成整个流程——只需按正确顺序将相关 Adapty 文档喂给它即可。
For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command.
## 开始之前:看板配置 \{#before-you-start-dashboard-setup\}
在编写任何 SDK 代码之前,Adapty 需要进行一些看板配置。您可以使用交互式 LLM 技能来完成,也可以通过看板手动操作。
### 技能方式(推荐) \{#skill-approach-recommended\}
Adapty CLI 技能让您的 LLM 能够直接设置您的应用、产品、访问等级、付费墙和版位——无需为每个步骤打开看板。您只需在看板中[连接您的应用商店](integrate-payments)即可。
```
npx skills add adaptyteam/adapty-cli --skill adapty-cli
```
添加技能后,在您的智能代理中运行 `/adapty-cli`。它将引导您完成每个步骤——包括何时需要打开看板连接应用商店。
### 看板方式 \{#dashboard-approach\}
如果您更倾向于手动配置,以下是编写任何代码之前需要完成的内容。您的 LLM 无法为您查找看板中的值——您需要自行提供。
1. **连接您的应用商店**:在 Adapty 看板中,前往 **App settings → General**。这是购买功能正常运作的必要条件。
[连接 Google Play](integrate-payments)
2. **复制您的 Public SDK key**:在 Adapty 看板中,前往 **App settings → General**,找到 **API keys** 部分。在代码中,这是您传递给 Adapty 配置构建器的字符串。
3. **创建至少一个产品**:在 Adapty 看板中,前往 **Products** 页面。您不需要在代码中直接引用产品——Adapty 通过付费墙来传递产品。
[添加产品](quickstart-products)
4. **创建付费墙和版位**:在 Adapty 看板中,在 **Paywalls** 页面创建付费墙,然后在 **Placements** 页面将其分配给一个版位。在代码中,版位 ID 是您传递给 `Adapty.getPaywall("YOUR_PLACEMENT_ID")` 的字符串。
[创建付费墙](quickstart-paywalls)
5. **设置访问等级**:在 Adapty 看板中,在 **Products** 页面按产品进行配置。在代码中,通过 `profile.accessLevels["premium"]?.isActive` 检查该字符串。默认的 `premium` 访问等级适用于大多数应用。如果付费用户根据产品获得不同功能的访问权限(例如 `basic` 方案与 `pro` 方案),请在开始编码之前[创建额外的访问等级](assigning-access-level-to-a-product)。
:::tip
一旦您完成上述五项配置,就可以开始编写代码了。告诉您的 LLM:"我的 Public SDK key 是 X,我的版位 ID 是 Y",以便它能生成正确的初始化和付费墙获取代码。
:::
### 准备就绪后再进行配置 \{#set-up-when-ready\}
以下内容在开始编码时并非必须,但随着集成的成熟您会需要它们:
- **A/B 测试**:在 **Placements** 页面配置。无需更改代码。
[A/B 测试](ab-tests)
- **更多付费墙和版位**:使用不同的版位 ID 添加更多 `getPaywall` 调用。
- **分析集成**:在 **Integrations** 页面配置。具体设置因集成而异。参见[分析集成](analytics-integration)和[归因集成](attribution-integration)。
## 将 Adapty 文档提供给您的 LLM \{#feed-adapty-docs-to-your-llm\}
### 使用 Context7(推荐) \{#use-context7-recommended\}
[Context7](https://context7.com) 是一个 MCP 服务器,可让您的 LLM 直接访问最新的 Adapty 文档。您的 LLM 会根据您的问题自动获取正确的文档——无需手动粘贴 URL。
Context7 适用于 **Cursor**、**Claude Code**、**Windsurf** 及其他兼容 MCP 的工具。运行以下命令进行设置:
```
npx ctx7 setup
```
此命令会检测您的编辑器并配置 Context7 服务器。如需手动设置,请参阅 [Context7 GitHub 仓库](https://github.com/upstash/context7)。
配置完成后,在提示词中引用 Adapty 库:
```
Use the adaptyteam/adapty-docs library to look up how to install the Android SDK
```
:::warning
即使 Context7 无需手动粘贴文档链接,实施顺序依然重要。请按照下方的[实施流程](#implementation-walkthrough)逐步操作,以确保一切正常运作。
:::
### 使用纯文本文档 \{#use-plain-text-docs\}
您可以以纯文本 Markdown 格式访问任何 Adapty 文档。在 URL 末尾添加 `.md`,或点击文章标题下方的 **Copy for LLM**。例如:[adapty-cursor-android.md](https://adapty.io/docs/zh/adapty-cursor-android.md)。
下方[实施流程](#implementation-walkthrough)中的每个阶段都包含"发送给您的 LLM"代码块,其中包含可粘贴的 `.md` 链接。
如需一次获取更多文档,请参阅下方的[索引文件和平台专属子集](#plain-text-doc-index-files)。
## 实施流程 \{#implementation-walkthrough\}
本指南的其余部分按实施顺序介绍 Adapty 集成。每个阶段包含发送给 LLM 的文档、完成后应看到的内容,以及常见问题。
### 规划您的集成 \{#plan-your-integration\}
在编写代码之前,请让您的 LLM 分析您的项目并制定实施计划。如果您的 AI 工具支持规划模式(例如 Cursor 或 Claude Code 的规划模式),请使用它,以便 LLM 在编写任何代码之前能同时读取您的项目结构和 Adapty 文档。
告诉您的 LLM 您使用哪种购买方式——这会影响它应遵循的指南:
- [**Adapty 付费墙编辑工具**](adapty-paywall-builder):您在 Adapty 的无代码编辑工具中创建付费墙,SDK 会自动渲染它们。
- [**手动创建的付费墙**](android-making-purchases):您在代码中构建自己的付费墙 UI,但仍使用 Adapty 获取产品和处理购买。
- [**Observer 模式**](observer-vs-full-mode):您保留现有的购买基础设施,仅使用 Adapty 进行分析和集成。
不确定选哪种?请阅读[快速入门中的对比表](android-quickstart-paywalls)。
### 安装并配置 SDK \{#install-and-configure-the-sdk\}
通过 Android Studio 中的 Gradle 添加 Adapty SDK 依赖项,并使用您的 Public SDK key 激活它。这是基础——没有它,其他一切都无法正常运作。
**指南:**[安装并配置 Adapty SDK](sdk-installation-android)
发送给您的 LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/zh/sdk-installation-android.md
```
:::tip[检查点]
- **预期结果:** 应用构建并运行。Logcat 显示 Adapty 激活日志。
- **注意事项:** "Public API key is missing" → 检查您是否已将占位符替换为 **App settings** 中的真实密钥。
:::
### 显示付费墙并处理购买 \{#show-paywalls-and-handle-purchases\}
通过版位 ID 获取付费墙、显示它,并处理购买事件。所需的指南取决于您处理购买的方式。
在操作过程中逐个在沙盒中测试购买——不要等到最后再测试。请参阅[在沙盒中测试购买](test-purchases-in-sandbox)了解设置说明。
**指南:**
- [使用付费墙启用购买(快速入门)](android-quickstart-paywalls)
- [获取付费墙编辑工具付费墙及其配置](android-get-pb-paywalls)
- [展示付费墙](android-present-paywalls)
- [处理付费墙事件](android-handling-events)
- [响应按钮操作](android-handle-paywall-actions)
发送给您的 LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/zh/android-quickstart-paywalls.md
- https://adapty.io/docs/zh/android-get-pb-paywalls.md
- https://adapty.io/docs/zh/android-present-paywalls.md
- https://adapty.io/docs/zh/android-handling-events.md
- https://adapty.io/docs/zh/android-handle-paywall-actions.md
```
:::tip[检查点]
- **预期结果:** 付费墙显示您配置的产品。点击产品会触发沙盒购买对话框。
- **注意事项:** 付费墙为空或 `getPaywall` 报错 → 验证版位 ID 与看板中完全一致,且版位已分配目标受众。
:::
**指南:**
- [在自定义付费墙中启用购买(快速入门)](android-quickstart-manual)
- [获取付费墙和产品](fetch-paywalls-and-products-android)
- [渲染由远程配置设计的付费墙](present-remote-config-paywalls-android)
- [进行购买](android-making-purchases)
- [恢复购买](android-restore-purchase)
发送给您的 LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/zh/android-quickstart-manual.md
- https://adapty.io/docs/zh/fetch-paywalls-and-products-android.md
- https://adapty.io/docs/zh/present-remote-config-paywalls-android.md
- https://adapty.io/docs/zh/android-making-purchases.md
- https://adapty.io/docs/zh/android-restore-purchase.md
```
:::tip[检查点]
- **预期结果:** 您的自定义付费墙显示从 Adapty 获取的产品。点击产品会触发沙盒购买对话框。
- **注意事项:** 产品数组为空 → 验证付费墙在看板中已分配产品,且版位已分配目标受众。
:::
**指南:**
- [Observer 模式概述](observer-vs-full-mode)
- [实现 Observer 模式](implement-observer-mode-android)
- [在 Observer 模式中上报交易](report-transactions-observer-mode-android)
发送给您的 LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/zh/observer-vs-full-mode.md
- https://adapty.io/docs/zh/implement-observer-mode-android.md
- https://adapty.io/docs/zh/report-transactions-observer-mode-android.md
```
:::tip[检查点]
- **预期结果:** 使用您现有的购买流程完成沙盒购买后,交易出现在 Adapty 看板的 **Event Feed** 中。
- **注意事项:** 没有事件 → 验证您已向 Adapty 上报交易,且 Google Play 实时开发者通知已配置。
:::
### 检查订阅状态 \{#check-subscription-status\}
购买完成后,检查用户画像中的活跃访问等级,以控制高级内容的访问权限。
**指南:**[检查订阅状态](android-check-subscription-status)
发送给您的 LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/zh/android-check-subscription-status.md
```
:::tip[检查点]
- **预期结果:** 沙盒购买后,`profile.accessLevels["premium"]?.isActive` 返回 `true`。
- **注意事项:** 购买后 `accessLevels` 为空 → 检查产品在看板中是否已分配访问等级。
:::
### 识别用户 \{#identify-users\}
将您的应用用户账户与 Adapty 用户画像关联,以便购买记录能在不同设备间持久保存。
:::important
如果您的应用没有身份验证功能,请跳过此步骤。
:::
**指南:**[识别用户](android-quickstart-identify)
发送给您的 LLM:
```
Read these Adapty docs before writing code:
- https://adapty.io/docs/zh/android-quickstart-identify.md
```
:::tip[检查点]
- **预期结果:** 调用 `Adapty.identify("your-user-id")` 后,看板的 **Profiles** 部分显示您的自定义用户 ID。
- **注意事项:** 在激活之后、获取付费墙之前调用 `identify`,以避免匿名用户画像归因问题。
:::
### 准备发布 \{#prepare-for-release\}
一旦您的集成在沙盒中正常运行,请按照发布清单检查,确保一切已为生产环境做好准备。
**指南:**[发布清单](release-checklist)
发送给您的 LLM:
```
Read these Adapty docs before releasing:
- https://adapty.io/docs/zh/release-checklist.md
```
:::tip[检查点]
- **预期结果:** 所有清单项目均已确认:应用商店连接、服务器通知、购买流程、访问等级检查以及隐私要求。
- **注意事项:** 缺少 Google Play 实时开发者通知 → 在 **App settings → Android SDK** 中配置,否则事件不会出现在看板中。
:::
## 纯文本文档索引文件 \{#plain-text-doc-index-files\}
如果您需要为 LLM 提供超出单个页面的更广泛上下文,我们提供了列出或合并所有 Adapty 文档的索引文件:
- [`llms.txt`](https://adapty.io/docs/zh/llms.txt):列出所有页面及其 `.md` 链接。这是[一种新兴标准](https://llmstxt.org/),旨在让网站对 LLM 更易访问。请注意,对于某些 AI 智能代理(例如 ChatGPT),您需要下载 `llms.txt` 并将其作为文件上传到聊天中。
- [`llms-full.txt`](https://adapty.io/docs/zh/llms-full.txt):将整个 Adapty 文档站点合并为一个文件。体积非常大——仅在需要完整内容时使用。
- Android 专属的 [`android-llms.txt`](https://adapty.io/docs/zh/android-llms.txt) 和 [`android-llms-full.txt`](https://adapty.io/docs/zh/android-llms-full.txt):与完整站点相比,平台专属子集可节省 token 用量。
---
# File: android-get-pb-paywalls
---
---
title: "获取流程与付费墙 - Android"
description: "在 Android 应用中从 Adapty 获取流程和付费墙。"
---
在[设计好流程或付费墙编辑工具付费墙](adapty-paywall-builder)之后,你可以在移动应用中展示它。第一步是获取与版位关联的流程或付费墙及其视图配置,具体如下所述。
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
在移动应用中展示流程之前(点击展开)
1. 在 Adapty 看板中[创建产品](create-product)。
2. 在 Adapty 看板中[创建流程/付费墙并将产品添加到其中](create-paywall)。
3. 在 Adapty 看板中[创建版位并将流程/付费墙添加到其中](create-placement)。
4. 在移动应用中安装 [Adapty SDK](sdk-installation-android)。
## 获取流程/付费墙 \{#fetch-flowpaywall\}
如果你已经使用流程编辑器或付费墙编辑工具设计了流程或付费墙,则无需在移动应用代码中手动处理渲染逻辑来向用户展示它。此类流程或付费墙已包含展示内容和展示方式的完整定义。不过,你仍需通过版位获取其 ID 及视图配置,然后在移动应用中进行呈现。
为了确保最佳性能,请尽早获取流程或付费墙及其[视图配置](android-get-pb-paywalls#fetch-the-view-configuration),以便在向用户展示之前有足够的时间下载图片。
使用 `getFlow` 方法获取流程或付费墙:
```kotlin showLineNumbers
...
Adapty.getFlow("YOUR_PLACEMENT_ID", loadTimeout = 10.seconds) { result ->
when (result) {
is AdaptyResult.Success -> {
val flow = result.value
// the requested flow/paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
...
Adapty.getFlow("YOUR_PLACEMENT_ID", TimeInterval.seconds(10), result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyFlow flow = ((AdaptyResult.Success) result).getValue();
// the requested flow/paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
参数:
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 |
| **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方案,因为它能确保用户始终获取最新数据。
但如果你认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。这种方式下用户可能无法获取最新数据,但无论网络状况如何,都能体验到更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。
请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清理时才会被清除。
Adapty SDK 通过两层机制在本地存储流程和付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速获取,并配备了独立的备用服务器以应对 CDN 不可用的情况。整套系统旨在确保你始终获取最新版本,同时在网络条件较差的情况下也能保证可靠性。
|
| **loadTimeout** | 默认值:5 秒 | 此值限制该方法的超时时间。达到超时时间后,将返回缓存数据或本地备用数据。
请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为底层操作可能由多个请求组成。
对于 Android:你可以使用扩展函数创建 `TimeInterval`(例如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或者使用 `TimeInterval.seconds(5)`。如需不设限制,请使用 `TimeInterval.INFINITE`。
|
| 参数 | 描述 |
| :-------- | :---------- |
| Flow | 一个 `AdaptyFlow` 对象,包含版位信息、标识符(`id`、`variationId`)、名称、远程配置,以及 `hasViewConfiguration` 标志(用于指示该流程是否包含视图配置)。如需为预加载、自定义 UI 或程序化检查获取实际产品,请调用 `getPaywallProducts(flow)`。 |
## 获取视图配置 \{#fetch-the-view-configuration\}
获取流程或付费墙之后,通过 `flow.hasViewConfiguration` 检查其是否包含视图配置。该标志用于区分版位在 Adapty 看板中的设计方式:
- **`true`** — 该版位是在 **Flow Builder**(流程)或 **付费墙编辑工具**(付费墙)中设计的,Adapty 会为您渲染 UI。请继续执行以下步骤,获取视图配置并[展示流程或付费墙](android-present-paywalls)。
- **`false`** — 该版位是没有编辑工具 UI 的自定义付费墙。[将其作为远程配置付费墙处理](present-remote-config-paywalls-android)。
:::important
请确保在 Flow Builder 中启用 **Show on device** 开关。如果未开启此选项,将无法获取视图配置。
:::
使用 `getFlowConfiguration` 方法加载视图配置。
```kotlin showLineNumbers
if (!flow.hasViewConfiguration) {
// use your custom logic
return
}
AdaptyUI.getFlowConfiguration(flow, loadTimeout = 10.seconds) { result ->
when(result) {
is AdaptyResult.Success -> {
val flowConfiguration = result.value
// use loaded configuration
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
| 参数 | 是否必填 | 描述 |
| :-------------- | :----------------------------------------------- | :----------------------------------------------------------- |
| **flow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 |
| **locale** | 可选
默认:设备语言
| [本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符,格式为语言代码,可包含一至两个以 `-` 分隔的子标签(例如 `en`、`pt-br`)。详见[本地化与语言代码](android-localizations-and-locale-codes)。 |
| **loadTimeout** | 默认:5 秒 | 该参数限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。注意,在极少数情况下,由于该方法底层可能包含多个请求,实际超时时间可能略晚于 `loadTimeout` 指定的时间。 |
使用 `getFlowConfiguration` 方法加载视图配置。
```java showLineNumbers
if (!flow.hasViewConfiguration()) {
// use your custom logic
return;
}
AdaptyUI.getFlowConfiguration(flow, TimeInterval.seconds(10), result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyUI.FlowConfiguration flowConfiguration =
((AdaptyResult.Success) result).getValue();
// use loaded configuration
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
| 参数 | 是否必填 | 描述 |
| :-------------- | :------------- | :----------------------------------------------------------- |
| **flow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 |
| **locale** | 可选
默认值:设备语言
| [本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符,格式为由 `-` 分隔的一个或两个子标签的语言代码(例如 `en`、`pt-br`)。详见[本地化与语言代码](android-localizations-and-locale-codes)。 |
| **loadTimeout** | 默认值:5 秒 | 该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。请注意,在极少数情况下,由于该操作在底层可能包含多个请求,实际超时时间可能略晚于 `loadTimeout` 中指定的值。 |
:::note
如果您使用多种语言,请了解如何添加[编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何正确使用语言区域代码,详见[此处](android-localizations-and-locale-codes)。
:::
加载完成后,[展示流程或付费墙](android-present-paywalls)。
## 获取默认目标受众的流程或付费墙以加速加载 \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\}
通常情况下,流程和付费墙的获取几乎是即时完成的,无需担心速度问题。但如果你配置了大量目标受众和版位,且用户的网络连接较差,获取流程或付费墙可能会比预期耗时更长。在这种情况下,你可能希望优先展示默认流程或付费墙,以保障流畅的用户体验,而不是让用户看到空白页面。
为了解决这个问题,您可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的流程或付费墙。但请务必了解,推荐的做法是通过 `getFlow` 方法来获取流程或付费墙,详情请参阅上方的[获取流程/付费墙](#fetch-flowpaywall)部分。
:::warning
为什么我们推荐使用 `getFlow`
`getFlowForDefaultAudience` 方法存在以下几个明显的缺点:
- **潜在的向后兼容性问题**:如果需要为不同的应用版本(当前版本和未来版本)展示不同的流程,可能会面临挑战。你要么设计出兼容当前(旧版)版本的流程,要么接受使用当前(旧版)版本的用户可能遇到流程无法渲染的问题。
- **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一流程,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。
如果您愿意接受这些缺点以换取更快的流程或付费墙获取速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则请继续使用[上文](#fetch-flowpaywall)介绍的 `getFlow`。
:::
```kotlin showLineNumbers
Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val flow = result.value
// the requested flow
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyFlow flow = ((AdaptyResult.Success) result).getValue();
// the requested flow
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **placementId** | 必填 | [版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 |
| **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若请求失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。
不过,如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存存在时直接返回缓存数据。这种方式下用户获取的数据可能不是最新的,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全可靠的。
请注意,缓存在应用重启后仍会保留,仅在应用重新安装或手动清除时才会被清空。
|
## 自定义资源 \{#customize-assets\}
要自定义流程或付费墙中的图片和视频,请实现自定义资源。
主图和视频具有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,您可以通过这些 ID 来定位对应元素并自定义其行为。
对于其他图片和视频,您需要在 Adapty 看板中[设置自定义 ID](custom-media)。
例如,您可以:
- 向部分用户展示不同的图片或视频。
- 在远程主图加载时,先显示本地预览图。
- 在视频播放前显示预览图。
以下是通过简单字典提供自定义资源的示例:
```kotlin showLineNumbers
val customAssets = AdaptyCustomAssets.of(
"hero_image" to
AdaptyCustomImageAsset.remote(
url = "https://example.com/image.jpg",
preview = AdaptyCustomImageAsset.file(
FileLocation.fromAsset("images/hero_image_preview.png"),
)
),
"hero_video" to
AdaptyCustomVideoAsset.file(
FileLocation.fromResId(requireContext(), R.raw.custom_video),
preview = AdaptyCustomImageAsset.file(
FileLocation.fromResId(requireContext(), R.drawable.video_preview),
),
),
)
val flowView = AdaptyUI.getFlowView(
activity,
flowConfiguration,
products,
eventListener,
insets,
customAssets,
)
```
:::note
如果找不到资源,流程将回退到其默认外观。
:::
对于视频,您可以选择传入 `resolution`,在视频加载前预留布局空间并设置宽高比(`width / height`):
```kotlin showLineNumbers
AdaptyCustomVideoAsset.file(
FileLocation.fromResId(requireContext(), R.raw.custom_video),
preview = AdaptyCustomImageAsset.file(
FileLocation.fromResId(requireContext(), R.drawable.video_preview),
),
resolution = AdaptyCustomVideoAsset.Resolution(width = 1080, height = 1920),
)
```
在 [Adapty 看板中使用新版付费墙编辑工具完成付费墙的视觉设计](adapty-paywall-builder)后,你可以在移动应用中展示它。第一步是获取与版位关联的付费墙及其视图配置,具体方法如下。
:::warning
新版付费墙编辑工具需要 Android SDK 3.0 或更高版本。
:::
请注意,本文档适用于通过付费墙编辑工具自定义的付费墙。如果您是手动实现付费墙,请参阅[在移动应用中获取远程配置付费墙的付费墙与产品](fetch-paywalls-and-products-android)。
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
在移动应用中展示付费墙之前(点击展开)
1. 在 Adapty 看板中[创建产品](create-product)。
2. 在 Adapty 看板中[创建付费墙并将产品添加到其中](create-paywall)。
3. 在 Adapty 看板中[创建版位并将付费墙添加到其中](create-placement)。
4. 在你的移动应用中安装 [Adapty SDK](sdk-installation-android)。
## 获取使用付费墙编辑工具设计的付费墙 \{#fetch-paywall-designed-with-paywall-builder\}
如果你已经[使用付费墙编辑工具设计了付费墙](adapty-paywall-builder),则无需在移动端代码中手动渲染并展示给用户。这类付费墙已经包含了展示内容和展示方式的完整配置。不过,你仍需通过版位获取其 ID、视图配置,然后在移动应用中将其呈现出来。
为确保最佳性能,请尽早获取付费墙及其[视图配置](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder),以便在向用户展示之前有足够的时间下载图片。
使用 `getPaywall` 方法获取付费墙:
```kotlin showLineNumbers
...
Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en", loadTimeout = 10.seconds) { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// the requested paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
...
Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", TimeInterval.seconds(10), result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// the requested paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
参数:
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **placementId** | 必填 | 目标[版位](placements)的标识符。这是你在 Adapty 看板中创建版位时指定的值。 |
| **locale** | 可选
默认值:`en`
| [付费墙本地化](add-paywall-locale-in-adapty-paywall-builder)的标识符。该参数应为由连字符(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。
示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。
有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。
|
| **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。
但如果你认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存存在时优先返回缓存数据。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。
请注意,缓存在应用重启后依然保留,只有在应用重新安装或手动清理时才会被清除。
Adapty SDK 通过两层机制在本地存储付费墙:上述定期更新的缓存,以及[备用付费墙](fallback-paywalls)。我们还使用 CDN 加速付费墙的加载,并在 CDN 不可用时提供独立的备用服务器。该系统旨在确保你始终获取最新版本的付费墙,同时在网络条件较差的情况下也能保证可靠性。
|
| **loadTimeout** | 默认值:5 秒 | 该值用于限制此方法的超时时间。超时后将返回缓存数据或本地备用数据。
请注意,在极少数情况下,此方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作底层可能由多个请求组成。
对于 Android:你可以使用扩展函数创建 `TimeInterval`(例如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或使用 `TimeInterval.seconds(5)`。若不设置限制,请使用 `TimeInterval.INFINITE`。
|
响应参数:
| 参数 | 描述 |
| :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Paywall | 一个 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象,包含产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 |
## 获取使用付费墙编辑工具设计的付费墙视图配置 \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\}
:::important
请确保在付费墙编辑工具中开启 **Show on device** 开关。如果未开启该选项,将无法获取视图配置。
:::
获取付费墙后,检查其是否包含 `ViewConfiguration`——这表明该付费墙是使用付费墙编辑工具创建的,并将指导你如何展示该付费墙。如果存在 `ViewConfiguration`,则将其视为付费墙编辑工具付费墙;否则,请[将其作为远程配置付费墙处理](present-remote-config-paywalls)。
使用 `getViewConfiguration` 方法加载视图配置。
```kotlin showLineNumbers
if (!paywall.hasViewConfiguration) {
// use your custom logic
return
}
AdaptyUI.getViewConfiguration(paywall, loadTimeout = 10.seconds) { result ->
when(result) {
is AdaptyResult.Success -> {
val viewConfiguration = result.value
// use loaded configuration
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
| 参数 | 是否必填 | 说明 |
| :-------------- | :------------- | :----------------------------------------------------------- |
| **paywall** | 必填 | `AdaptyPaywall` 对象,用于获取目标付费墙的控制器。 |
| **loadTimeout** | 默认:5 秒 | 该参数限制此方法的超时时间。若超时,将返回缓存数据或本地备用内容。注意:在极少数情况下,实际超时时间可能比 `loadTimeout` 中指定的略长,因为该操作在底层可能由多个请求组成。 |
使用 `getViewConfiguration` 方法加载视图配置。
```java showLineNumbers
if (!paywall.hasViewConfiguration()) {
// use your custom logic
return;
}
AdaptyUI.getViewConfiguration(paywall, TimeInterval.seconds(10), result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyUI.LocalizedViewConfiguration viewConfiguration =
((AdaptyResult.Success) result).getValue();
// use loaded configuration
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
| 参数 | 是否必填 | 描述 |
| :----------------------- | :----------------- | :----------------------------------------------------------- |
| **paywall** | 必填 | 用于获取目标付费墙控制器的 `AdaptyPaywall` 对象。 |
| **loadTimeout** | 默认值:5 秒 | 该值限制此方法的超时时间。若超时,将返回缓存数据或本地备用数据。请注意,在极少数情况下,此方法实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能由多个请求组成。 |
:::note
如果您使用多种语言,请了解如何添加[付费墙编辑工具本地化](add-paywall-locale-in-adapty-paywall-builder),以及如何正确使用语言区域代码,详见[此处](android-localizations-and-locale-codes)。
:::
加载完成后,[展示付费墙](android-present-paywalls)。
## 为默认目标受众获取付费墙以提升加载速度 \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\}
通常情况下,付费墙的获取几乎是即时完成的,无需担心速度问题。但如果你配置了大量目标受众和付费墙,且用户的网络状况较差,获取付费墙的时间可能会超出预期。在这种情况下,你可能希望先展示一个默认付费墙,以保证流畅的用户体验,而不是让用户看到空白。
为解决此问题,您可以使用 `getPaywallForDefaultAudience` 方法,该方法会获取指定版位中**所有用户**目标受众的付费墙。但请务必注意,推荐的做法是通过 `getPaywall` 方法获取付费墙,详见上方[获取付费墙信息](#fetch-paywall-designed-with-paywall-builder)章节。
:::warning
为什么我们推荐使用 `getPaywall`
`getPaywallForDefaultAudience` 方法存在以下几个明显缺陷:
- **潜在的向后兼容问题**:如果需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么将付费墙设计为兼容当前(旧版)版本,要么接受使用当前(旧版)版本的用户可能遇到付费墙无法渲染的问题。
- **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。
如果你愿意接受这些不足之处以换取更快的付费墙加载速度,可以按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请继续使用[上文](#fetch-paywall-designed-with-paywall-builder)介绍的 `getPaywall`。
:::
```kotlin showLineNumbers
Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// the requested paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// the requested paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
:::note
`getPaywallForDefaultAudience` 方法从 Android SDK 2.11.3 开始支持。
:::
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 |
| **locale** | 可选
默认值:`en`
| [付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间用连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。
示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。
有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。
|
| **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,如果请求失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。
但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——如果缓存数据存在,则直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来避免网络请求是安全的。
请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清除时才会被清空。
|
## 自定义资源 \{#customize-assets\}
要自定义付费墙中的图片和视频,需要实现自定义资源。
主图和视频有预定义的 ID:`hero_image` 和 `hero_video`。在自定义资源包中,你可以通过这些 ID 来定位对应元素并自定义其行为。
对于其他图片和视频,你需要在 Adapty 看板中[设置自定义 ID](custom-media)。
例如,你可以:
- 为部分用户展示不同的图片或视频。
- 在远程主图加载时,先显示本地预览图。
- 在播放视频前显示预览图。
:::important
要使用此功能,请将 Adapty Android SDK 更新至 3.7.0 或更高版本。
:::
以下是如何通过简单字典提供自定义资源的示例:
```kotlin showLineNumbers
val customAssets = AdaptyCustomAssets.of(
"hero_image" to
AdaptyCustomImageAsset.remote(
url = "https://example.com/image.jpg",
preview = AdaptyCustomImageAsset.file(
FileLocation.fromAsset("images/hero_image_preview.png"),
)
),
"hero_video" to
AdaptyCustomVideoAsset.file(
FileLocation.fromResId(requireContext(), R.raw.custom_video),
preview = AdaptyCustomImageAsset.file(
FileLocation.fromResId(requireContext(), R.drawable.video_preview),
),
),
)
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
insets,
customAssets,
)
```
:::note
如果找不到某个资源,付费墙将回退到其默认外观。
:::
---
# File: android-present-paywalls
---
---
title: "展示流程与付费墙 - Android"
description: "在 Android 应用中向用户展示流程和付费墙。"
---
如果你已经创建了流程或付费墙,就不需要在移动应用代码中手动处理其渲染逻辑来将其展示给用户。这类流程或付费墙本身已包含展示内容及展示方式的完整定义。
:::warning
本指南适用于由 Adapty 渲染的流程和**新付费墙编辑工具付费墙**。远程配置付费墙和 [Observer 模式](observer-vs-full-mode)的处理方式有所不同。
- 有关展示**远程配置付费墙**,请参阅[展示远程配置设计的付费墙](present-remote-config-paywalls)。
- 有关展示**观察者模式付费墙**,请参阅[Android - 在观察者模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)
:::
如需获取下文使用的 `flowConfiguration` 对象,请参阅[获取流程与付费墙](android-get-pb-paywalls)。
要在设备屏幕上显示可视化流程,必须先进行配置。调用 `AdaptyUI.getFlowView()` 方法或直接创建 `AdaptyFlowView`:
```kotlin showLineNumbers
val flowView = AdaptyUI.getFlowView(
activity,
flowConfiguration,
products,
eventListener,
insets,
customAssets,
tagResolver,
timerResolver,
)
```
```kotlin showLineNumbers
val flowView =
AdaptyFlowView(activity) // or retrieve it from xml
...
with(flowView) {
showFlow(
flowConfiguration,
products,
eventListener,
insets,
customAssets,
tagResolver,
timerResolver,
)
}
```
```java showLineNumbers
AdaptyFlowView flowView = AdaptyUI.getFlowView(
activity,
flowConfiguration,
products,
eventListener,
insets,
customAssets,
tagResolver,
timerResolver
);
```
```java showLineNumbers
AdaptyFlowView flowView =
new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver);
```
```xml showLineNumbers
```
视图成功创建后,你可以将其添加到视图层级中,并在设备屏幕上显示。
如果你获取 `AdaptyFlowView` 时_没有_调用 `AdaptyUI.getFlowView()`,还需要额外调用 `.showFlow()` 方法。
要在设备屏幕上显示可视化流程,必须先进行配置。请使用以下可组合函数:
```kotlin showLineNumbers
AdaptyFlowScreen(
flowConfiguration,
products,
eventListener,
insets,
customAssets,
tagResolver,
timerResolver,
)
```
请求参数:
| 参数 | 是否必填 | 描述 |
| :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **flowConfiguration** | 必填 | 提供一个包含流程视觉详情的 `AdaptyUI.FlowConfiguration` 对象。使用 `AdaptyUI.getFlowConfiguration(flow)` 方法加载它。详情请参阅[获取视图配置](android-get-pb-paywalls#fetch-the-view-configuration)。 |
| **products** | 可选 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在屏幕上的显示时机。如果传入 `null`,AdaptyUI 将自动获取所需产品。 |
| **eventListener** | 可选 | 提供一个 `AdaptyFlowEventListener` 来监听流程事件。建议继承 `AdaptyFlowDefaultEventListener` 以简化使用。详情请参阅[处理流程与付费墙事件](android-handling-events)。 |
| **insets** | 可选 | Insets 是流程周围的间距,用于防止可点击元素被系统状态栏遮挡。
默认值:`Unspecified`,即 Adapty 会自动调整 insets,这对边到边的流程效果很好。
如果你的流程不是边到边的,可能需要设置自定义 insets。具体方法请参阅下方的[修改流程 insets](android-present-paywalls#change-flow-insets) 部分。
|
| **customAssets** | 可选 | 传入一个 `AdaptyCustomAssets` 对象,在运行时替换流程或付费墙中的图片和视频。详情请参阅[自定义资源](android-get-pb-paywalls#customize-assets)。 |
| **tagResolver** | 可选 | 使用 `AdaptyUiTagResolver` 解析流程文本中的自定义标签。该解析器接受标签参数并将其解析为对应的字符串。详情请参阅付费墙编辑工具中的自定义标签相关内容。 |
| **timerResolver** | 可选 | 如果你需要使用自定义计时器功能,请在此传入对应的解析器。 |
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
## 修改流程边距 \{#change-flow-insets\}
边距是指流程周围的空白区域,用于防止可点击元素被系统栏遮挡。默认情况下,Adapty 会自动调整边距,非常适合全面屏流程。
如果你的流程不是全面屏,可以自定义边距:
- 如果状态栏和导航栏都不与 `AdaptyFlowView` 重叠,请使用 `AdaptyFlowInsets.None`。
- 对于更复杂的场景,例如流程与顶部状态栏重叠但不与底部重叠,可以仅将 `bottomInset` 设置为 `0`,如下例所示:
```kotlin showLineNumbers
//create extension function
fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) {
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
ViewCompat.setOnApplyWindowInsetsListener(this, null)
action(systemBarInsets)
insets
}
}
//and then use it with the view
flowView.onReceiveSystemBarsInsets { insets ->
val flowInsets = AdaptyFlowInsets.vertical(insets.top, 0)
flowView.showFlow(
flowConfiguration,
products,
eventListener,
flowInsets,
customAssets,
tagResolver,
timerResolver,
)
}
```
```java showLineNumbers
...
ViewCompat.setOnApplyWindowInsetsListener(flowView, (view, insets) -> {
Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars());
ViewCompat.setOnApplyWindowInsetsListener(flowView, null);
AdaptyFlowInsets flowInsets =
AdaptyFlowInsets.vertical(systemBarInsets.top, 0);
flowView.showFlow(flowConfiguration, products, eventListener, flowInsets);
return insets;
});
```
## 使用开发者自定义计时器 \{#use-developer-defined-timer\}
要在移动应用中使用开发者自定义计时器,请创建一个 `timerResolver` 对象——这是一个字典或映射,用于将自定义计时器与流程渲染时替换它们的字符串值进行配对。示例如下:
```kotlin showLineNumbers
...
val customTimers = mapOf(
"CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025
)
val timerResolver = AdaptyUiTimerResolver { timerId ->
customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } )
}
```
```java showLineNumbers
...
Map customTimers = new HashMap<>();
customTimers.put(
"CUSTOM_TIMER_NY",
new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime()
);
AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() {
@NonNull
@Override
public Date timerEndAtDate(@NonNull String timerId) {
Date date = customTimers.get(timerId);
return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* 1小时后 */
}
};
```
在此示例中,`CUSTOM_TIMER_NY` 是您在 Adapty 看板中设置的开发者自定义计时器的**计时器 ID**。`timerResolver` 确保您的应用动态更新计时器,显示正确的值——例如 `13d 09h 03m 34s`(计算方式为计时器结束时间(如元旦)减去当前时间)。
## 使用自定义标签 \{#use-custom-tags\}
要在移动应用中使用自定义标签,需要创建一个 `tagResolver` 对象——一个将自定义标签与渲染流程时用于替换的字符串值配对的字典或映射。示例如下:
```kotlin showLineNumbers
val customTags = mapOf("USERNAME" to "John")
val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] }
```
```java showLineNumbers
Map customTags = new HashMap<>();
customTags.put("USERNAME", "John");
AdaptyUiTagResolver tagResolver = customTags::get;
```
在此示例中,`USERNAME` 是你在 Adapty 看板中以 `` 形式输入的自定义标签。`tagResolver` 会确保应用动态地将此自定义标签替换为指定的值——例如 `John`。
我们建议在呈现流程之前立即创建并填充 `tagResolver`。准备好后,将其传递给用于呈现流程的 AdaptyUI 方法。
## 更改流程加载指示器颜色 \{#change-flow-loading-indicator-color\}
您可以通过以下方式覆盖加载指示器的默认颜色:
```xml showLineNumbers title = "XML"
```
如果您已使用付费墙编辑工具自定义了付费墙,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示。此类付费墙已包含展示内容及展示方式的全部配置。
:::warning
本指南仅适用于**新版付费墙编辑工具付费墙**,需要 SDK v3.0。不同版本付费墙编辑工具设计的付费墙、远程配置付费墙以及 [Observer 模式](observer-vs-full-mode)的付费墙呈现流程各有不同。
- 如需呈现**远程配置付费墙**,请参阅[渲染远程配置设计的付费墙](present-remote-config-paywalls)。
- 如需呈现 **Observer 模式付费墙**,请参阅 [Android - 在 Observer 模式下呈现付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。
:::
要获取下文中使用的 `viewConfiguration` 对象,请参阅[获取付费墙编辑工具付费墙及其配置](android-get-pb-paywalls)。
要在设备屏幕上显示可视化付费墙,必须先对其进行配置。为此,调用 `AdaptyUI.getPaywallView()` 方法,或直接创建 `AdaptyPaywallView`:
```kotlin showLineNumbers
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
insets,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
```
```kotlin showLineNumbers
val paywallView =
AdaptyPaywallView(activity) // or retrieve it from xml
...
with(paywallView) {
showPaywall(
viewConfiguration,
products,
eventListener,
insets,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
}
```
```java showLineNumbers
AdaptyPaywallView paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
insets,
personalizedOfferResolver,
tagResolver,
timerResolver
);
```
```java showLineNumbers
AdaptyPaywallView paywallView =
new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
paywallView.showPaywall(viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver);
```
```xml showLineNumbers
```
视图成功创建后,你可以将其添加到视图层级中,并在设备屏幕上显示出来。
如果你获取 `AdaptyPaywallView` 的方式_不是_通过调用 `AdaptyUI.getPaywallView()`,还需要额外调用 `.showPaywall()` 方法。
要在设备屏幕上显示可视化付费墙,必须先对其进行配置。请使用以下可组合函数:
```kotlin showLineNumbers
AdaptyPaywallScreen(
viewConfiguration,
products,
eventListener,
insets,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
```
请求参数:
| 参数 | 是否必填 | 描述 |
| :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **viewConfiguration** | 必填 | 提供一个包含付费墙视觉详情的 `AdaptyUI.LocalizedViewConfiguration` 对象。使用 `Adapty.getViewConfiguration(paywall)` 方法加载该对象。详情请参阅[获取付费墙的视觉配置](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder)主题。 |
| **products** | 可选 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在屏幕上的显示时机。如果传入 `null`,AdaptyUI 将自动获取所需产品。 |
| **eventListener** | 可选 | 提供一个 `AdaptyUiEventListener` 以监听付费墙事件。推荐继承 AdaptyUiDefaultEventListener 以简化使用。详情请参阅[处理付费墙事件](android-handling-events)主题。 |
| **insets** | 可选 | Insets 是付费墙周围的间距,用于防止可点击元素被系统栏遮挡。
默认值为 `UNSPECIFIED`,即 Adapty 将自动调整 insets,非常适合全屏边到边付费墙。
如果你的付费墙不是边到边布局,可能需要设置自定义 insets。具体方法请参阅下方的[更改付费墙 insets](android-present-paywalls#change-paywall-insets) 部分。
|
| **personalizedOfferResolver** | 可选 | 如需标记个性化定价([了解更多](https://developer.android.com/google/play/billing/integrate#personalized-price)),请实现 `AdaptyUiPersonalizedOfferResolver` 并传入你自己的逻辑,将 `AdaptyPaywallProduct` 映射为 true(表示该产品价格已个性化)或 false。 |
| **tagResolver** | 可选 | 使用 `AdaptyUiTagResolver` 解析付费墙文本中的自定义标签。该解析器接收一个标签参数,并将其解析为对应的字符串。详情请参阅付费墙编辑工具中的自定义标签主题。 |
| **timerResolver** | 可选 | 如果你要使用自定义计时器功能,请在此处传入对应的解析器。 |
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
## 更改付费墙边距 \{#change-paywall-insets\}
边距是付费墙周围的空白区域,用于防止可点击元素被系统栏遮挡。默认情况下,Adapty 会自动调整边距,这对全屏付费墙效果很好。
如果你的付费墙不是全屏布局,可能需要自定义边距:
- 如果状态栏和导航栏都不与 `AdaptyPaywallView` 重叠,请使用 `AdaptyPaywallInsets.NONE`。
- 对于更复杂的自定义场景,例如付费墙与顶部状态栏重叠但不与底部重叠,可以仅将 `bottomInset` 设置为 `0`,如下例所示:
```kotlin showLineNumbers
//create extension function
fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) {
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
ViewCompat.setOnApplyWindowInsetsListener(this, null)
action(systemBarInsets)
insets
}
}
//and then use it with the view
paywallView.onReceiveSystemBarsInsets { insets ->
val paywallInsets = AdaptyPaywallInsets.vertical(insets.top, 0)
paywallView.showPaywall(
viewConfiguration,
products,
eventListener,
paywallInsets,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
}
```
```java showLineNumbers
...
ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> {
Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars());
ViewCompat.setOnApplyWindowInsetsListener(paywallView, null);
AdaptyPaywallInsets paywallInsets =
AdaptyPaywallInsets.of(systemBarInsets.top, 0);
paywallView.showPaywall(paywall, products, viewConfiguration, paywallInsets, productTitleResolver);
return insets;
});
```
## 使用开发者自定义计时器 \{#use-developer-defined-timer\}
要在移动应用中使用开发者自定义计时器,请创建一个 `timerResolver` 对象——这是一个字典或映射,用于将自定义计时器与付费墙渲染时替换它们的字符串值进行配对。示例如下:
```kotlin showLineNumbers
...
val customTimers = mapOf(
"CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025
)
val timerResolver = AdaptyUiTimerResolver { timerId ->
customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } )
}
```
```java showLineNumbers
...
Map customTimers = new HashMap<>();
customTimers.put(
"CUSTOM_TIMER_NY",
new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime()
);
AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() {
@NonNull
@Override
public Date timerEndAtDate(@NonNull String timerId) {
Date date = customTimers.get(timerId);
return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* in 1 hour */
}
};
```
在此示例中,`CUSTOM_TIMER_NY` 是您在 Adapty 看板中设置的开发者自定义计时器的**计时器 ID**。`timerResolver` 确保您的应用动态更新计时器,显示正确的值——例如 `13d 09h 03m 34s`(由计时器结束时间(如元旦)减去当前时间计算得出)。
## 使用自定义标签 \{#use-custom-tags\}
要在移动应用中使用自定义标签,需创建一个 `tagResolver` 对象——一个将自定义标签与渲染付费墙时用于替换它们的字符串值配对的字典或映射。示例如下:
```kotlin showLineNumbers
val customTags = mapOf("USERNAME" to "John")
val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] }
```
```java showLineNumbers
Map customTags = new HashMap<>();
customTags.put("USERNAME", "John");
AdaptyUiTagResolver tagResolver = customTags::get;
```
在此示例中,`USERNAME` 是你在 Adapty 看板中以 `` 格式输入的自定义标签。`tagResolver` 会确保应用将该自定义标签动态替换为指定的值——例如 `John`。
建议在展示付费墙之前创建并填充 `tagResolver`。准备好后,将其传入用于展示付费墙的 AdaptyUI 方法。
## 更改付费墙加载指示器颜色 \{#change-paywall-loading-indicator-color\}
您可以通过以下方式覆盖加载指示器的默认颜色:
```xml showLineNumbers title = "XML"
```
---
# File: android-handle-paywall-actions
---
---
title: "响应流程操作 - Android"
description: "在 Android 应用中处理流程和付费墙的按钮操作。"
---
如果你正在使用 Adapty 流程编辑工具或付费墙编辑工具构建流程或付费墙,正确设置按钮至关重要:
1. 在[编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。
2. 在应用代码中编写逻辑,处理每个已分配的操作。
本指南介绍如何在代码中处理自定义操作和已有操作。
:::warning
**只有购买、恢复购买、关闭流程/付费墙以及打开 URL 这些操作会自动处理。** 其他所有按钮操作都需要在应用代码中自行实现响应逻辑。
:::
## 关闭流程和付费墙 \{#close-flows-and-paywalls\}
要添加一个用于关闭流程或付费墙的按钮:
1. 在编辑工具中,添加一个按钮并为其分配 **Close** 操作。
2. 在应用代码中,为 `close` 操作实现一个处理函数。
:::info
在 Android SDK 中,`close` 操作默认会触发关闭流程或付费墙。不过,如果需要,你可以在代码中覆盖此行为。例如,关闭一个流程时可以触发打开另一个流程。
:::
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior
}
}
```
## 从流程和付费墙中打开 URL \{#open-urls-from-flows-and-paywalls\}
:::tip
如果你想添加一组链接(例如使用条款和购买恢复),可以在编辑工具中添加 **Link** 元素,并以与具有 **Open URL** 动作的按钮相同的方式处理它。
:::
要添加一个从你的流程或付费墙中打开链接的按钮(例如**使用条款**或**隐私政策**):
1. 在编辑工具中,添加一个按钮,为其分配 **Open URL** 动作,并输入你想打开的 URL。
2. 在你的应用代码中,为 `openUrl` 动作实现一个处理程序,用于在浏览器中打开接收到的 URL。
:::info
在 Android SDK 中,`openUrl` 操作默认会触发打开 URL 的行为。不过,你可以在代码中根据需要覆盖此行为。
:::
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
is AdaptyUI.Action.OpenUrl -> {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior
context.startActivity(intent)
}
}
}
```
## 处理自定义操作 \{#handle-custom-actions\}
如需添加一个处理其他操作的按钮:
1. 在编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并设置一个 ID。
2. 在应用代码中,为你创建的操作 ID 实现对应的处理逻辑。
例如,如果你有另一组订阅优惠或一次性购买,可以添加一个按钮,用于展示另一个流程或付费墙:
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
is AdaptyUI.Action.Custom -> {
if (action.customId == "openNewPaywall") {
// Display another flow or paywall
}
}
}
}
```
如果您使用 Adapty 付费墙编辑工具构建付费墙,请务必正确设置按钮:
1. 在[付费墙编辑工具中添加按钮](paywall-buttons),并为其分配已有操作或创建自定义操作 ID。
2. 在应用代码中编写逻辑,处理你分配的每个操作。
本指南介绍如何在代码中处理自定义操作和已有操作。
:::warning
**购买、恢复购买、关闭付费墙和打开 URL 会自动处理。** 其他所有按钮操作都需要在应用代码中实现相应的处理逻辑。
:::
## 关闭付费墙 \{#close-paywalls\}
要添加一个关闭付费墙的按钮:
1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Close** 操作。
2. 在应用代码中,为 `close` 操作实现一个处理程序,用于关闭付费墙。
:::info
在 Android SDK 中,`close` 操作默认会触发关闭付费墙。不过,如果需要,你可以在代码中覆盖此行为。例如,关闭一个付费墙时可以触发打开另一个付费墙。
:::
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior
}
}
```
## 从付费墙打开 URL \{#open-urls-from-paywalls\}
:::tip
如果你想添加一组链接(例如使用条款和购买恢复),可以在付费墙编辑工具中添加 **Link** 元素,并像处理带有 **Open URL** 动作的按钮一样处理它。
:::
要添加一个从付费墙打开链接的按钮(例如 **Terms of use** 或 **Privacy policy**):
1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Open URL** 动作,并输入你想打开的 URL。
2. 在应用代码中,为 `openUrl` 动作实现一个处理器,用于在浏览器中打开接收到的 URL。
:::info
在 Android SDK 中,`openUrl` 动作默认会触发打开 URL 的行为。不过,你也可以在代码中按需覆盖此行为。
:::
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
is AdaptyUI.Action.OpenUrl -> {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior
context.startActivity(intent)
}
}
}
```
## 登录应用 \{#log-into-the-app\}
要添加一个用于用户登录应用的按钮:
1. 在付费墙编辑工具中,添加一个按钮并为其分配 **Login** 操作。
2. 在应用代码中,实现 `login` 操作的处理逻辑,用于识别您的用户。
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
AdaptyUI.Action.Login -> {
val intent = Intent(context, LoginActivity::class.java)
context.startActivity(intent)
}
}
}
```
## 处理自定义操作 \{#handle-custom-actions\}
要添加一个处理其他操作的按钮:
1. 在付费墙编辑工具中,添加一个按钮,为其分配 **Custom** 操作,并指定一个 ID。
2. 在你的应用代码中,为你创建的操作 ID 实现相应的处理逻辑。
例如,如果你有另一组订阅套餐或一次性购买商品,可以添加一个按钮来展示另一个付费墙:
```kotlin
override fun onActionPerformed(action: AdaptyUI.Action, context: Context) {
when (action) {
is AdaptyUI.Action.Custom -> {
if (action.customId == "openNewPaywall") {
// Display another paywall
}
}
}
}
```
---
# File: android-handling-events
---
---
title: "处理 Flow 与付费墙事件 - Android"
description: "在 Android 应用中处理 Flow 与付费墙事件。"
---
:::important
本指南涵盖购买、恢复、产品选择和流程渲染的事件处理。你还必须实现按钮处理(关闭流程、打开链接等)。详情请参阅我们的[按钮操作处理指南](android-handle-paywall-actions)。
:::
使用[流程编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)配置的流程和付费墙无需额外代码即可完成购买和恢复购买。不过,它们会产生一些可供应用响应的事件,包括按钮点击(关闭按钮、URL、产品选择等)以及购买相关操作的通知。请参阅以下内容了解如何响应这些事件。
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
如果需要控制或监控购买界面上发生的流程,请实现 `AdaptyFlowEventListener` 的各个方法。
如果您希望在某些情况下保留默认行为,可以继承 `AdaptyFlowDefaultEventListener` 并仅覆盖您想修改的方法。
以下是 `AdaptyFlowDefaultEventListener` 中的默认行为。
### 用户生成的事件 \{#user-generated-events\}
#### 选择产品 \{#product-selection\}
当用户或系统选择某个产品进行购买时,将调用以下方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onProductSelected(
product: AdaptyPaywallProduct,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
#### 开始购买 \{#started-purchase\}
当用户发起购买流程时,将调用此方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
product: AdaptyPaywallProduct,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
在 Observer 模式下,该方法不会被调用。详情请参阅 [Android - 在 Observer 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。
#### 购买成功、取消或待处理 \{#successful-canceled-or-pending-purchase\}
如果购买成功,将调用此方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFinished(
purchaseResult: AdaptyPurchaseResult,
product: AdaptyPaywallProduct,
context: Context,
) {
if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
context.getActivityOrNull()?.onBackPressed()
}
```
事件示例(点击展开)
```javascript
// Successful purchase
{
"purchaseResult": {
"type": "Success",
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
}
}
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
// Cancelled purchase
{
"purchaseResult": {
"type": "UserCanceled"
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
// Pending purchase
{
"purchaseResult": {
"type": "Pending"
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
我们建议在这种情况下关闭该页面。
在观察者模式下,该方法不会被调用。详情请参阅 [Android - 在观察者模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode) 主题。
#### 购买失败 \{#failed-purchase\}
如果购买因错误而失败,此方法将被调用。这包括 Google Play Billing 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消操作会触发 `onPurchaseFinished`(结果为已取消),而待处理的支付不会触发此方法。
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFailure(
error: AdaptyError,
product: AdaptyPaywallProduct,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"error": {
"code": "purchase_failed",
"message": "Purchase failed due to insufficient funds",
"details": {
"underlyingError": "Insufficient funds in account"
}
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
该方法在 Observer 模式下不会被调用。详情请参阅 [Android - 在 Observer 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode) 主题。
#### 完成 Web 支付导航 \{#finished-web-payment-navigation\}
此方法在尝试为特定产品打开 [Web 付费墙](web-paywall) 后调用,无论导航成功还是失败均会触发:
```kotlin showLineNumbers title="Kotlin"
public override fun onFinishWebPaymentNavigation(
product: AdaptyPaywallProduct?,
error: AdaptyError?,
context: Context,
) {}
```
**参数:**
| 参数 | 描述 |
|:------------|:------------------------------------------------------------------------------------|
| **product** | 打开网页付费墙时对应的 `AdaptyPaywallProduct`。可以为 `null`。 |
| **error** | 如果网页付费墙导航失败,则为 `AdaptyError` 对象;导航成功时为 `null`。 |
事件示例(点击展开)
```javascript
// Successful navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": null
}
// Failed navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": {
"code": "web_navigation_failed",
"message": "Failed to open web paywall",
"details": {
"underlyingError": "Browser unavailable"
}
}
}
```
#### 购买恢复成功 \{#successful-restore\}
如果购买恢复成功,将调用此方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
profile: AdaptyProfile,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
},
"subscriptions": [
{
"vendorProductId": "premium_monthly",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
]
}
}
```
我们建议在用户已拥有所需 `accessLevel` 时关闭该页面。请参阅[订阅状态](android-listen-subscription-changes)了解如何进行检查。
#### 恢复失败 \{#failed-restore\}
如果 `Adapty.restorePurchases()` 执行失败,将会调用以下方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
error: AdaptyError,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}
```
#### 升级订阅 \{#upgrade-subscription\}
当用户在已有订阅激活的情况下尝试购买新订阅时,你可以通过重写此方法来控制新购买的处理方式。你有两个选项:
1. **用新订阅替换当前订阅**:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
.build()
)
return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
2. **同时保留两个订阅**(单独添加新订阅):
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
:::note
如果你不覆盖此方法,默认行为是保持两个订阅同时有效(等同于使用 `AdaptyPurchaseParameters.Empty`)。
:::
你也可以根据需要设置额外的购买参数:
```kotlin
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
.withOfferPersonalized(true) // optional - if using personalized pricing
.build()
```
事件示例(点击展开)
```javascript
{
"product": {
"vendorProductId": "premium_yearly",
"localizedTitle": "Premium Yearly",
"localizedDescription": "Premium subscription for 1 year",
"localizedPrice": "$99.99",
"price": 99.99,
"currencyCode": "USD"
},
"subscriptionUpdateParams": {
"replacementMode": "with_time_proration"
}
}
```
### 数据获取与渲染 \{#data-fetching-and-rendering\}
#### 产品加载错误 \{#product-loading-errors\}
如果你在初始化时未传入产品数据,AdaptyUI 会自行从服务器获取所需对象。若该操作失败,AdaptyUI 将调用以下方法来上报错误:
```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
error: AdaptyError,
context: Context,
): Boolean = false
```
事件示例(点击展开)
```javascript
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}
```
如果返回 `true`,AdaptyUI 将在 2 秒后重新发起请求。
#### 渲染错误 \{#rendering-errors\}
如果界面渲染过程中发生错误,系统会通过调用以下方法来上报:
```kotlin showLineNumbers title="Kotlin"
public override fun onError(
error: AdaptyError,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"error": {
"code": "rendering_failed",
"message": "Failed to render flow interface",
"details": {
"underlyingError": "Invalid flow configuration"
}
}
}
```
正常情况下不应出现此类错误,如果你遇到了,请告知我们。
### 导航 \{#navigation\}
#### 系统返回按钮 \{#system-back-button\}
默认情况下,用户无法通过系统返回按钮或返回手势退出流程——用户只能通过你定义的路径离开,例如 **Close** 按钮或编辑工具中的 `on_device_back` 动作。如果你希望系统返回按钮能够关闭流程,请重写 `onBackPressed` 并返回 `false`,将该操作交由宿主 Activity 或 Fragment 处理:
```kotlin showLineNumbers title="Kotlin"
public override fun onBackPressed(context: Context): Boolean {
return false // let the host handle the back press (e.g. finish the activity or pop the fragment)
}
```
仅当当前屏幕未配置 `on_device_back` 动作时,才会触发此回调——已配置的动作优先,由内部处理。返回 `true` 表示消费该返回事件(默认行为),返回 `false` 则让宿主自身的返回逻辑继续执行。
### 保留事件 \{#reserved-events\}
`AdaptyFlowEventListener` 声明了一些回调,对应流程尚未使用的功能。你无需自行实现它们——`AdaptyFlowDefaultEventListener` 已经提供了默认的空操作实现。
| 方法 | 描述 |
|:-------|:------------|
| **onAnalyticEvent** | 为流程中的自定义分析事件预留。目前流程尚未向您的代码发送此类事件,因此无需实现。 |
| **onShowAppRate** | 为流程中的应用评价请求预留。目前流程尚未触发应用评价请求,因此无需实现。 |
| **onShowRequestPermission** | 为流程中的系统权限请求(如推送通知或摄像头访问)预留。目前流程尚未触发权限请求,因此无需实现。 |
:::important
本指南介绍购买、恢复、产品选择和付费墙渲染的事件处理。你还需要实现按钮处理(关闭付费墙、打开链接等)。详情请参阅[按钮操作处理指南](android-handle-paywall-actions)。
:::
通过[付费墙编辑工具](adapty-paywall-builder)配置的付费墙无需额外代码即可完成购买和恢复购买操作。但它们会生成一些事件,供你的应用响应。这些事件包括按钮点击(关闭按钮、URL、产品选择等)以及付费墙上与购买相关操作的通知。请阅读以下内容,了解如何响应这些事件。
:::warning
本指南仅适用于**新版付费墙编辑工具付费墙**,需要 Adapty SDK v3.0 或更高版本。
:::
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
如果需要控制或监控购买页面上发生的流程,请实现 `AdaptyUiEventListener` 的相关方法。
如果某些情况下希望保留默认行为,可以继承 `AdaptyUiDefaultEventListener`,只覆写需要更改的方法。
以下是 `AdaptyUiDefaultEventListener` 的默认行为。
### 用户触发事件 \{#user-generated-events\}
#### 产品选择 \{#product-selection\}
当用户或系统选择某个产品进行购买时,将调用此方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onProductSelected(
product: AdaptyPaywallProduct,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
#### 已开始购买 \{#started-purchase\}
当用户发起购买流程时,将调用此方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
product: AdaptyPaywallProduct,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
该方法在 Observer 模式下不会被调用。详情请参阅 [Android - 在 Observer 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。
#### 购买成功、取消或待处理 \{#successful-canceled-or-pending-purchase\}
购买成功时,将调用以下方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFinished(
purchaseResult: AdaptyPurchaseResult,
product: AdaptyPaywallProduct,
context: Context,
) {
if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
context.getActivityOrNull()?.onBackPressed()
}
```
事件示例(点击展开)
```javascript
// Successful purchase
{
"purchaseResult": {
"type": "Success",
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
}
}
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
// Cancelled purchase
{
"purchaseResult": {
"type": "UserCanceled"
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
// Pending purchase
{
"purchaseResult": {
"type": "Pending"
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
我们建议在这种情况下关闭当前页面。
该方法在 Observer 模式下不会被调用。详情请参阅 [Android - 在 Observer 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。
#### 购买失败 \{#failed-purchase\}
如果购买因错误而失败,将调用此方法。这包括 Google Play Billing 错误(支付限制、无效产品、网络故障)、交易验证失败以及系统错误。请注意,用户取消操作会触发 `onPurchaseFinished`(结果为 cancelled),待处理的支付则不会触发此方法。
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFailure(
error: AdaptyError,
product: AdaptyPaywallProduct,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"error": {
"code": "purchase_failed",
"message": "Purchase failed due to insufficient funds",
"details": {
"underlyingError": "Insufficient funds in account"
}
},
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}
```
该方法在 Observer 模式下不会被调用。详情请参考 [Android - 在 Observer 模式下展示付费墙编辑工具付费墙](android-present-paywall-builder-paywalls-in-observer-mode)。
#### 完成网页支付导航 \{#finished-web-payment-navigation\}
此方法在尝试为特定产品打开[网页付费墙](web-paywall)后触发,包括导航成功和失败两种情况:
```kotlin showLineNumbers title="Kotlin"
public override fun onFinishWebPaymentNavigation(
product: AdaptyPaywallProduct?,
error: AdaptyError?,
context: Context,
) {}
```
**参数:**
| 参数 | 说明 |
|:------------|:-----------------------------------------------------------------------------------------|
| **product** | 打开网页付费墙时对应的 `AdaptyPaywallProduct`。可以为 `null`。 |
| **error** | 若网页付费墙跳转失败,则为 `AdaptyError` 对象;跳转成功时为 `null`。 |
事件示例(点击展开)
```javascript
// Successful navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": null
}
// Failed navigation
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
},
"error": {
"code": "web_navigation_failed",
"message": "Failed to open web paywall",
"details": {
"underlyingError": "Browser unavailable"
}
}
}
```
#### 购买恢复成功 \{#successful-restore\}
如果购买恢复成功,将调用此方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
profile: AdaptyProfile,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"profile": {
"accessLevels": {
"premium": {
"id": "premium",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
},
"subscriptions": [
{
"vendorProductId": "premium_monthly",
"isActive": true,
"expiresAt": "2024-02-15T10:30:00Z"
}
]
}
}
```
我们建议在用户拥有所需 `accessLevel` 时关闭该页面。请参阅[订阅状态](android-listen-subscription-changes)主题,了解如何进行检查。
#### 恢复失败 \{#failed-restore\}
如果 `Adapty.restorePurchases()` 失败,将调用此方法:
```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
error: AdaptyError,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}
```
#### 升级订阅 \{#upgrade-subscription\}
当用户在已有活跃订阅的情况下尝试购买新订阅时,你可以通过重写此方法来控制新购买的处理方式。你有两个选项:
1. **用新订阅替换当前订阅**:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
.build()
)
return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
2. **保留两个订阅**(单独添加新订阅):
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
:::note
如果不覆盖此方法,默认行为是保持两个订阅同时有效(等同于使用 `AdaptyPurchaseParameters.Empty`)。
:::
您也可以根据需要设置额外的购买参数:
```kotlin
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
.withOfferPersonalized(true) // optional - if using personalized pricing
.build()
```
如果在某个订阅仍处于活跃状态时购买了新订阅,可覆盖此方法,将当前订阅替换为新订阅。如果希望保留原有的活跃订阅,同时单独添加新订阅,则调用 `onSubscriptionUpdateParamsReceived(null)`:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingSubscriptionUpdateParams(
product: AdaptyPaywallProduct,
context: Context,
onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
) {
onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
}
```
事件示例(点击展开)
```javascript
{
"product": {
"vendorProductId": "premium_yearly",
"localizedTitle": "Premium Yearly",
"localizedDescription": "Premium subscription for 1 year",
"localizedPrice": "$99.99",
"price": 99.99,
"currencyCode": "USD"
},
"subscriptionUpdateParams": {
"replacementMode": "with_time_proration"
}
}
```
### 数据获取与渲染 \{#data-fetching-and-rendering\}
#### 产品加载错误 \{#product-loading-errors\}
如果你在初始化时没有传入产品,AdaptyUI 会自动从服务器获取所需对象。若该操作失败,AdaptyUI 将调用以下方法来报告错误:
```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
error: AdaptyError,
context: Context,
): Boolean = false
```
事件示例(点击展开)
```javascript
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}
```
如果返回 `true`,AdaptyUI 将在 2 秒后重试请求。
#### 渲染错误 \{#rendering-errors\}
如果在界面渲染过程中发生错误,系统将通过调用以下方法来上报:
```kotlin showLineNumbers title="Kotlin"
public override fun onRenderingError(
error: AdaptyError,
context: Context,
) {}
```
事件示例(点击展开)
```javascript
{
"error": {
"code": "rendering_failed",
"message": "Failed to render paywall interface",
"details": {
"underlyingError": "Invalid paywall configuration"
}
}
}
```
正常情况下不应出现此类错误,如果遇到,请告知我们。
---
# File: android-use-fallback-paywalls
---
---
title: "Android - 使用备用付费墙"
description: "处理用户离线或 Adapty 服务器不可用的情况。"
---
:::warning
备用付费墙需要 Android SDK v2.11 及更高版本支持。
:::
为了保持流畅的用户体验,请务必为您的流程、[付费墙](paywalls)和[用户引导](onboardings)设置[备用方案](/fallback-paywalls)。这一预防措施可以在网络部分或完全中断时,确保应用仍能正常运行。
* **若应用无法访问 Adapty 服务器:**
应用可以显示备用流程或付费墙,并读取本地的用户引导配置。
* **若应用无法访问互联网:**
应用可以显示备用流程或付费墙。用户引导包含远程内容,需要联网才能正常使用。
:::important
在按照本指南操作之前,请先从 Adapty [下载](/local-fallback-paywalls)备用配置文件。
:::
## 配置 \{#configuration\}
1. 将备用配置文件移动到 Android 项目的 `assets` 或 `res/raw` 目录中。
2. 在获取目标流程、付费墙或用户引导**之前**,调用 `.setFallback` 方法。
```kotlin showLineNumbers
//if you put the 'android_fallback.json' file to the 'assets' directory
val location = FileLocation.fromAsset("android_fallback.json")
//or `FileLocation.fromAsset("/android_fallback.json")` if you placed it in a child folder of 'assets')
//if you put the 'android_fallback.json' file to the 'res/raw' directory
val location = FileLocation.fromResId(context, R.raw.android_fallback)
//you can also pass a file URI
val fileUri: Uri = //get Uri for the file with fallback paywalls
val location = FileLocation.fromFileUri(fileUri)
//pass the file location
Adapty.setFallback(location, callback)
```
```java showLineNumbers
//if you put the 'android_fallback.json' file to the 'assets' directory
FileLocation location = FileLocation.fromAsset("android_fallback.json");
//or `FileLocation.fromAsset("/android_fallback.json");` if you placed it in a child folder of 'assets')
//if you put the 'android_fallback.json' file to the 'res/raw' directory
FileLocation location = FileLocation.fromResId(context, R.raw.android_fallback);
//you can also pass a file URI
Uri fileUri = //get Uri for the file with fallback paywalls
FileLocation location = FileLocation.fromFileUri(fileUri);
//pass the file location
Adapty.setFallback(location, callback);
```
参数:
| 参数 | 描述 |
| :----------- | :----------------------------------------------------------- |
| **location** | 备用配置文件的 [FileLocation](https://android.adapty.io/adapty/com.adapty.utils/-file-location/-companion/) 对象 |
---
# File: android-localizations-and-locale-codes
---
---
title: "在 Android SDK 中使用本地化和语言区域代码"
description: "管理应用本地化和语言区域代码,触达全球用户(Android)。"
---
## 为什么这很重要 \{#why-this-is-important\}
语言区域代码会在多种场景下发挥作用——例如,当您尝试为应用的当前本地化版本获取正确的付费墙时。
由于语言区域代码较为复杂,且在不同平台间存在差异,我们为所有支持的平台制定了统一的内部标准。然而,正因为这些代码较为复杂,了解您究竟向服务器发送了什么内容以获取正确的本地化版本,以及后续发生了什么,对您来说非常重要——这样您才能始终获得预期的结果。
## Adapty 的语言区域代码标准 \{#locale-code-standard-at-adapty\}
Adapty 使用略经修改的 [BCP 47 标准](https://en.wikipedia.org/wiki/IETF_language_tag)作为语言区域代码规范:每个代码由小写子标签组成,以连字符分隔。示例如下:`en`(英语)、`pt-br`(葡萄牙语(巴西))、`zh`(简体中文)、`zh-hant`(繁体中文)。
## 语言区域代码匹配 \{#locale-code-matching\}
当 Adapty 收到客户端 SDK 发来的包含语言区域代码的请求,并开始查找付费墙对应本地化版本时,将执行以下步骤:
1. 传入的语言区域字符串转换为小写,所有下划线(`_`)替换为连字符(`-`)
2. 查找与完整语言区域代码完全匹配的本地化版本
3. 如果未找到匹配项,则截取第一个连字符之前的子字符串(例如 `pt-br` 对应 `pt`),并再次查找匹配的本地化版本
4. 如果仍未找到匹配项,则返回默认的 `en` 本地化版本
通过这种方式,发送了 `'pt_BR'` 的 iOS 设备、发送了 `pt-BR` 的 Android 设备,以及发送了 `pt-br` 的其他设备,都将获得相同的结果。
## 实现本地化:推荐方式 \{#implementing-localizations-recommended-way\}
如果您正在考虑本地化问题,很可能已经在项目中处理了本地化字符串文件。在这种情况下,我们建议在每个本地化对应的字符串文件中,添加一个键值对来存储预期的 Adapty 语言区域代码,然后在调用 SDK 时提取该键对应的值,如下所示:
```kotlin showLineNumbers
// 1. Modify your strings.xml files
/*
strings.xml - Spanish
*/
es
/*
strings.xml - Portuguese (Brazil)
*/
pt-br
// 2. Extract and use the locale code
val localeCode = context.getString(R.string.adapty_paywalls_locale)
// pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method
```
这样,您可以完全掌控每位应用用户将获取的本地化版本。
## 实现本地化:其他方式 \{#implementing-localizations-the-other-way\}
您也可以在不为每个本地化版本显式定义语言区域代码的情况下,获得类似(但不完全相同)的结果。这意味着需要从平台提供的其他对象中提取语言区域代码,如下所示:
```kotlin showLineNumbers
val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
context.resources.configuration.locales[0]
else
context.resources.configuration.locale
val localeCode = locale.toLanguageTag()
// pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method
```
请注意,我们不推荐此方式,因为很难预测 Adapty 服务器实际接收到的内容。
如果您仍决定使用此方式,请确保已覆盖所有相关的使用场景。
---
# File: android-web-paywall
---
---
title: "在 Android SDK 中实现网页付费墙"
description: "设置网页付费墙,无需支付 Play Store 费用和审核即可收款。"
---
:::important
在开始之前,请确保您已[在看板中配置了网页付费墙](web-paywall),并安装了 Adapty SDK 3.15 或更高版本。
:::
## 打开网页付费墙 \{#open-web-paywalls\}
如果您使用的是自行开发的付费墙,则需要通过 SDK 方法来处理网页付费墙。`.openWebPaywall` 方法会:
1. 生成一个唯一 URL,使 Adapty 能够将向特定用户展示的付费墙与其被重定向到的网页关联起来。
2. 追踪用户返回应用的时机,并以短时间间隔请求 `.getProfile`,以判断用户画像的访问权限是否已更新。
这样一来,如果付款成功且访问权限已更新,订阅几乎会立即在应用中激活。
:::note
用户返回应用后,请刷新 UI 以反映用户画像的更新。Adapty 将接收并处理用户画像更新事件。
:::
```kotlin showLineNumbers
Adapty.openWebPaywall(
activity = activity,
product = product,
) { error ->
if (error == null) {
// the web paywall was opened successfully
} else {
// handle the error
}
}
```
:::note
`openWebPaywall` 方法有两个版本:
1. `openWebPaywall(product)`:通过付费墙生成 URL,并将产品数据添加到 URL 中。
2. `openWebPaywall(paywall)`:通过付费墙生成 URL,但不将产品数据添加到 URL 中。当 Adapty 付费墙中的产品与网页付费墙中的产品不同时,请使用此版本。
:::
## 在应用内浏览器中打开网页付费墙 \{#open-web-paywalls-in-an-in-app-browser\}
默认情况下,网页付费墙会在外部浏览器中打开。
为提供无缝的用户体验,您可以在应用内浏览器中打开网页付费墙。这样会在您的应用内显示网页购买页面,让用户无需切换应用即可完成交易。
要启用此功能,请将 `presentation` 参数设置为 `AdaptyWebPresentation.InAppBrowser`:
```kotlin showLineNumbers
Adapty.openWebPaywall(
activity = activity,
product = product,
presentation = AdaptyWebPresentation.InAppBrowser,
) { error ->
if (error == null) {
// the web paywall was opened successfully
} else {
// handle the error
val adaptyError = error
}
}
```
---
# File: android-troubleshoot-paywall-builder
---
---
title: "在 Android SDK 中排查付费墙编辑工具问题"
description: "在 Android SDK 中排查付费墙编辑工具问题"
---
本指南帮助您解决在 Android SDK 中使用 Adapty 付费墙编辑工具设计付费墙时遇到的常见问题。
## 获取付费墙配置失败 \{#getting-a-paywall-configuration-fails\}
**问题**:`getViewConfiguration` 方法无法获取付费墙配置。
**原因**:该付费墙未在付费墙编辑工具中启用设备显示。
**解决方案**:在付费墙编辑工具中启用 **Show on device** 开关。
## 付费墙展示次数过多 \{#the-paywall-view-number-is-too-big\}
**问题**:付费墙展示次数显示为预期值的两倍。
**原因**:你可能在代码中调用了 `logShowFlow`(Android SDK v4+)/ `logShowPaywall`,如果你使用的是付费墙编辑工具或 Flow Builder,这会导致展示次数重复计算。对于使用这些工具构建的流程和付费墙,分析数据会自动追踪,无需手动调用此方法。
**解决方案**:如果你使用的是付费墙编辑工具或 Flow Builder,请确保代码中没有调用 `logShowFlow`(Android SDK v4+)/ `logShowPaywall`。
## 其他问题 \{#other-issues\}
**问题**:您遇到了上述未涵盖的其他付费墙编辑工具相关问题。
**解决方案**:如有需要,请参考[迁移指南](android-sdk-migration-guides)将 SDK 升级至最新版本。许多问题已在较新的 SDK 版本中得到修复。
---
# File: android-quickstart-manual
---
---
title: "在 Android SDK 的自定义付费墙中启用购买功能"
description: "将 Adapty SDK 集成到自定义 Android 付费墙中,以启用应用内购买功能。"
---
本指南介绍如何将 Adapty 集成到自定义付费墙中。你可以完全掌控付费墙的实现方式,同时由 Adapty SDK 负责获取产品、处理新购买以及恢复历史购买记录。
:::important
**本指南适用于需要自行实现自定义付费墙的开发者。** 如果你希望以最简便的方式开启购买功能,请使用 [Adapty Flow Builder](android-quickstart-paywalls)。使用 Flow Builder,你可以在无代码可视化编辑器中创建流程,Adapty 自动处理所有购买逻辑,并且无需重新发布应用即可测试不同的设计方案。
:::
## 开始之前 \{#before-you-start\}
### 设置产品 \{#set-up-products\}
要启用应用内购买,你需要了解三个关键概念:
- [**产品**](product) – 用户可以购买的任何内容(订阅、消耗型商品、永久授权)
- [**付费墙**](paywalls) – 定义向用户展示哪些产品的配置。在 Adapty 中,付费墙是获取产品的唯一方式,但这种设计让你无需修改应用代码就能调整产品、价格和优惠。
- [**版位**](placements) – 应用中展示付费墙的位置和时机(如 `main`、`onboarding`、`settings`)。你在看板中为版位配置付费墙,然后在代码里通过版位 ID 请求对应的付费墙。这样就能轻松运行 A/B 测试,并向不同用户展示不同的付费墙。
即使你使用自定义付费墙,也需要了解这些概念。简单来说,它们就是你在应用中管理所售产品的方式。
要实现自定义付费墙,你需要创建一个**付费墙**并将其添加到一个**版位**。这样才能获取你的产品。如果想了解需要在看板中完成哪些操作,请参考[这里](quickstart)的快速入门指南。
### 管理用户 \{#manage-users\}
您可以选择在您的后端使用或不使用身份验证。
但是,Adapty SDK 对匿名用户和已识别用户的处理方式不同。请阅读[用户识别快速入门指南](android-quickstart-identify)以了解具体细节,确保您正确处理用户信息。
## 第一步:获取产品 \{#step-1-get-products\}
要为自定义付费墙获取产品,你需要:
1. 通过将[版位](placements) ID 传递给 `getFlow` 方法来获取 `flow` 对象。
2. 使用 `getPaywallProducts` 方法获取该流程的产品数组。
```kotlin showLineNumbers
fun loadPaywall() {
Adapty.getFlow("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val flow = result.value
Adapty.getPaywallProducts(flow) { productResult ->
when (productResult) {
is AdaptyResult.Success -> {
val products = productResult.value
// Use products to build your custom paywall UI
}
is AdaptyResult.Error -> {
val error = productResult.error
// Handle the error
}
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
}
```
```java showLineNumbers
public void loadPaywall() {
Adapty.getFlow("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyFlow flow = ((AdaptyResult.Success) result).getValue();
Adapty.getPaywallProducts(flow, productResult -> {
if (productResult instanceof AdaptyResult.Success) {
List products = ((AdaptyResult.Success>) productResult).getValue();
// Use products to build your custom paywall UI
} else if (productResult instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) productResult).getError();
// Handle the error
}
});
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
}
```
## 步骤 2. 接受购买 \{#step-2-accept-purchases\}
当用户在自定义付费墙中点击某个产品时,请调用 `makePurchase` 方法并传入所选产品。该方法会处理购买流程并返回更新后的用户画像。
```kotlin showLineNumbers
fun purchaseProduct(activity: Activity, product: AdaptyPaywallProduct) {
Adapty.makePurchase(activity, product) { result ->
when (result) {
is AdaptyResult.Success -> {
when (val purchaseResult = result.value) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
// Purchase successful, profile updated
}
is AdaptyPurchaseResult.UserCanceled -> {
// User canceled the purchase
}
is AdaptyPurchaseResult.Pending -> {
// Purchase is pending (e.g., user will pay offline with cash)
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
}
```
```java showLineNumbers
public void purchaseProduct(Activity activity, AdaptyPaywallProduct product) {
Adapty.makePurchase(activity, product, null, result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue();
if (purchaseResult instanceof AdaptyPurchaseResult.Success) {
AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile();
// 购买成功,用户画像已更新
} else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) {
// 用户取消了购买
} else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) {
// 购买待处理(例如,用户将以现金线下付款)
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// 处理错误
}
});
}
```
## 第三步:恢复购买 \{#step-3-restore-purchases\}
Google Play 及其他应用商店要求所有包含订阅功能的应用提供让用户恢复购买的方式。
当用户点击恢复购买按钮时,调用 `restorePurchases` 方法。该方法会将用户的购买历史与 Adapty 同步,并返回更新后的用户画像。
```kotlin showLineNumbers
fun restorePurchases() {
Adapty.restorePurchases { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
// Restore successful, profile updated
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
}
```
```java showLineNumbers
public void restorePurchases() {
Adapty.restorePurchases(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
// Restore successful, profile updated
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
}
```
## 后续步骤 \{#next-steps\}
:::tip
有疑问或遇到问题?欢迎访问我们的[支持论坛](https://adapty.featurebase.app/),在那里你可以找到常见问题的解答,也可以提出自己的问题。我们的团队和社区随时为你提供帮助!
:::
您的付费墙已准备好在应用中展示。[在 Google Play Store 上测试您的购买流程](testing-on-android),确保您可以从付费墙完成测试购买。如需了解生产环境中的完整实现方式,请参阅我们示例应用中的 [ProductListFragment.kt](https://github.com/adaptyteam/AdaptySDK-Android/blob/master/app/src/main/java/com/adapty/example/ProductListFragment.kt),其中演示了包含完善错误处理、UI 反馈和订阅管理的购买处理逻辑。
接下来,[检查用户是否已完成购买](android-check-subscription-status),以判断是否需要展示付费墙或开放付费功能。
---
# File: fetch-paywalls-and-products-android
---
---
title: "在 Android SDK 中获取远程配置付费墙的付费墙和产品"
description: "在 Adapty Android SDK 中获取付费墙和产品,提升用户变现效果。"
---
在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本文内容涉及远程配置和自定义付费墙。如需了解如何获取在 **Flow Builder** 或 **Paywall Builder** 中配置的流程或付费墙,请参阅[获取流程与付费墙](android-get-pb-paywalls)。
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
在开始获取流程和产品之前(点击展开)
1. 在 Adapty 看板中[创建产品](create-product)。
2. 在 Adapty 看板中[创建流程或付费墙,并将产品添加到其中](create-paywall)。
3. 在 Adapty 看板中[创建版位,并将流程或付费墙添加到版位中](create-placement)。
4. 在移动应用中[安装 Adapty SDK](sdk-installation-android)。
## 获取流程信息 \{#fetch-flow-information\}
在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的组合体。这些跨平台产品被整合到流程和付费墙中,让你可以在移动应用的特定版位中展示它们。
要展示产品,你需要通过 `getFlow` 方法从某个[版位](placements)获取 `AdaptyFlow`。
:::important
**不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。流程是远程配置的,因此产品数量和可用优惠随时可能变化。你的应用必须动态处理这些变化——如果一个流程今天返回两个产品,明天返回三个,无需修改代码即可全部展示。
:::
```kotlin showLineNumbers
Adapty.getFlow("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val flow = result.value
// the requested flow
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getFlow("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyFlow flow = ((AdaptyResult.Success) result).getValue();
// the requested flow
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **placementId** | 必填 | [版位](placements)的标识符,即您在 Adapty 看板中创建版位时所指定的值。 || **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。
但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存存在时直接返回缓存数据。这种情况下用户获取的数据可能不是最新的,但无论网络状况多差,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。
请注意,重启应用后缓存依然保留,只有在重新安装应用或手动清理时才会清除。
Adapty SDK 通过两个层级存储流程和付费墙:上述定期更新的缓存,以及[备用付费墙](android-use-fallback-paywalls)。我们还使用 CDN 加速流程和付费墙的获取,并在 CDN 不可达时提供独立的备用服务器。
|
| **loadTimeout** | 默认值:5 秒 | 此值限制该方法的超时时间。若超时,将返回缓存数据或本地备用数据。
请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为底层操作可能包含多个请求。
|
不要硬编码产品 ID!由于流程是远程配置的,可用产品、产品数量以及特殊优惠(如免费试用)都可能随时变化。请确保你的代码能够处理这些情况。
例如,如果你最初获取到 2 个产品,应用应显示这 2 个产品;但如果之后获取到 3 个产品,应用无需修改任何代码即可显示全部 3 个产品。唯一需要硬编码的是版位 ID。
响应参数:
| 参数 | 描述 |
| :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Flow | 一个 `AdaptyFlow` 对象,包含版位、标识符(`id`、`variationId`)、名称、`remoteConfigs` 数组(每个已配置语言区域对应一条记录)以及 `hasViewConfiguration` 标志。如需获取该 flow 的产品,请调用 `getPaywallProducts(flow)`。 |
:::note
在 v4 中,`locale` 参数已从 `getFlow` 移至 `getFlowConfiguration`(仅在使用 AdaptyUI 渲染时使用)。对于自定义付费墙,所有可用的语言区域将一并在 `flow.remoteConfigs` 中返回——请选择与用户设备或应用设置相匹配的语言区域。
:::
## 获取产品 \{#fetch-products\}
获取流程后,你可以查询与其对应的产品数组:
```kotlin showLineNumbers
Adapty.getPaywallProducts(flow) { result ->
when (result) {
is AdaptyResult.Success -> {
val products = result.value
// the requested products
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywallProducts(flow, result -> {
if (result instanceof AdaptyResult.Success) {
List products = ((AdaptyResult.Success>) result).getValue();
// the requested products
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
响应参数:
| 参数 | 描述 |
| :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Products | [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他若干属性。 |
在实现自定义流程设计时,你可能需要访问 [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象中的相关属性。以下列出了最常用的属性,完整属性列表请参阅上方链接文档。
| 属性 | 描述 |
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Title** | 要显示产品标题,请使用 `product.localizedTitle`。请注意,本地化基于用户所选的商店国家/地区,而非设备本身的语言区域设置。 |
| **Price** | 要显示本地化价格,请使用 `product.price.localizedString`。该本地化基于设备的语言区域信息。你也可以通过 `product.price.amount` 以数字形式获取价格,该值以当地货币为单位。要获取对应的货币符号,请使用 `product.price.currencySymbol`。 |
| **Subscription Period** | 要显示订阅周期(如周、月、年等),请使用 `product.subscriptionDetails?.localizedSubscriptionPeriod`。该本地化基于设备的语言区域。若需以编程方式获取订阅周期,请使用 `product.subscriptionDetails?.subscriptionPeriod`。通过该属性可访问 `unit` 枚举以获取周期单位(即 DAY、WEEK、MONTH、YEAR 或 UNKNOWN)。`numberOfUnits` 值表示周期单位的数量。例如,对于季度订阅,`unit` 属性值为 `MONTH`,`numberOfUnits` 属性值为 `3`。 |
| **Introductory Offer** | 要显示徽标或其他指示器来表明订阅包含新用户优惠,请查看 `product.subscriptionDetails?.introductoryOfferPhases` 属性。这是一个列表,最多可包含两个折扣阶段:免费试用阶段和新用户优惠价格阶段。每个阶段对象包含以下实用属性:
• `paymentMode`:枚举类型,取值为 `FREE_TRIAL`、`PAY_AS_YOU_GO`、`PAY_UPFRONT` 和 `UNKNOWN`。免费试用对应 `FREE_TRIAL` 类型。
• `price`:折扣价格(数字形式)。免费试用时该值为 `0`。
• `localizedNumberOfPeriods`:使用设备语言区域本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段中显示为 `3 days`。
• `subscriptionPeriod`:也可通过此属性获取优惠周期的详细信息,其使用方式与上一节关于订阅周期的描述相同。
• `localizedSubscriptionPeriod`:针对用户语言区域格式化的折扣订阅周期字符串。 |
## 通过默认目标受众流程加速流程加载 \{#speed-up-flow-fetching-with-default-audience-flow\}
通常情况下,流程的加载几乎是即时完成的,无需为此担心。但如果你配置了大量目标受众和版位,且用户的网络连接较差,流程加载可能会比预期慢。在这种情况下,你可能希望展示一个默认流程,以确保用户体验流畅,而不是什么都不显示。
为了解决这个问题,您可以使用 `getFlowForDefaultAudience` 方法,该方法会获取指定版位中 **All Users** 目标受众的流程。但需要特别注意的是,推荐的方式是通过 `getFlow` 方法来获取流程,详情请参阅上文的[获取流程信息](fetch-paywalls-and-products-android#fetch-flow-information)章节。
:::warning
为什么我们推荐使用 `getFlow`
`getFlowForDefaultAudience` 方法存在以下几个明显的缺陷:
- **潜在的向后兼容性问题**:如果您需要为不同的应用版本(当前版本和未来版本)展示不同的流程,可能会面临挑战。您要么必须设计能够支持当前(旧版)版本的流程,要么接受使用当前(旧版)版本的用户可能无法正常渲染流程的风险。
- **失去精准定向**:所有用户都将看到为 **All Users** 目标受众设计的同一个流程,这意味着您将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。
如果您愿意接受这些缺点以换取更快的流程获取速度,请按如下方式使用 `getFlowForDefaultAudience` 方法。否则,请继续使用[上文](fetch-paywalls-and-products-android#fetch-flow-information)中介绍的 `getFlow`。
:::
```kotlin showLineNumbers
Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val flow = result.value
// the requested flow
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyFlow flow = ((AdaptyResult.Success) result).getValue();
// the requested flow
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 || **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若请求失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。
但如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`——当缓存存在时直接返回缓存数据。这样一来,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来减少网络请求是安全可靠的。
请注意,重启应用不会清除缓存,只有卸载重装或手动清理才会清空缓存。
|
在展示远程配置和自定义付费墙之前,您需要先获取相关信息。请注意,本主题适用于远程配置和自定义付费墙。如需了解如何获取付费墙编辑工具自定义付费墙的相关指导,请参阅[获取付费墙编辑工具的付费墙及其配置](android-get-pb-paywalls)。
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
在应用中开始获取付费墙和产品之前(点击展开)
1. 在 Adapty 看板中[创建产品](create-product)。
2. 在 Adapty 看板中[创建付费墙并将产品添加到付费墙中](create-paywall)。
3. 在 Adapty 看板中[创建版位并将付费墙添加到版位中](create-placement)。
4. 在您的移动应用中[安装 Adapty SDK](sdk-installation-android)。
## 获取付费墙信息 \{#fetch-paywall-information\}
在 Adapty 中,[产品](product)是 App Store 和 Google Play 产品的统一组合。这些跨平台产品被集成到付费墙中,让你可以在移动应用的特定版位展示它们。
要展示产品,你需要通过 `getPaywall` 方法从某个[版位](placements)获取[付费墙](paywalls)。
:::important
**不要硬编码产品 ID。** 唯一需要硬编码的是版位 ID。付费墙是远程配置的,产品数量和可用优惠随时可能变化。你的应用必须动态处理这些变化——如果付费墙今天返回两个产品,明天返回三个,则应在不修改代码的情况下全部展示。
:::
```kotlin showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// the requested paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// the requested paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 |
| **locale** | 可选
默认值:`en`
| [付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。
示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。
有关语言区域代码及推荐用法,请参阅[本地化与语言区域代码](android-localizations-and-locale-codes)。
|
| **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐使用此方式,因为它能确保用户始终获取最新数据。
但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,都能享受更快的加载速度。缓存会定期更新,因此在会话期间使用缓存以减少网络请求是安全的。
请注意,缓存在应用重启后仍然保留,只有在重新安装应用或手动清理时才会被清除。
Adapty SDK 通过两层机制存储付费墙:上述定期更新的缓存,以及[备用付费墙](android-use-fallback-paywalls)。我们还使用 CDN 加速付费墙的获取,并在 CDN 不可用时提供独立的备用服务器。这套机制旨在确保您始终能获取最新版本的付费墙,同时在网络条件较差时也能保持可靠性。
|
| **loadTimeout** | 默认值:5 秒 | 该值限制了此方法的超时时间。若超时,将返回缓存数据或本地备用数据。
请注意,在少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的值,因为该操作在底层可能包含多个不同的请求。
|
不要硬编码产品 ID!由于付费墙是远程配置的,可用产品的数量以及特殊优惠(如免费试用)都可能随时变化。请确保你的代码能够处理这些情况。
例如,如果你最初获取到 2 个产品,应用应显示这 2 个产品;但如果后来获取到 3 个产品,应用无需修改代码即可显示全部 3 个。唯一需要硬编码的是版位 ID。
返回参数:
| 参数 | 描述 |
| :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Paywall | 一个 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象,包含:产品 ID 列表、付费墙标识符、远程配置及其他若干属性。 |
## 获取产品 \{#fetch-products\}
获取付费墙后,你可以查询与其对应的产品数组:
```kotlin showLineNumbers
Adapty.getPaywallProducts(paywall) { result ->
when (result) {
is AdaptyResult.Success -> {
val products = result.value
// the requested products
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywallProducts(paywall, result -> {
if (result instanceof AdaptyResult.Success) {
List products = ((AdaptyResult.Success>) result).getValue();
// the requested products
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
响应参数:
| 参数 | 描述 |
| :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Products | [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象列表,包含:产品标识符、产品名称、价格、货币、订阅时长及其他多个属性。 |
在实现自定义付费墙设计时,你可能需要访问 [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象中的以下属性。下面列出的是最常用的属性,完整的属性说明请参阅链接文档。
| 属性 | 描述 |
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **标题** | 使用 `product.localizedTitle` 显示产品名称。请注意,本地化内容基于用户在应用商店选择的国家/地区,而非设备本身的语言设置。 |
| **价格** | 使用 `product.price.localizedString` 显示本地化价格,该本地化基于设备的语言区域信息。你也可以通过 `product.price.amount` 以数字形式获取价格,值以本地货币为单位。要获取对应的货币符号,请使用 `product.price.currencySymbol`。 |
| **订阅周期** | 使用 `product.subscriptionDetails?.localizedSubscriptionPeriod` 显示周期(如周、月、年等),该本地化基于设备的语言区域。如需以编程方式获取订阅周期,请使用 `product.subscriptionDetails?.subscriptionPeriod`。通过该属性可访问 `unit` 枚举,获取周期长度(即 DAY、WEEK、MONTH、YEAR 或 UNKNOWN)。`numberOfUnits` 表示周期单位的数量。例如,季度订阅的 unit 属性为 `MONTH`,numberOfUnits 属性为 `3`。 |
| **新用户优惠** | 如需显示"包含新用户优惠"的徽章或其他标识,请查看 `product.subscriptionDetails?.introductoryOfferPhases` 属性。该列表最多包含两个折扣阶段:免费试用阶段和优惠价格阶段。每个阶段对象包含以下实用属性:
• `paymentMode`:枚举值,包括 `FREE_TRIAL`、`PAY_AS_YOU_GO`、`PAY_UPFRONT` 和 `UNKNOWN`。免费试用对应 `FREE_TRIAL` 类型。
• `price`:以数字表示的折扣价格。免费试用时,该值为 `0`。
• `localizedNumberOfPeriods`:根据设备语言区域本地化的字符串,描述优惠时长。例如,三天试用优惠在此字段显示为 `3 days`。
• `subscriptionPeriod`:也可通过此属性获取优惠周期的具体详情,其使用方式与上文描述的订阅周期相同。
• `localizedSubscriptionPeriod`:针对用户语言区域格式化的折扣订阅周期。 |
## 通过默认目标受众付费墙加速付费墙获取 \{#speed-up-paywall-fetching-with-default-audience-paywall\}
通常情况下,付费墙的获取几乎是即时完成的,无需特别担心速度问题。但如果你配置了大量目标受众和付费墙,且用户的网络连接较差,获取付费墙可能会比预期耗时更长。在这种情况下,你可能希望先展示一个默认付费墙,以确保用户体验流畅,而不是什么都不显示。
要解决这个问题,你可以使用 `getPaywallForDefaultAudience` 方法,该方法会获取指定版位中针对 **All Users** 目标受众的付费墙。但需要特别注意的是,推荐的做法是通过 `getPaywall` 方法来获取付费墙,详情请参阅上方的[获取付费墙信息](fetch-paywalls-and-products-android#fetch-paywall-information)章节。
:::warning
为什么我们推荐使用 `getPaywall`
`getPaywallForDefaultAudience` 方法存在以下几个明显缺陷:
- **潜在的向后兼容性问题**:如果你需要为不同的应用版本(当前版本和未来版本)展示不同的付费墙,可能会遇到挑战。你要么将付费墙设计成兼容当前(旧版)版本,要么接受使用当前(旧版)版本的用户可能会遇到付费墙无法渲染的问题。
- **精准定向缺失**:所有用户都将看到为 **All Users** 目标受众设计的同一个付费墙,这意味着你将失去个性化定向能力(包括基于国家、营销归因或自定义属性的定向)。
如果您愿意接受这些不足之处,以换取更快的付费墙加载速度,请按如下方式使用 `getPaywallForDefaultAudience` 方法。否则,请使用上文介绍的 `getPaywall` 方法([详见上文](fetch-paywalls-and-products-android#fetch-paywall-information))。
:::
```kotlin showLineNumbers
Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// the requested paywall
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
// the requested paywall
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
:::note
`getPaywallForDefaultAudience` 方法从 Android SDK 2.11.3 版本开始支持。
:::
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **placementId** | 必填 | [版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 |
| **locale** | 可选
默认值:`en`
| [付费墙本地化](add-remote-config-locale)的标识符。该参数应为语言代码,由一个或多个子标签组成,子标签之间以连字符(**-**)分隔。第一个子标签表示语言,第二个子标签表示地区。
示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。
有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](android-localizations-and-locale-codes)。
|
| **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若失败则返回缓存数据。我们推荐使用此选项,因为它能确保用户始终获取最新数据。
不过,如果您认为用户的网络环境不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时优先返回缓存。这种情况下,用户获取的数据可能不是最新的,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存来避免额外的网络请求是安全可靠的。
请注意,缓存在应用重启后依然保留,仅在重新安装应用或手动清除时才会被清空。
|
---
# File: present-remote-config-paywalls-android
---
---
title: "在 Android SDK 中渲染远程配置设计的付费墙"
description: "了解如何在 Adapty Android SDK 中展示远程配置付费墙以个性化用户体验。"
---
如果你使用远程配置自定义了付费墙,则需要在移动应用代码中自行实现渲染逻辑,才能将其展示给用户。由于远程配置的灵活性完全由你掌控,付费墙的内容和外观都取决于你的设计。Adapty 提供了获取远程配置的方法,让你能够自主展示自定义付费墙。
## 获取 Flow 远程配置并展示 \{#get-flow-remote-config-and-present-it\}
在 v4 中,一个 flow 在 `remoteConfigs` 数组中为每个已配置的语言环境携带一个 `AdaptyRemoteConfig` 条目。选取与用户偏好匹配的语言环境,然后读取所需的值。
```kotlin showLineNumbers
Adapty.getFlow("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val flow = result.value
val config = flow.remoteConfigs.firstOrNull { it.locale == "en" }
?: flow.remoteConfigs.firstOrNull()
val headerText = config?.dataMap?.get("header_text") as? String
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getFlow("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyFlow flow = ((AdaptyResult.Success) result).getValue();
AdaptyRemoteConfig config = null;
for (AdaptyRemoteConfig remoteConfig : flow.getRemoteConfigs()) {
if ("en".equals(remoteConfig.getLocale())) {
config = remoteConfig;
break;
}
}
if (config == null && !flow.getRemoteConfigs().isEmpty()) {
config = flow.getRemoteConfigs().get(0);
}
if (config != null && config.getDataMap().get("header_text") instanceof String) {
String headerText = (String) config.getDataMap().get("header_text");
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
至此,一旦您获取了所有必要的数据,就可以将其渲染并组装成一个美观的页面。请确保设计能够适配各种手机屏幕尺寸和方向,为不同设备提供流畅且友好的用户体验。
:::warning
请务必按照以下说明[记录付费墙浏览事件](present-remote-config-paywalls-android#track-paywall-view-events),以便 Adapty 分析系统能够采集漏斗和 A/B 测试所需的数据。
:::
完成付费墙的展示后,继续设置购买流程。当用户发起购买时,只需使用流程中的产品调用 `.makePurchase()` 方法即可。有关 `.makePurchase()` 方法的详细信息,请参阅[发起购买](android-making-purchases)。
我们建议[创建一个备用付费墙](android-use-fallback-paywalls)。当用户没有网络连接或无可用缓存时,备用付费墙会自动展示,确保用户在任何情况下都能获得流畅的体验。
## 追踪付费墙浏览事件 \{#track-paywall-view-events\}
Adapty 帮助你衡量流程和付费墙的表现。购买数据会自动收集,但浏览事件需要你手动记录,因为只有你知道用户何时看到了某个流程。
要记录浏览事件,只需调用 `.logShowFlow(flow)`,相关数据便会反映在漏斗和 A/B 测试的数据指标中。
:::important
如果你使用 [Flow Builder](adapty-flow-builder) 或 [付费墙编辑工具](adapty-paywall-builder) 渲染流程或付费墙,则无需调用 `.logShowFlow(flow)`。Adapty 在这些情况下会自动记录浏览行为。
:::
```kotlin showLineNumbers
Adapty.logShowFlow(flow)
```
请求参数:
| 参数 | 是否必填 | 描述 |
| :-------- | :------- |:-----------------------------------------------------------------------------------------|
| **flow** | 必填 | 通过 `Adapty.getFlow` 获取的 `AdaptyFlow` 对象。 |
如果你通过远程配置自定义了付费墙,则需要在移动应用代码中实现渲染逻辑,才能将其展示给用户。远程配置具有高度灵活性,完全由你掌控——付费墙包含哪些内容、界面如何呈现,都取决于你的设计。我们提供了获取远程配置的方法,让你能够自主展示通过远程配置搭建的自定义付费墙。
## 获取付费墙远程配置并呈现 \{#get-paywall-remote-config-and-present-it\}
要获取付费墙的远程配置,请访问 `remoteConfig` 属性并提取所需的值。
```kotlin showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getPaywall("YOUR_PLACEMENT_ID", result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue();
AdaptyPaywall.RemoteConfig remoteConfig = paywall.getRemoteConfig();
if (remoteConfig != null) {
if (remoteConfig.getDataMap().get("header_text") instanceof String) {
String headerText = (String) remoteConfig.getDataMap().get("header_text");
}
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
此时,一旦您获取了所有必要的值,就可以将它们渲染并组合成一个视觉上吸引人的页面。请确保设计能够适配各种手机屏幕尺寸和方向,在不同设备上提供流畅且用户友好的体验。
:::warning
请务必按照下方说明[记录付费墙查看事件](present-remote-config-paywalls-android#track-paywall-view-events),以便 Adapty 分析系统能够收集漏斗和 A/B 测试所需的数据。
:::
展示付费墙后,请继续设置购买流程。当用户发起购买时,只需使用付费墙中的产品调用 `.makePurchase()`。有关 `.makePurchase()` 方法的详细信息,请阅读[发起购买](android-making-purchases)。
我们建议[创建一个备用付费墙(即备用付费墙)](android-use-fallback-paywalls)。当没有网络连接或缓存可用时,该备用付费墙将向用户展示,确保在这些情况下依然提供流畅的体验。
## 追踪付费墙查看事件 \{#track-paywall-view-events\}
Adapty 帮助您衡量付费墙的效果。虽然我们会自动收集购买数据,但记录付费墙查看事件需要您的参与,因为只有您才知道用户何时看到了付费墙。
要记录付费墙查看事件,只需调用 `.logShowPaywall(paywall)`,该事件将反映在漏斗和 A/B 测试的付费墙数据图表中。
:::important
如果您展示的是在[付费墙编辑工具](adapty-paywall-builder)中创建的付费墙,则无需调用 `.logShowPaywall(paywall)`。
:::
```kotlin showLineNumbers
Adapty.logShowPaywall(paywall)
```
请求参数:
| 参数 | 是否必填 | 描述 |
| :---------- | :------- |:------------------------------------------------------------------------------------------------------------|
| **paywall** | 必填 | 一个 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象。 |
---
# File: android-making-purchases
---
---
title: "在 Android SDK 中进行应用内购买"
description: "使用 Adapty 处理应用内购买和订阅的指南。"
---
在移动应用中展示付费墙是向用户提供高级内容或服务访问权限的重要步骤。然而,仅仅展示付费墙只有在您使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙时,才足以支持购买流程。
如果您不使用付费墙编辑工具,则必须使用名为 `.makePurchase()` 的独立方法来完成购买并解锁所需内容。该方法是用户与付费墙交互并完成所需交易的入口。
如果您的付费墙对用户尝试购买的产品设有有效的促销活动,Adapty 将在购买时自动应用该优惠。
:::warning
请注意,新用户优惠只有在您使用付费墙编辑工具设置付费墙时才会自动应用。
在其他情况下,您需要[验证用户是否符合 iOS 上新用户优惠的资格](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios)。跳过此步骤可能导致您的应用在发布审核时被拒绝,还可能对符合新用户优惠资格的用户收取全价。
:::
请确保您已[完成初始配置](quickstart),不要跳过任何步骤。没有完成配置,我们将无法验证购买。
## 进行购买 \{#make-purchase\}
:::note
**正在使用[付费墙编辑工具](adapty-paywall-builder)?** 购买将自动处理——您可以跳过此步骤。
**需要分步指导?** 请查阅[快速入门指南](android-implement-paywalls-manually),获取包含完整上下文的端到端实现说明。
:::
```kotlin showLineNumbers
Adapty.makePurchase(activity, product, null) { result ->
when (result) {
is AdaptyResult.Success -> {
when (val purchaseResult = result.value) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
// Grant access to the paid features
}
}
is AdaptyPurchaseResult.UserCanceled -> {
// Handle the case where the user canceled the purchase
}
is AdaptyPurchaseResult.Pending -> {
// Handle deferred purchases (e.g., the user will pay offline with cash)
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
```
```java showLineNumbers
Adapty.makePurchase(activity, product, null, result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue();
if (purchaseResult instanceof AdaptyPurchaseResult.Success) {
AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile();
AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
if (premium != null && premium.isActive()) {
// Grant access to the paid features
}
} else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) {
// Handle the case where the user canceled the purchase
} else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) {
// Handle deferred purchases (e.g., the user will pay offline with cash)
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
```
请求参数:
| 参数 | 必要性 | 描述 |
| :------------ | :------- | :-------------------------------------------------------------------------------------------------- |
| **Product** | 必填 | 从付费墙中获取的 [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) 对象。 |
响应参数:
| 参数 | 描述 |
|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Profile** | 如果请求成功,响应将包含此对象。[AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) 对象提供了关于用户访问等级、订阅及应用内非订阅购买的全面信息。
请检查访问等级状态,以确认用户是否拥有访问应用所需的权限。
|
:::warning
**注意:** 如果你使用的 Apple StoreKit 版本低于 v2.0,且 Adapty SDK 版本低于 v2.9.0,则需要提供 [Apple App Store 共享密钥](app-store-connection-configuration#step-5-enter-app-store-shared-secret)。此方法目前已被 Apple 弃用。
:::
## 购买时更改订阅 \{#change-subscription-when-making-a-purchase\}
当用户选择新订阅而不是续订当前订阅时,具体行为取决于应用商店。对于 Google Play,订阅不会自动更新。您需要按照以下说明在移动应用代码中管理切换操作。
要在 Android 中将订阅替换为另一个订阅,请使用附加参数调用 `.makePurchase()` 方法:
```kotlin showLineNumbers
Adapty.makePurchase(
activity,
product,
AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(subscriptionUpdateParams)
.build()
) { result ->
when (result) {
is AdaptyResult.Success -> {
when (val purchaseResult = result.value) {
is AdaptyPurchaseResult.Success -> {
val profile = purchaseResult.profile
// successful cross-grade
}
is AdaptyPurchaseResult.UserCanceled -> {
// user canceled the purchase flow
}
is AdaptyPurchaseResult.Pending -> {
// the purchase has not been finished yet, e.g. user will pay offline by cash
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
```
附加请求参数:
| 参数 | 必要性 | 描述 |
| :----------------------------- | :------- | :----------------------------------------------------------- |
| **subscriptionUpdateParams** | 必填 | 一个 [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/) 对象。 |
```java showLineNumbers
Adapty.makePurchase(
activity,
product,
new AdaptyPurchaseParameters.Builder()
.withSubscriptionUpdateParams(subscriptionUpdateParams)
.build(),
result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue();
if (purchaseResult instanceof AdaptyPurchaseResult.Success) {
AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile();
// successful cross-grade
} else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) {
// user canceled the purchase flow
} else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) {
// the purchase has not been finished yet, e.g. user will pay offline by cash
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// Handle the error
}
});
```
附加请求参数:
| 参数 | 必要性 | 描述 |
| :----------------------------- | :------- | :----------------------------------------------------------- |
| **subscriptionUpdateParams** | 必填 | 一个 [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/) 对象。 |
您可以在 Google 开发者文档中了解更多关于订阅和替换模式的内容:
- [关于替换模式](https://developer.android.com/google/play/billing/subscriptions#replacement-modes)
- [Google 对替换模式的建议](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations)
- 替换模式 [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE())。注意:此方法仅适用于订阅升级,不支持降级。
- 替换模式 [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED())。注意:实际的订阅变更将在当前订阅计费周期结束时才会发生。
### 管理预付费套餐 \{#manage-prepaid-plans\}
如果您的应用用户可以购买[预付费套餐](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans)(例如,购买数月的非续订订阅),您可以为预付费套餐启用[待处理交易](https://developer.android.com/google/play/billing/subscriptions#pending)。
```kotlin showLineNumbers
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withEnablePendingPrepaidPlans(true)
.build()
```
```java showLineNumbers
new AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withEnablePendingPrepaidPlans(true)
.build();
```
---
# File: android-restore-purchase
---
---
title: "在 Android SDK 中恢复移动应用内的购买"
description: "了解如何在 Adapty 中恢复购买,以确保无缝的用户体验。"
---
恢复购买是一项允许用户重新获得之前购买内容(例如订阅或应用内购买)访问权限的功能,且无需再次付费。该功能对于那些可能卸载后重新安装了应用,或切换到新设备并希望访问之前购买内容而无需再次付款的用户尤为有用。
:::note
在使用[付费墙编辑工具](adapty-paywall-builder)构建的付费墙中,购买会自动恢复,无需您编写额外代码。如果您的情况属于此类,可以跳过此步骤。
:::
如果您未使用[付费墙编辑工具](adapty-paywall-builder)来自定义付费墙,请调用 `.restorePurchases()` 方法来恢复购买:
```kotlin showLineNumbers
Adapty.restorePurchases { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
// successful access restore
}
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.restorePurchases(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
if (profile != null) {
AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL");
if (premium != null && premium.isActive()) {
// successful access restore
}
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
响应参数:
| 参数 | 描述 |
|---------|-----------|
| **Profile** | 一个 [`AdaptyProfile`](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) 对象。该模型包含访问等级、订阅及非订阅购买的相关信息。
请检查**访问等级状态**以确定用户是否有权访问该应用。
|
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
---
# File: implement-observer-mode-android
---
---
title: "在 Android SDK 中实现观察者模式"
description: "在 Adapty 中实现观察者模式,以在 Android SDK 中追踪用户订阅事件。"
---
如果您已经拥有自己的购买基础设施,并且还未准备好完全切换到 Adapty,您可以探索[观察者模式](observer-vs-full-mode)。在其基本形式下,观察者模式提供高级分析功能,并可与归因和分析系统无缝集成。
如果这满足您的需求,您只需要:
1. 在配置 Adapty SDK 时通过将 `observerMode` 参数设置为 `true` 来开启该模式。请按照 [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk) 的设置说明进行操作。
2. 将您现有购买基础设施中的[交易上报](report-transactions-observer-mode-android)给 Adapty。
## 观察者模式设置 \{#observer-mode-setup\}
如果您自行处理购买和订阅状态,并使用 Adapty 发送订阅事件和分析数据,请开启观察者模式。
:::important
在观察者模式下运行时,Adapty SDK 不会关闭任何交易,请确保您自行处理这些交易。
:::
```kotlin showLineNumbers
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Adapty.activate(
applicationContext,
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withObserverMode(true) //default false
.build()
)
}
```
```java showLineNumbers
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Adapty.activate(
applicationContext,
new AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withObserverMode(true) //default false
.build()
);
}
```
参数:
| 参数 | 描述 |
| --------------------------- | ------------------------------------------------------------ |
| observerMode | 一个布尔值,用于控制[观察者模式](observer-vs-full-mode)。默认值为 `false`。 |
## 在观察者模式中使用 Adapty 付费墙 \{#using-adapty-paywalls-in-observer-mode\}
如果您还希望使用 Adapty 的付费墙和 A/B 测试功能,您可以这样做——但在观察者模式下需要一些额外的设置。除上述步骤外,您还需要执行以下操作:
1. 按照常规方式展示[远程配置付费墙](present-remote-config-paywalls-android)。对于付费墙编辑工具付费墙,请参阅 [Android](android-present-paywall-builder-paywalls-in-observer-mode) 的专项设置指南。
3. 将付费墙与购买交易[关联](report-transactions-observer-mode-android)。
---
# File: report-transactions-observer-mode-android
---
---
title: "在 Android SDK 的观察者模式下报告交易"
description: "在 Android SDK 的 Adapty 观察者模式下报告购买交易,以获取用户洞察和收入追踪。"
---
在观察者模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的购买。您需要从应用商店手动报告交易。在发布应用之前务必完成此设置,以避免分析数据出现错误。
使用 `reportTransaction` 显式报告每笔交易,以便 Adapty 识别。
:::warning
**请勿跳过交易报告!**
如果您不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成服务。
:::
如果您使用 Adapty 付费墙,请在报告交易时包含 `variationId`。这会将购买与触发该购买的付费墙关联起来,从而确保付费墙分析数据的准确性。
```kotlin showLineNumbers
val transactionInfo = TransactionInfo.fromPurchase(purchase)
Adapty.reportTransaction(transactionInfo, variationId) { result ->
if (result is AdaptyResult.Success) {
// success
}
}
```
参数:
| 参数 | 是否必填 | 描述 |
| --------------- | -------- | ------------------------------------------------------------ |
| transactionInfo | 必填 | 来自购买的 TransactionInfo,其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 |
| variationId | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 |
```java showLineNumbers
TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase);
Adapty.reportTransaction(transactionInfo, variationId, result -> {
if (result instanceof AdaptyResult.Success) {
// success
}
});
```
参数:
| 参数 | 是否必填 | 描述 |
| --------------- | -------- | ------------------------------------------------------------ |
| transactionInfo | 必填 | 来自购买的 TransactionInfo,其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 |
| variationId | 可选 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 |
在观察者模式下,Adapty SDK 无法自动追踪通过您现有购买系统完成的购买。您需要从应用商店报告交易或恢复购买。在发布应用之前务必完成此设置,以避免分析数据出现错误。
使用 `restorePurchases` 向 Adapty 报告交易。
:::warning
**请勿跳过购买恢复!**
如果您不调用 `restorePurchases`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成服务。
:::
如果您使用 Adapty 付费墙,请使用 `setVariationId` 方法将交易与触发购买的付费墙关联起来。这可确保购买被正确归因到触发付费墙,从而获得准确的分析数据。此步骤仅在您使用 Adapty 付费墙时才有必要。
```kotlin showLineNumbers
Adapty.restorePurchases { result ->
if (result is AdaptyResult.Success) {
// success
}
}
Adapty.setVariationId(transactionId, variationId) { error ->
if (error == null) {
// success
}
}
```
参数:
| 参数 | 是否必填 | 描述 |
| ------------- | -------- | ------------------------------------------------------------ |
| transactionId | 必填 | 购买的字符串标识符(`purchase.getOrderId`),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 |
| variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 |
```java showLineNumbers
Adapty.restorePurchases(result -> {
if (result instanceof AdaptyResult.Success) {
// success
}
});
Adapty.setVariationId(transactionId, variationId, error -> {
if (error == null) {
// success
}
});
```
参数:
| 参数 | 是否必填 | 描述 |
| ------------- | -------- | ------------------------------------------------------------ |
| transactionId | 必填 | 购买的字符串标识符(`purchase.getOrderId`),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 |
| variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 |
**报告交易**
使用 `restorePurchases` 在观察者模式下向 Adapty 报告交易,详情请参阅[在移动端代码中恢复购买](android-restore-purchase)页面。
:::warning
**请勿跳过交易报告!**
如果您不调用 `restorePurchases`,Adapty 将无法识别该交易,它不会出现在分析数据中,也不会被发送到集成服务。
:::
**将付费墙与交易关联**
由于您负责处理购买,Adapty SDK 无法确定购买来源。因此,如果您打算在观察者模式下使用付费墙和/或 A/B 测试,则需要在移动应用代码中将来自应用商店的交易与相应的付费墙关联起来。在发布应用之前务必正确完成此设置,否则将导致分析数据出现错误。
```kotlin
Adapty.setVariationId(transactionId, variationId) { error ->
if (error == null) {
// success
}
}
```
请求参数:
| 参数 | 是否必填 | 描述 |
| ------------- | -------- | ------------------------------------------------------------ |
| transactionId | 必填 | 购买的字符串标识符(purchase.getOrderId),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 |
| variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 |
```java
Adapty.setVariationId(transactionId, variationId, error -> {
if (error == null) {
// success
}
});
```
| 参数 | 是否必填 | 描述 |
| ------------- | -------- | ------------------------------------------------------------ |
| transactionId | 必填 | 购买的字符串标识符(purchase.getOrderId),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。 |
| variationId | 必填 | 实验变体的字符串标识符。您可以通过 [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 |
---
# File: android-present-paywall-builder-paywalls-in-observer-mode
---
---
title: "在 Android SDK 中以观察者模式呈现付费墙编辑工具创建的付费墙"
description: "了解如何使用 Adapty 的付费墙编辑工具在观察者模式下呈现付费墙。"
---
如果你使用流程编辑工具或付费墙编辑工具创建了流程或付费墙,无需在移动端代码中手动处理渲染逻辑,即可将其展示给用户。这类流程或付费墙本身已包含展示内容和展示方式的完整定义。
:::warning
本节仅适用于[观察者模式](observer-vs-full-mode)。如果你不使用观察者模式,请参阅 [Android - 展示流程与付费墙](android-present-paywalls)。
:::
开始展示流程之前(点击展开)
1. 完成 Adapty [与 Google Play 的初始集成](initial-android)。
2. 安装并配置 Adapty SDK,确保将 `observerMode` 参数设置为 `true`。请参阅我们针对特定框架的说明 [Android 版](sdk-installation-android)。
3. 在 Adapty 看板中[创建产品](create-product)。
4. [在编辑工具中配置流程或付费墙](create-paywall),并为其分配产品。
5. 在 Adapty 看板中[创建版位并为其分配流程或付费墙](create-placement)。
6. 在移动应用代码中[获取流程及其配置](android-get-pb-paywalls)。
1. 实现 `AdaptyUiObserverModeHandler`。
当用户发起购买时,`onPurchaseInitiated` 事件会通知你。你可以在此回调中触发自定义的购买流程:
```kotlin showLineNumbers
val observerModeHandler =
AdaptyUiObserverModeHandler { product, flow, flowView, onStartPurchase, onFinishPurchase ->
onStartPurchase()
yourBillingClient.makePurchase(
product,
onSuccess = { purchase ->
onFinishPurchase()
//handle success
},
onError = {
onFinishPurchase()
//handle error
},
onCancel = {
onFinishPurchase()
//handle cancel
}
)
}
```
```java showLineNumbers
AdaptyUiObserverModeHandler observerModeHandler = (product, flow, flowView, onStartPurchase, onFinishPurchase) -> {
onStartPurchase.invoke();
yourBillingClient.makePurchase(
product,
purchase -> {
onFinishPurchase.invoke();
//handle success
},
error -> {
onFinishPurchase.invoke();
//handle error
},
() -> { //cancellation
onFinishPurchase.invoke();
//handle cancel
}
);
};
```
要在观察者模式下处理恢复操作,请重写 `getRestoreHandler()`。默认情况下它返回 `null`,此时会使用 Adapty 内置的 `Adapty.restorePurchases()` 流程。如需自定义恢复逻辑:
```kotlin showLineNumbers
val observerModeHandler = object : AdaptyUiObserverModeHandler {
// onPurchaseInitiated implementation (see above)
override fun getRestoreHandler() =
AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore ->
onStartRestore()
yourBillingClient.restorePurchases(
onSuccess = { restoredPurchases ->
onFinishRestore()
//handle successful restore
},
onError = {
onFinishRestore()
//handle error
}
)
}
}
```
```java showLineNumbers
AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() {
// onPurchaseInitiated implementation (see above)
@Override
public RestoreHandler getRestoreHandler() {
return (onStartRestore, onFinishRestore) -> {
onStartRestore.invoke();
yourBillingClient.restorePurchases(
restoredPurchases -> {
onFinishRestore.invoke();
//handle successful restore
},
error -> {
onFinishRestore.invoke();
//handle error
}
);
};
}
};
```
记得调用以下回调函数,以便将购买或恢复流程的状态通知 AdaptyUI。这对于正确的流程行为(例如显示加载器)是必要的:
| 回调 | 描述 |
| :----------------- |:---------------------------------------------------------------------------------------|
| onStartPurchase() | 应调用此回调以通知 AdaptyUI 购买已开始。 |
| onFinishPurchase() | 应调用此回调以通知 AdaptyUI 购买已完成。 |
| onStartRestore() | 可选。可调用此回调以通知 AdaptyUI 恢复已开始。 |
| onFinishRestore() | 可选。可调用此回调以通知 AdaptyUI 恢复已完成。 |
2. 要在设备屏幕上显示可视化流程,必须先对其进行配置。
为此,请调用 `AdaptyUI.getFlowView()` 方法,或直接创建 `AdaptyFlowView`:
```kotlin showLineNumbers
val flowView = AdaptyUI.getFlowView(
activity,
flowConfiguration,
products,
eventListener,
insets,
customAssets,
tagResolver,
timerResolver,
observerModeHandler,
)
```
```kotlin showLineNumbers
val flowView =
AdaptyFlowView(activity) // or retrieve it from xml
...
with(flowView) {
showFlow(
flowConfiguration,
products,
eventListener,
insets,
customAssets,
tagResolver,
timerResolver,
observerModeHandler,
)
}
```
```java showLineNumbers
AdaptyFlowView flowView = AdaptyUI.getFlowView(
activity,
flowConfiguration,
products,
eventListener,
insets,
customAssets,
tagResolver,
timerResolver,
observerModeHandler
);
```
```java showLineNumbers
AdaptyFlowView flowView =
new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler);
```
```xml showLineNumbers
```
视图成功创建后,即可将其添加到视图层级并显示。
使用以下可组合函数:
```kotlin showLineNumbers
AdaptyFlowScreen(
flowConfiguration,
products,
eventListener,
insets,
customAssets,
tagResolver,
timerResolver,
observerModeHandler,
)
```
请求参数:
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **flowConfiguration** | 必填 | 提供一个包含流程视觉详情的 `AdaptyUI.FlowConfiguration` 对象。使用 `AdaptyUI.getFlowConfiguration(flow)` 方法加载该对象。详情请参阅[获取视图配置](android-get-pb-paywalls#fetch-the-view-configuration)。 |
| **products** | 选填 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在页面上的显示时机。若传入 `null`,AdaptyUI 将自动获取所需产品。 |
| **eventListener** | 选填 | 提供一个 `AdaptyFlowEventListener` 以监听流程事件。建议继承 `AdaptyFlowDefaultEventListener` 以简化使用。详情请参阅[处理流程与付费墙事件](android-handling-events)。 |
| **insets** | 选填 | Insets 是流程四周的空白区域,用于防止可点击元素被系统栏遮挡。默认值为 `Unspecified`,即由 Adapty 自动调整 insets。请参阅[更改流程 insets](android-present-paywalls#change-flow-insets)。 |
| **customAssets** | 选填 | 传入一个 `AdaptyCustomAssets` 对象,以在运行时替换流程或付费墙中的图片和视频。详情请参阅[自定义资源](android-get-pb-paywalls#customize-assets)。 |
| **tagResolver** | 选填 | 使用 `AdaptyUiTagResolver` 解析流程文本中的自定义标签。该解析器接收标签参数并将其解析为对应的字符串。详情请参阅付费墙编辑工具中的自定义标签相关内容。 |
| **observerModeHandler** | Observer 模式下必填 | 您在上一步中实现的 `AdaptyUiObserverModeHandler`。 |
:::warning
不要忘记[将付费墙与购买交易关联](report-transactions-observer-mode-android)。否则,Adapty 将无法确定购买的来源流程。
:::
开始展示付费墙之前(点击展开)
1. 设置 Adapty 与 [Google Play](initial-android) 和 [App Store](initial_ios) 的初始集成。
2. 安装并配置 Adapty SDK。确保将 `observerMode` 参数设置为 `true`。请参阅我们针对 [Android](sdk-installation-android) 的框架专属说明。
3. 在 Adapty 看板中[创建产品](create-product)。
4. 在 Adapty 看板中[配置付费墙、为其分配产品](create-paywall),并使用付费墙编辑工具对其进行自定义。
5. 在 Adapty 看板中[创建版位并为其分配付费墙](create-placement)。
6. 在移动应用代码中[获取付费墙编辑工具创建的付费墙及其配置](android-get-pb-paywalls)。
1. 实现 `AdaptyUiObserverModeHandler`。
`onPurchaseInitiated` 事件将通知您用户已发起购买。您可以在此回调中触发自定义的购买流程:
```kotlin showLineNumbers
val observerModeHandler =
AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase ->
onStartPurchase()
yourBillingClient.makePurchase(
product,
onSuccess = { purchase ->
onFinishPurchase()
//handle success
},
onError = {
onFinishPurchase()
//handle error
},
onCancel = {
onFinishPurchase()
//handle cancel
}
)
}
```
```java showLineNumbers
AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> {
onStartPurchase.invoke();
yourBillingClient.makePurchase(
product,
purchase -> {
onFinishPurchase.invoke();
//handle success
},
error -> {
onFinishPurchase.invoke();
//handle error
},
() -> { //cancellation
onFinishPurchase.invoke();
//handle cancel
}
);
};
```
要在观察者模式下处理恢复操作,请重写 `getRestoreHandler()`。默认情况下它返回 `null`,此时会使用 Adapty 内置的 `Adapty.restorePurchases()` 流程。如需自定义恢复逻辑,请按以下方式实现:
```kotlin showLineNumbers
val observerModeHandler = object : AdaptyUiObserverModeHandler {
// onPurchaseInitiated implementation (see above)
override fun getRestoreHandler() =
AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore ->
onStartRestore()
yourBillingClient.restorePurchases(
onSuccess = { restoredPurchases ->
onFinishRestore()
//handle successful restore
},
onError = {
onFinishRestore()
//handle error
}
)
}
}
```
```java showLineNumbers
AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() {
// onPurchaseInitiated implementation (see above)
@Override
public RestoreHandler getRestoreHandler() {
return (onStartRestore, onFinishRestore) -> {
onStartRestore.invoke();
yourBillingClient.restorePurchases(
restoredPurchases -> {
onFinishRestore.invoke();
//handle successful restore
},
error -> {
onFinishRestore.invoke();
//handle error
}
);
};
}
};
```
请记得调用以下回调以通知 AdaptyUI 购买或恢复流程。这对于正确的付费墙行为(例如显示加载动画)是必要的:
| 回调函数 | 说明 |
| :----------------- |:---------------------------------------------------------------------------------------|
| onStartPurchase() | 调用此回调函数以通知 AdaptyUI 购买已开始。 |
| onFinishPurchase() | 调用此回调函数以通知 AdaptyUI 购买已完成。 |
| onStartRestore() | 可选。调用此回调函数以通知 AdaptyUI 恢复购买已开始。 |
| onFinishRestore() | 可选。调用此回调函数以通知 AdaptyUI 恢复购买已完成。 |
2. 要在设备屏幕上显示可视化付费墙,您必须先对其进行配置。
为此,请调用 `AdaptyUI.getPaywallView()` 方法或直接创建 `AdaptyPaywallView`:
```kotlin showLineNumbers
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
personalizedOfferResolver,
tagResolver,
timerResolver,
observerModeHandler,
)
```
```kotlin showLineNumbers
val paywallView =
AdaptyPaywallView(activity) // or retrieve it from xml
...
with(paywallView) {
showPaywall(
viewConfiguration,
products,
eventListener,
personalizedOfferResolver,
tagResolver,
timerResolver,
observerModeHandler,
)
}
```
```java showLineNumbers
AdaptyPaywallView paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
eventListener,
personalizedOfferResolver,
tagResolver,
timerResolver,
observerModeHandler
);
```
```java showLineNumbers
AdaptyPaywallView paywallView =
new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
paywallView.showPaywall(viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler);
```
```xml showLineNumbers
```
视图成功创建后,您可以将其添加到视图层级中并显示。
使用以下可组合函数:
```kotlin showLineNumbers
AdaptyPaywallScreen(
viewConfiguration,
products,
eventListener,
personalizedOfferResolver,
tagResolver,
timerResolver,
)
```
请求参数:
| 参数 | 是否必填 | 描述 |
|---------|--------|-----------|
| **Products** | 可选 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在屏幕上的展示时机。如果传入 `null`,AdaptyUI 将自动获取所需产品。 |
| **ViewConfiguration** | 必填 | 提供一个包含付费墙视觉详情的 `AdaptyViewConfiguration` 对象。使用 `Adapty.getViewConfiguration(paywall)` 方法加载该对象。详情请参阅[获取付费墙的视觉配置](#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder)。 |
| **EventListener** | 可选 | 提供一个 `AdaptyUiEventListener` 来监听付费墙事件。推荐继承 `AdaptyUiDefaultEventListener` 以简化使用。详情请参阅[处理付费墙事件](android-handling-events)。 |
| **PersonalizedOfferResolver** | 可选 | 如需标识个性化定价([了解更多](https://developer.android.com/google/play/billing/integrate#personalized-price)),请实现 `AdaptyUiPersonalizedOfferResolver`,并传入自定义逻辑:若产品价格为个性化定价则返回 true,否则返回 false。 |
| **TagResolver** | 可选 | 使用 `AdaptyUiTagResolver` 解析付费墙文本中的自定义标签。该解析器接收一个标签参数,并将其解析为对应的字符串。详情请参阅付费墙编辑工具中的自定义标签相关内容。 |
| **ObserverModeHandler** | Observer 模式下必填 | 即你在上一步中实现的 `AdaptyUiObserverModeHandler`。 |
| **variationId** | 必填 | 实验变体的字符串标识符。可通过 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 |
| **transaction** | 必填 | iOS,StoreKit 1:[`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。
iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。
Android:购买的字符串标识符(`purchase.getOrderId()`),其中 purchase 是 billing library [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。
|
开始呈现付费墙之前(点击展开)
1. 设置 Adapty 与 [Google Play](initial-android) 和 [App Store](initial_ios) 的初始集成。
2. 安装并配置 Adapty SDK。确保将 `observerMode` 参数设置为 `true`。请参阅我们针对 [Android](sdk-installation-android)、[React Native](sdk-installation-reactnative)、[Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) 和 [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk) 的框架专属说明。
3. 在 Adapty 看板中[创建产品](create-product)。
4. 在 Adapty 看板中[配置付费墙、为其分配产品](create-paywall),并使用付费墙编辑工具对其进行自定义。
5. 在 Adapty 看板中[创建版位并为其分配付费墙](create-placement)。
6. 在移动应用代码中[获取付费墙编辑工具创建的付费墙及其配置](android-get-pb-paywalls)。
1. 实现 `AdaptyUiObserverModeHandler`。`AdaptyUiObserverModeHandler` 的回调(`onPurchaseInitiated`)将在用户发起购买时通知您。您可以在此回调中触发自定义的购买流程:
```kotlin showLineNumbers
val observerModeHandler =
AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase ->
onStartPurchase()
yourBillingClient.makePurchase(
product,
onSuccess = { purchase ->
onFinishPurchase()
//handle success
},
onError = {
onFinishPurchase()
//handle error
},
onCancel = {
onFinishPurchase()
//handle cancel
}
)
}
```
```java showLineNumbers
AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> {
onStartPurchase.invoke();
yourBillingClient.makePurchase(
product,
purchase -> {
onFinishPurchase.invoke();
//handle success
},
error -> {
onFinishPurchase.invoke();
//handle error
},
() -> { //cancellation
onFinishPurchase.invoke();
//handle cancel
}
);
};
```
同时,请记得向 AdaptyUI 调用这些回调。这对于正确的付费墙行为(例如显示加载动画等)是必要的:
| Kotlin 中的回调 | Java 中的回调 | 描述 |
| :----------------- | :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------- |
| onStartPurchase() | onStartPurchase.invoke() | 应调用此回调以通知 AdaptyUI 购买已开始。 |
| onFinishPurchase() | onFinishPurchase.invoke() | 应调用此回调以通知 AdaptyUI 购买已成功完成、失败或已取消。 |
2. 要显示可视化付费墙,必须先对其进行初始化。为此,请调用 `AdaptyUI.getPaywallView()` 方法,或直接创建 `AdaptyPaywallView`:
```kotlin showLineNumbers
val paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
AdaptyPaywallInsets.of(topInset, bottomInset),
eventListener,
personalizedOfferResolver,
tagResolver,
observerModeHandler,
)
//======= OR =======
val paywallView =
AdaptyPaywallView(activity) // or retrieve it from xml
...
with(paywallView) {
setEventListener(eventListener)
setObserverModeHandler(observerModeHandler)
showPaywall(
viewConfiguration,
products,
AdaptyPaywallInsets.of(topInset, bottomInset),
personalizedOfferResolver,
tagResolver,
)
}
```
```java showLineNumbers
AdaptyPaywallView paywallView = AdaptyUI.getPaywallView(
activity,
viewConfiguration,
products,
AdaptyPaywallInsets.of(topInset, bottomInset),
eventListener,
personalizedOfferResolver,
tagResolver,
observerModeHandler
);
//======= OR =======
AdaptyPaywallView paywallView =
new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml
...
paywallView.setEventListener(eventListener);
paywallView.setObserverModeHandler(observerModeHandler);
paywallView.showPaywall(viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver);
```
```xml showLineNumbers
```
视图成功创建后,您可以将其添加到视图层级并显示。
请求参数:
| 参数 | 是否必填 | 描述 |
|---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Products** | 可选 | 提供一个 `AdaptyPaywallProduct` 数组,以优化产品在屏幕上的显示时机。如果传入 `null`,AdaptyUI 将自动获取所需产品。 |
| **ViewConfiguration** | 必填 | 提供一个包含付费墙视觉详情的 `AdaptyViewConfiguration` 对象。使用 `Adapty.getViewConfiguration(paywall)` 方法加载该对象。详情请参阅 [获取付费墙的视觉配置](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder)。 |
| **Insets** | 必填 | 定义一个 `AdaptyPaywallInsets` 对象,包含系统栏遮挡区域的信息,用于为内容创建垂直边距。如果状态栏和导航栏都不遮挡 `AdaptyPaywallView`,则传入 `AdaptyPaywallInsets.NONE`。在系统栏覆盖部分 UI 的全屏模式下,请按表格下方的说明获取 insets。 |
| **EventListener** | 可选 | 提供一个 `AdaptyUiEventListener` 以监听付费墙事件。建议继承 `AdaptyUiDefaultEventListener` 以简化使用。详情请参阅[处理付费墙事件](android-handling-events)。 |
| **PersonalizedOfferResolver** | 可选 | 如需标记个性化定价([了解更多](https://developer.android.com/google/play/billing/integrate#personalized-price)),请实现 `AdaptyUiPersonalizedOfferResolver`,并传入自定义逻辑:若某产品的价格为个性化定价则返回 true,否则返回 false。 |
| **TagResolver** | 可选 | 使用 `AdaptyUiTagResolver` 解析付费墙文本中的自定义标签。该解析器接收标签参数并将其解析为对应字符串。详情请参阅付费墙编辑工具中的自定义标签相关文档。 |
| **ObserverModeHandler** | Observer 模式下必填 | 您在上一步骤中实现的 `AdaptyUiObserverModeHandler`。 |
| **variationId** | 必填 | 实验变体的字符串标识符。可通过 [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) 对象的 `variationId` 属性获取。 |
| **transaction** | 必填 | iOS,StoreKit 1:[`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction) 对象。
iOS,StoreKit 2:[Transaction](https://developer.apple.com/documentation/storekit/transaction) 对象。
Android:购买记录的字符串标识符(`purchase.getOrderId()`),其中 purchase 是计费库 [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) 类的实例。
|
对于全屏模式下系统状态栏遮挡部分界面的情况,请通过以下方式获取插入值:
```kotlin showLineNumbers
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
//create extension function
fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) {
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
ViewCompat.setOnApplyWindowInsetsListener(this, null)
action(systemBarInsets)
insets
}
}
//and then use it with the view
paywallView.onReceiveSystemBarsInsets { insets ->
val paywallInsets = AdaptyPaywallInsets.of(insets.top, insets.bottom)
paywallView.setEventListener(eventListener)
paywallView.setObserverModeHandler(observerModeHandler)
paywallView.showPaywall(viewConfig, products, paywallInsets, personalizedOfferResolver, tagResolver)
}
```
```java showLineNumbers
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
...
ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> {
Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars());
ViewCompat.setOnApplyWindowInsetsListener(paywallView, null);
AdaptyPaywallInsets paywallInsets =
AdaptyPaywallInsets.of(systemBarInsets.top, systemBarInsets.bottom);
paywallView.setEventListener(eventListener);
paywallView.setObserverModeHandler(observerModeHandler);
paywallView.showPaywall(viewConfiguration, products, paywallInsets, personalizedOfferResolver, tagResolver);
return insets;
});
```
返回值:
| 对象 | 说明 |
| :------------------ | :------------------------------------------------- |
| `AdaptyPaywallView` | 代表所请求付费墙界面的对象。 |
:::warning
不要忘记[将付费墙关联到购买交易](report-transactions-observer-mode-android)。否则,Adapty 将无法确定购买来源的付费墙。
:::
---
# File: android-troubleshoot-purchases
---
---
title: "排查 Android SDK 中的购买问题"
description: "排查 Android SDK 中的购买问题"
---
本指南帮助您解决在 Android SDK 中手动实现购买时遇到的常见问题。
## makePurchase 调用成功,但用户画像未更新 \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\}
**问题**:`makePurchase` 方法成功完成,但用户的用户画像和订阅状态未在 Adapty 中更新。
**原因**:这通常表明 Google Play Store 设置不完整或存在配置问题。
**解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。
## makePurchase 被调用两次 \{#makepurchase-is-invoked-twice\}
**问题**:同一次购买中 `makePurchase` 方法被多次调用。
**原因**:这通常发生在由于 UI 状态管理问题或用户快速操作导致购买流程被多次触发时。
**解决方案**:请确保您已完成所有 [Google Play 设置步骤](initial-android)。
## 观察者模式下出现 AdaptyError.cantMakePayments \{#adaptyerrorcantmakepayments-in-observer-mode\}
**问题**:在观察者模式下使用 `makePurchase` 时收到 `AdaptyError.cantMakePayments`。
**原因**:在观察者模式下,您应在自己这侧处理购买,而不是使用 Adapty 的 `makePurchase` 方法。
**解决方案**:如果您使用 `makePurchase` 进行购买,请关闭观察者模式。您需要二选一:要么使用 `makePurchase`,要么在观察者模式下自行处理购买。详情请参见[实现观察者模式](implement-observer-mode-android)。
## Adapty 错误:(code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\}
**问题**:您收到来自 Google Play Store 的计费不可用错误。
**原因**:此错误与 Adapty 无关,它是 Google Play 计费库的错误,表示设备上计费功能不可用。
**解决方案**:此错误与 Adapty 无关。您可以在 Play Store 文档中查看并了解更多信息:[处理 BillingResult 响应代码](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers。
## 找不到 makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\}
**问题**:遇到找不到 `makePurchasesCompletionHandlers` 的问题。
**原因**:这通常与沙盒测试问题有关。
**解决方案**:创建一个新的沙盒用户并重试。这通常可以解决与沙盒相关的购买完成处理程序问题。
## 其他问题 \{#other-issues\}
**问题**:您遇到了上述未涵盖的其他购买相关问题。
**解决方案**:如有需要,请按照[迁移指南](android-sdk-migration-guides)将 SDK 迁移至最新版本。许多问题已在较新的 SDK 版本中得到修复。
---
# File: android-identifying-users
---
---
title: "在 Android SDK 中识别用户"
description: "在 Adapty 中识别用户,提升个性化订阅体验(Android)。"
---
Adapty 会为每位用户创建一个内部 Profile ID。但如果你有自己的身份验证系统,建议设置自定义的 Customer User ID。你可以在[用户画像](profiles-crm)部分通过 Customer User ID 查找用户,也可以在[服务端 API](getting-started-with-server-side-api) 中使用它——该 ID 会同步发送至所有集成。
### 在配置时设置 Customer User ID \{#setting-customer-user-id-on-configuration\}
如果您在配置时已有用户 ID,只需将其作为 `customerUserId` 参数传递给 `.activate()` 方法:
```kotlin showLineNumbers
Adapty.activate(applicationContext, "PUBLIC_SDK_KEY", customerUserId = "YOUR_USER_ID")
```
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
### 在配置完成后设置客户用户 ID \{#setting-customer-user-id-after-configuration\}
如果在 SDK 配置时没有用户 ID,可以在之后任意时刻通过 `.identify()` 方法进行设置。最常见的使用场景是在注册或登录之后,当用户从匿名状态切换为已认证状态时。
```kotlin showLineNumbers
Adapty.identify("YOUR_USER_ID") { error ->
if (error == null) {
// successful identify
}
}
```
```java showLineNumbers
Adapty.identify("YOUR_USER_ID", error -> {
if (error == null) {
// successful identify
}
});
```
请求参数:
- **Customer User ID**(必填):字符串类型的用户标识符。
:::warning
重复提交重要用户数据
在某些情况下,例如用户重新登录账户时,Adapty 服务器可能已经存有该用户的信息。此时,Adapty SDK 会自动切换到新用户。如果你曾向匿名用户提交过任何数据(例如自定义属性或来自第三方网络的归因数据),需要为已识别的用户重新提交这些数据。
另外请注意,识别用户后应重新请求所有付费墙和产品,因为新用户的数据可能有所不同。
:::
### 退出登录与重新登录 \{#logging-out-and-logging-in\}
你可以随时调用 `.logout()` 方法退出当前用户:
```kotlin showLineNumbers
Adapty.logout { error ->
if (error == null) {
// successful logout
}
}
```
```java showLineNumbers
Adapty.logout(error -> {
if (error == null) {
// successful logout
}
});
```
之后可以调用 `.identify()` 方法重新登录用户。
### 跨设备用户识别 \{#detect-users-across-devices\}
当 SDK 激活时,它会自动从 StoreKit (iOS) 或 Google Play Billing (Android) 读取用户现有的权益,并将其同步到 Adapty 后端。活跃订阅无需应用调用 `restorePurchases`,即可出现在 Adapty 用户画像中。
**不会**自动发生的是:识别新设备上的用户画像与原设备上的用户画像属于同一用户。Adapty 通过 Customer User ID 匹配用户画像,因此身份连续性取决于您使用什么作为 CUID。
**Adapty 跨设备可检测的内容**
| 您的配置 | Adapty 检测到的内容 | 您需要做什么 |
| --- | --- | --- |
| Customer User ID = `device_id`(无应用登录) | 新设备获得不同的 CUID,因此拥有不同的用户画像。订阅通过 **Access level updated** 事件同步到新用户画像,但 `subscription_started` 不会触发——新用户画像被视为原始购买的继承者。基于 `subscription_started` 的分析将少计回归用户。 | 使用稳定的账户 ID 作为 Customer User ID,以便回归用户能跨设备匹配到现有用户画像。 |
| Customer User ID = 稳定账户 ID(每台设备均需登录) | SDK 在 `activate()` 时自动同步订阅,`identify()` 通过 CUID 匹配现有用户画像。 | 无需额外配置——身份和订阅均可自动解析。 |
| Apple Family Sharing 继承者 | 家庭成员仅通过 **Access level updated** 事件接收订阅——`subscription_started` 不会触发。 | 监听 **Access level updated**。完整的事件矩阵请参见 [Apple Family Sharing](apple-family-sharing)。 |
| 同一 Apple/Google 账户,不同应用内用户 | 最先记录购买的用户画像成为父级。后续用户画像通过继承链查看订阅,并触发一次 **Access level updated** 事件。 | 要求用户登录,然后选择适合您业务模型的[共享模式](sharing-paid-access-between-user-accounts)。 |
**在新设备上恢复购买**
在付费墙上提供一个用户可主动触发的"恢复购买"按钮。Apple App Review(指南 3.1.1)要求提供此按钮,且当自动同步遗漏边缘情况时,它也可作为备用方案。该按钮应调用 SDK 中的 `restorePurchases`。
正常使用时,首次启动时无需通过代码调用 `restorePurchases`——SDK 已在 `activate()` 时执行了等效操作。仅在需要强制刷新收据检查时才使用代码调用,例如在 `activate()` 完成后调试访问等级缺失问题时。
---
# File: android-setting-user-attributes
---
---
title: "在 Android SDK 中设置用户属性"
description: "了解如何在 Adapty 中设置用户属性,以实现更好的目标受众细分。"
---
您可以为应用的用户设置可选属性,例如电子邮件、电话号码等。您可以使用这些属性来创建用户[市场细分](segments),或直接在 CRM 中查看。
### 设置用户属性 \{#setting-user-attributes\}
要设置用户属性,请调用 `.updateProfile()` 方法:
```kotlin showLineNumbers
val builder = AdaptyProfileParameters.Builder()
.withEmail("email@email.com")
.withPhoneNumber("+18888888888")
.withFirstName("John")
.withLastName("Appleseed")
.withGender(AdaptyProfile.Gender.OTHER)
.withBirthday(AdaptyProfile.Date(1970, 1, 3))
Adapty.updateProfile(builder.build()) { error ->
if (error != null) {
// handle the error
}
}
```
```java showLineNumbers
AdaptyProfileParameters.Builder builder = new AdaptyProfileParameters.Builder()
.withEmail("email@email.com")
.withPhoneNumber("+18888888888")
.withFirstName("John")
.withLastName("Appleseed")
.withGender(AdaptyProfile.Gender.OTHER)
.withBirthday(new AdaptyProfile.Date(1970, 1, 3));
Adapty.updateProfile(builder.build(), error -> {
if (error != null) {
// handle the error
}
});
```
请注意,之前通过 `updateProfile` 方法设置的属性不会被重置。
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
### 允许的键列表 \{#the-allowed-keys-list\}
`AdaptyProfileParameters.Builder` 的允许键 `` 及其对应值 `` 如下所示:
| 键 | 值 |
|---|-----|
| email
phoneNumber
firstName
lastName
| String |
| gender | 枚举,允许的值为:`female`、`male`、`other` |
| birthday | Date |
### 自定义用户属性 \{#custom-user-attributes\}
您可以设置自定义属性,这些属性通常与您的应用使用情况相关。例如,对于健身应用,可以是每周锻炼次数;对于语言学习应用,可以是用户的知识水平等。您可以在市场细分中使用这些属性来创建针对性的付费墙和优惠,也可以在数据分析中用于找出哪些产品指标对营收影响最大。
```kotlin showLineNumbers
builder.withCustomAttribute("key1", "value1")
```
```java showLineNumbers
builder.withCustomAttribute("key1", "value1");
```
要删除已有的键,请使用 `.withRemoved(customAttributeForKey:)` 方法:
```kotlin showLineNumbers
builder.withRemovedCustomAttribute("key2")
```
```java showLineNumbers
builder.withRemovedCustomAttribute("key2");
```
有时您可能需要查看已设置的自定义属性。为此,请使用 `AdaptyProfile` 对象的 `customAttributes` 字段。
:::warning
请注意,`customAttributes` 的值可能不是最新的,因为用户属性可以随时从不同设备发送,服务器上的属性可能在上次同步后已发生变化。
:::
### 限制 \{#limits\}
- 每个用户最多 30 个自定义属性
- 键名最长 30 个字符,键名可包含字母数字字符及以下任意字符:`_` `-` `.`
- 值可以是字符串或浮点数,最多 50 个字符。
---
# File: android-listen-subscription-changes
---
---
title: "在 Android SDK 中检查订阅状态"
description: "在 Adapty 中跟踪和管理用户订阅状态,提升 Android 应用的客户留存率。"
---
借助 Adapty,跟踪订阅状态变得轻而易举。您无需在代码中手动插入产品 ID,只需检查用户是否拥有有效的[访问等级](access-level),即可轻松确认其订阅状态。
在开始检查订阅状态之前,请先配置[实时开发者通知 (RTDN)](enable-real-time-developer-notifications-rtdn)。
## 访问等级与 AdaptyProfile 对象 \{#access-level-and-the-adaptyprofile-object\}
访问等级是 [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) 对象的属性。我们建议在应用启动时(例如[识别用户](android-identifying-users#setting-customer-user-id-on-configuration)时)获取用户画像,并在发生变更时及时更新。这样,您就可以直接使用用户画像对象,而无需反复请求。
若要在用户画像更新时收到通知,请按照下方[监听用户画像更新(包括访问等级变更)](android-listen-subscription-changes)部分的说明,监听用户画像变更事件。
:::tip
想看看 Adapty SDK 在移动应用中的实际集成示例吗?欢迎查看我们的[示例应用](sample-apps),其中演示了完整的集成流程,包括展示付费墙、完成购买以及其他基本功能。
:::
## 从服务器获取访问等级 \{#retrieving-the-access-level-from-the-server\}
使用 `.getProfile()` 方法从服务器获取访问等级:
```kotlin showLineNumbers
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
// check the access
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
// check the access
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
响应参数:
| 参数 | 说明 |
| --------- | ------------------------------------------------------------ |
| Profile | [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) 对象。通常情况下,您只需检查用户画像的访问等级状态,即可判断用户是否拥有应用的高级访问权限。
`.getProfile` 方法始终尝试查询 API,因此能提供最新的结果。如果由于某些原因(例如无网络连接)导致 Adapty SDK 无法从服务器获取信息,则会返回缓存中的数据。此外,Adapty SDK 会定期更新 `AdaptyProfile` 缓存,以尽可能保持信息的实时性。
|
`.getProfile()` 方法会返回用户画像,您可以从中获取访问等级状态。每个应用可以拥有多个访问等级。例如,如果您有一个新闻应用并独立销售不同主题的订阅,可以创建"sports"和"science"等访问等级。但在大多数情况下,您只需要一个访问等级,此时直接使用默认的"premium"访问等级即可。
以下是检查默认"premium"访问等级的示例:
```kotlin showLineNumbers
Adapty.getProfile { result ->
when (result) {
is AdaptyResult.Success -> {
val profile = result.value
if (profile.accessLevels["premium"]?.isActive == true) {
// grant access to premium features
}
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
```java showLineNumbers
Adapty.getProfile(result -> {
if (result instanceof AdaptyResult.Success) {
AdaptyProfile profile = ((AdaptyResult.Success) result).getValue();
AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("premium");
if (premium != null && premium.isActive()) {
// grant access to premium features
}
} else if (result instanceof AdaptyResult.Error) {
AdaptyError error = ((AdaptyResult.Error) result).getError();
// handle the error
}
});
```
### 监听订阅状态更新 \{#listening-for-subscription-status-updates\}
每当用户的订阅发生变化,Adapty 都会触发一个事件。
要接收来自 Adapty 的消息,您需要进行一些额外配置:
```kotlin showLineNumbers
Adapty.setOnProfileUpdatedListener { profile ->
// handle any changes to subscription state
}
```
```java showLineNumbers t
Adapty.setOnProfileUpdatedListener(profile -> {
// handle any changes to subscription state
});
```
Adapty 也会在应用启动时触发一个事件,此时将传递缓存的订阅状态。
### 订阅状态缓存 \{#subscription-status-cache\}
Adapty SDK 中实现的缓存会存储用户画像的订阅状态。这意味着即使服务器不可用,也可以访问缓存数据以获取用户画像的订阅状态信息。
但请注意,目前无法直接从缓存请求数据。SDK 会每分钟定期查询服务器,以检查与用户画像相关的任何更新或变更。如有任何修改(例如新的交易或其他更新),这些变更将被同步到缓存数据中,以保持与服务器的一致性。
---
# File: kids-mode-android
---
---
title: "Android SDK 中的儿童模式"
description: "轻松启用儿童模式以遵守 Google 政策。Android SDK 中不会收集 GAID 或广告数据。"
---
如果您的 Android 应用面向儿童,则必须遵守 [Google](https://support.google.com/googleplay/android-developer/answer/9893335) 的相关政策。如果您正在使用 Adapty SDK,只需几个简单步骤即可完成配置,以满足这些政策要求并通过应用商店审核。
## 需要配置哪些内容?\{#whats-required\}
你需要配置 Adapty SDK,禁止收集以下信息:
- [Android 广告 ID(AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248)
- [IP 地址](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf)
此外,我们建议谨慎使用 customer user ID。格式为 `` 的用户 ID 与使用电子邮件一样,肯定会被视为收集个人数据。对于儿童模式,最佳做法是使用随机或匿名标识符(例如哈希 ID 或设备生成的 UUID),以确保合规性。
## 启用儿童模式 \{#enabling-kids-mode\}
### 在 Adapty 看板中进行更新 \{#updates-in-the-adapty-dashboard\}
在 Adapty 看板中,您需要禁用 IP 地址收集。请前往 [App settings](https://app.adapty.io/settings/general),并在 **Collect users' IP address** 下点击 **Disable IP address collection**。
### 在移动应用代码中进行更新 \{#updates-in-your-mobile-app-code\}
为遵守相关政策,您需要在初始化 Adapty SDK 时禁用 Android 广告 ID(AAID/GAID)和 IP 地址的收集:
**Kotlin:**
```kotlin showLineNumbers
override fun onCreate() {
super.onCreate()
Adapty.activate(
applicationContext,
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
// highlight-start
.withAdIdCollectionDisabled(true) // set to `true`
.withIpAddressCollectionDisabled(true) // set to `true`
// highlight-end
.build()
)
}
```
**Java:**
```java showLineNumbers
@Override
public void onCreate() {
super.onCreate();
Adapty.activate(
applicationContext,
new AdaptyConfig.Builder("PUBLIC_SDK_KEY")
// highlight-start
.withAdIdCollectionDisabled(true) // set to `true`
.withIpAddressCollectionDisabled(true) // set to `true`
// highlight-end
.build()
);
}
```
### Android 清单更新 \{#updates-in-your-android-manifest\}
:::note
如果你的应用**仅**面向儿童用户,且编译目标为 Android 13(API 33)或更高版本,Google Play 要求你不得请求 `AD_ID` 权限。应用中的其他 SDK(如分析、归因或广告 SDK)可能通过清单合并的方式添加该权限。设置 `withAdIdCollectionDisabled(true)` 可阻止 Adapty 收集该 ID,但无法移除其他 SDK 声明的权限。
:::
要移除该权限,请在 `app/src/main/AndroidManifest.xml` 的 `` 元素内添加以下内容。`` 元素必须声明 `xmlns:tools="http://schemas.android.com/tools"`。
```xml showLineNumbers title="AndroidManifest.xml"
```
---
# File: android-get-onboardings
---
---
title: "在 Android SDK 中获取用户引导"
description: "了解如何在 Adapty for Android 中获取用户引导。"
---
:::tip
**从 SDK v4 开始**,你可以构建[流程](android-get-pb-paywalls),作为用户引导更强大的替代方案。与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——带来更流畅的动画、一致的 Android 视觉风格、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](android-get-pb-paywalls)和[展示流程与付费墙](android-present-paywalls)以开始使用。
:::
在 Adapty 看板中使用编辑工具[设计好用户引导的视觉部分](design-onboarding)后,您可以在 Android 应用中展示它。该流程的第一步是获取与版位关联的用户引导及其视图配置,具体如下所述。
开始之前,请确保:
1. 您已安装 [Adapty Android SDK](sdk-installation-android) 3.8.0 或更高版本。
2. 您已[创建用户引导](create-onboarding)。
3. 您已将用户引导添加到[版位](placements)。
## 获取用户引导 \{#fetch-onboarding\}
当您使用我们的无代码编辑工具创建[用户引导](onboardings)时,它会以容器的形式存储,其中包含应用需要获取和展示的配置。该容器管理整个体验——显示哪些内容、如何呈现,以及如何处理用户交互(如问卷回答或表单输入)。容器还会自动追踪分析事件,因此您无需单独实现视图追踪。
为获得最佳性能,请尽早获取用户引导配置,以便图片在展示给用户之前有足够的时间下载。
要获取用户引导,请使用 `getOnboarding` 方法:
```kotlin showLineNumbers
Adapty.getOnboarding("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val onboarding = result.value
// the requested onboarding
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
```
参数说明:
| 参数 | 是否必填 | 描述 |
|---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 |
| **locale** | 可选
默认值:`en`
| 用户引导本地化的标识符。该参数应为由连字符(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。
示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。
有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。
|
| **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获得最新数据。
但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能无法获取最新数据,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。
请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。
Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存和备用用户引导。我们还使用 CDN 来更快地获取用户引导,并在 CDN 不可访问时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接不佳的情况下也能保证可靠性。
|
| **loadTimeout** | 默认值:5 秒 | 此值限制该方法的超时时间。如果超时,将返回缓存数据或本地备用数据。
请注意,在极少数情况下,该方法的实际超时时间可能略晚于 `loadTimeout` 中指定的时间,因为该操作底层可能包含多个不同请求。
对于 Android:您可以使用扩展函数创建 `TimeInterval`(如 `5.seconds`,其中 `.seconds` 来自 `import com.adapty.utils.seconds`),或使用 `TimeInterval.seconds(5)`。若不设置限制,请使用 `TimeInterval.INFINITE`。
|
响应参数:
| 参数 | 描述 |
|:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| Onboarding | 一个 [`AdaptyOnboarding`](https://android.adapty.io/adapty/com.adapty.models/-adapty-onboarding/) 对象,包含:用户引导标识符和配置、远程配置以及其他若干属性。 |
## 使用默认目标受众用户引导加速获取 \{#speed-up-onboarding-fetching-with-default-audience-onboarding\}
通常情况下,用户引导的获取几乎是即时的,因此您无需担心加速此过程。但如果您有大量目标受众和用户引导,且用户的网络连接较弱,则获取用户引导可能需要比预期更长的时间。在这种情况下,您可能希望展示默认用户引导,以确保流畅的用户体验,而不是不显示任何用户引导。
为解决此问题,您可以使用 `getOnboardingForDefaultAudience` 方法,该方法为**所有用户**目标受众获取指定版位的用户引导。但请务必理解,推荐的方式是通过 `getOnboarding` 方法获取用户引导,详见上方[获取用户引导](#fetch-onboarding)部分。
:::warning
请考虑使用 `getOnboarding` 而非 `getOnboardingForDefaultAudience`,因为后者存在以下重要限制:
- **兼容性问题**:在支持多个应用版本时可能产生问题,需要兼容向后设计,否则旧版本可能显示异常。
- **无个性化**:仅显示"所有用户"目标受众的内容,无法基于国家、归因或自定义属性进行定向。
如果对您的使用场景而言,更快的获取速度优先于这些缺点,请按如下所示使用 `getOnboardingForDefaultAudience`。否则,请按[上方](#fetch-onboarding)所述使用 `getOnboarding`。
:::
```kotlin
Adapty.getOnboardingForDefaultAudience("YOUR_PLACEMENT_ID") { result ->
when (result) {
is AdaptyResult.Success -> {
val onboarding = result.value
// Handle successful onboarding retrieval
}
is AdaptyResult.Error -> {
val error = result.error
// Handle error case
}
}
}
```
参数说明:
| 参数 | 是否必填 | 描述 |
|---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **placementId** | 必填 | 所需[版位](placements)的标识符。这是您在 Adapty 看板中创建版位时指定的值。 |
| **locale** | 可选
默认值:`en`
| 用户引导本地化的标识符。该参数应为由连字符(**-**)分隔的一个或两个子标签组成的语言代码。第一个子标签表示语言,第二个子标签表示地区。
示例:`en` 表示英语,`pt-br` 表示巴西葡萄牙语。
有关语言区域代码及推荐使用方式的更多信息,请参阅[本地化与语言区域代码](localizations-and-locale-codes)。
|
| **fetchPolicy** | 默认值:`.reloadRevalidatingCacheData` | 默认情况下,SDK 会尝试从服务器加载数据,若加载失败则返回缓存数据。我们推荐此选项,因为它能确保用户始终获得最新数据。
但如果您认为用户的网络连接不稳定,可以考虑使用 `.returnCacheDataElseLoad`,在缓存数据存在时直接返回缓存。在这种情况下,用户可能无法获取最新数据,但无论网络状况如何,加载速度都会更快。缓存会定期更新,因此在会话期间使用缓存以避免网络请求是安全的。
请注意,缓存在应用重启后仍会保留,只有在重新安装应用或手动清理时才会被清除。
Adapty SDK 在本地以两层方式存储用户引导:上述定期更新的缓存和备用用户引导。我们还使用 CDN 来更快地获取用户引导,并在 CDN 不可访问时使用独立的备用服务器。该系统旨在确保您始终获得最新版本的用户引导,同时在网络连接不佳的情况下也能保证可靠性。
|
---
# File: android-present-onboardings
---
---
title: "在 Android SDK 中展示用户引导"
description: "了解如何在 Android 上展示用户引导,以有效提升用户参与度。"
---
:::tip
**从 SDK v4 开始**,你可以构建[流程](android-get-pb-paywalls),作为用户引导更强大的替代方案。与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——带来更流畅的动画、一致的 Android 视觉体验、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](android-get-pb-paywalls)和[展示流程与付费墙](android-present-paywalls)以开始使用。
:::
在开始之前,请确保:
1. 您已安装 [Adapty Android SDK](sdk-installation-android) 3.8.0 或更高版本。
2. 您已[创建用户引导](create-onboarding)。
3. 您已将用户引导添加到[版位](placements)。
如果您已使用 Onboarding Builder 自定义了用户引导,则无需在移动应用代码中手动处理其渲染逻辑来向用户展示它。此类用户引导已包含展示内容和展示方式的完整配置。
要在设备屏幕上显示可视化的用户引导,首先需要进行配置。调用 `AdaptyUI.getOnboardingView()` 方法,或直接创建 `OnboardingView`:
```kotlin
val onboardingView = AdaptyUI.getOnboardingView(
activity = this,
viewConfig = onboardingConfig,
eventListener = eventListener
)
```
```kotlin
val onboardingView = AdaptyOnboardingView(activity)
onboardingView.show(
viewConfig = onboardingConfig,
delegate = eventListener
)
```
```java
AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView(
activity,
onboardingConfig,
eventListener
);
```
```java
AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity);
onboardingView.show(onboardingConfig, eventListener);
```
```xml
```
视图成功创建后,您可以将其添加到视图层级中并在设备屏幕上显示。
请求参数:
| 参数 | 是否必填 | 描述 |
| :-------- | :------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **viewConfig** | 必填 | 从 `AdaptyUI.getOnboardingConfiguration()` 获取的用户引导配置 |
| **eventListener** | 必填 | `AdaptyOnboardingEventListener` 的实现,用于处理用户引导事件。详情请参考[处理用户引导事件](android-handle-onboarding-events)。 |
## 更改加载指示器颜色 \{#change-loading-indicator-color\}
您可以通过以下方式覆盖加载指示器的默认颜色:
```xml
```
## 在启动页和用户引导之间添加流畅过渡 \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\}
默认情况下,在启动页和用户引导之间,你会看到加载画面,直到用户引导完全加载完毕。但如果你想让过渡更流畅,可以自定义过渡效果——延长启动页的显示时间,或显示其他内容。
为此,在 `res/layout` 目录下创建 `adapty_onboarding_placeholder_view.xml`,并在其中定义一个占位视图(即用户引导加载期间显示的内容)。
如果您定义了版位,用户引导将在后台加载,并在准备就绪后自动显示。
## 禁用安全区域内边距 \{#disable-safe-area-paddings\}
默认情况下,用户引导视图会自动应用安全区域内边距,以避免与状态栏、导航栏等系统 UI 元素重叠。如果你希望禁用此行为并完全控制布局,可以将 `safeAreaPaddings` 参数设置为 `false`。
```kotlin
val onboardingView = AdaptyUI.getOnboardingView(
activity = this,
viewConfig = onboardingConfig,
eventListener = eventListener,
safeAreaPaddings = false
)
```
```kotlin
val onboardingView = AdaptyOnboardingView(activity)
onboardingView.show(
viewConfig = onboardingConfig,
delegate = eventListener,
safeAreaPaddings = false
)
```
```java
AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView(
activity,
onboardingConfig,
eventListener,
false
);
```
```java
AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity);
onboardingView.show(onboardingConfig, eventListener, false);
```
你也可以通过在应用中添加布尔资源来全局控制此行为:
```xml
false
```
当 `safeAreaPaddings` 设置为 `false` 时,用户引导将扩展至全屏,不会进行任何自动内边距调整,从而让你完全掌控布局,使用户引导内容得以占用整个屏幕空间。
## 自定义用户引导中链接的打开方式 \{#customize-how-links-open-in-onboardings\}
:::important
自定义用户引导中链接的打开方式需要 Adapty SDK v3.15.1 及以上版本。
:::
默认情况下,用户引导中的链接会在应用内浏览器中打开。这种方式能为用户提供无缝体验,让用户无需切换应用即可在应用内查看网页。
如果你希望改为在外部浏览器中打开链接,可以将 `externalUrlsPresentation` 参数设置为 `AdaptyWebPresentation.ExternalBrowser` 来自定义此行为:
```kotlin
val onboardingConfig = AdaptyUI.getOnboardingConfiguration(
onboarding = onboarding,
externalUrlsPresentation = AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser
)
```
```java
AdaptyOnboardingConfiguration onboardingConfig = AdaptyUI.getOnboardingConfiguration(
onboarding,
AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser
);
```
---
# File: android-handle-onboarding-events
---
---
title: "在 Android SDK 中处理用户引导事件"
description: "在 Android 中使用 Adapty 处理用户引导相关事件。"
---
:::tip
**从 SDK v4 起**,你可以使用[流程](android-get-pb-paywalls)作为用户引导的更强大替代方案。与在 WebView 中运行的用户引导不同,流程直接在设备上原生渲染——动画更流畅、外观风格与 Android 保持一致、加载速度更快,且无需依赖 WebView 运行时。请参阅[获取流程与付费墙](android-get-pb-paywalls)和[展示流程与付费墙](android-present-paywalls)开始使用。
:::
开始之前,请确保:
1. 您已安装 [Adapty Android SDK](sdk-installation-android) 3.8.0 或更高版本。
2. 您已[创建用户引导](create-onboarding)。
3. 您已将用户引导添加到[版位](placements)。
通过编辑工具配置的用户引导会生成各类事件,您的应用可以对这些事件作出响应。请参阅下方说明,了解如何处理这些事件。
如需在 Android 应用中控制或监听用户引导屏幕上发生的操作,请实现 `AdaptyOnboardingEventListener` 接口。
## 自定义操作 \{#custom-actions\}
在编辑工具中,你可以为按钮添加**自定义**操作并为其分配一个 ID。然后,你可以在代码中使用该 ID,并将其作为自定义操作进行处理。
例如,当用户点击自定义按钮(如 **Login** 或 **Allow notifications**)时,委托方法 `onCustomAction` 将被触发,并携带来自编辑工具的操作 ID。你可以自定义 ID,例如 "allowNotifications"。
```kotlin showLineNumbers
class YourActivity : AppCompatActivity() {
private val eventListener = object : AdaptyOnboardingEventListener {
override fun onCustomAction(action: AdaptyOnboardingCustomAction, context: Context) {
when (action.actionId) {
"allowNotifications" -> {
// Request notification permissions
}
}
}
override fun onError(error: AdaptyOnboardingError, context: Context) {
// Handle errors
}
// ... other required delegate methods
}
}
```
事件示例(点击展开)
```json
{
"actionId": "allowNotifications",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 0,
"screensTotal": 3
}
}
```
## 关闭用户引导 \{#closing-onboarding\}
当用户点击分配了 **Close** 操作的按钮时,用户引导即视为已关闭。你需要处理用户关闭用户引导后的逻辑。例如:
:::important
你需要处理用户关闭用户引导后的逻辑,例如停止显示用户引导界面本身。
:::
示例:
```kotlin
override fun onCloseAction(action: AdaptyOnboardingCloseAction, context: Context) {
// Dismiss the onboarding screen
(context as? Activity)?.onBackPressed()
}
```
事件示例(点击展开)
```json
{
"action_id": "close_button",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "final_screen",
"screen_index": 3,
"total_screens": 4
}
}
```
## 打开付费墙 \{#opening-a-paywall\}
:::tip
如果你想在用户引导内部打开付费墙,请处理此事件。如果你想在付费墙关闭后再打开另一个付费墙,有一种更直接的方式——处理 [`AdaptyOnboardingCloseAction`](#closing-onboarding) 并直接打开付费墙,无需依赖事件数据。
:::
在用户引导中使用付费墙最流畅的方式,是将 action ID 设置为等同于付费墙的版位 ID。这样,在收到 `AdaptyOnboardingOpenPaywallAction` 事件后,你可以直接用版位 ID 获取并打开对应的付费墙:
```kotlin
override fun onOpenPaywallAction(action: AdaptyOnboardingOpenPaywallAction, context: Context) {
// Get the paywall using the placement ID from the action
Adapty.getPaywall(placementId = action.actionId) { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// Get the paywall configuration
AdaptyUI.getViewConfiguration(paywall) { result ->
when(result) {
is AdaptyResult.Success -> {
val paywallConfig = result.value
// Create and present the paywall
val paywallView = AdaptyUI.getPaywallView(
activity = this,
viewConfig = paywallConfig,
products,
eventListener = paywallEventListener
)
// Add the paywall view to your layout
binding.container.addView(paywallView)
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
is AdaptyResult.Error -> {
val error = result.error
// handle the error
}
}
}
}
```
事件示例(点击展开)
```json
{
"action_id": "premium_offer_1",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "pricing_screen",
"screen_index": 2,
"total_screens": 4
}
}
```
## 完成用户引导加载 \{#finishing-loading-onboarding\}
当用户引导完成加载时,将调用此方法:
```kotlin
override fun onFinishLoading(action: AdaptyOnboardingLoadedAction, context: Context) {
// Handle loading completion
}
```
事件示例(点击展开)
```json
{
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "welcome_screen",
"screen_index": 0,
"total_screens": 4
}
}
```
## 导航事件 \{#navigation-events\}
`onAnalyticsEvent` 方法会在用户引导流程中发生各类分析事件时被调用。
`event` 对象可以是以下类型之一:
|类型 | 描述 |
|------------|-------------|
| `OnboardingStarted` | 用户引导加载完成时触发 |
| `ScreenPresented` | 任意屏幕显示时触发 |
| `ScreenCompleted` | 屏幕完成时触发。包含可选的 `elementId`(已完成元素的标识符)和可选的 `reply`(用户的响应)。用户执行任意操作退出屏幕时触发。 |
| `SecondScreenPresented` | 第二个屏幕显示时触发 |
| `UserEmailCollected` | 通过输入框收集到用户邮箱时触发 |
| `OnboardingCompleted` | 用户到达 ID 为 `final` 的屏幕时触发。如需使用此事件,请将最后一个屏幕的 ID 设置为 `final`。 |
| `Unknown` | 任何无法识别的事件类型。包含 `name`(未知事件的名称)和 `meta`(附加元数据) |
每个事件都包含 `meta` 信息,内容如下:
| 字段 | 描述 |
|------------|-------------|
| `onboardingId` | 用户引导流程的唯一标识符 |
| `screenClientId` | 当前屏幕的标识符 |
| `screenIndex` | 当前屏幕在流程中的位置 |
| `totalScreens` | 流程中的屏幕总数 |
以下是如何使用分析事件进行追踪的示例:
```kotlin
override fun onAnalyticsEvent(event: AdaptyOnboardingAnalyticsEvent, context: Context) {
when (event) {
is AdaptyOnboardingAnalyticsEvent.OnboardingStarted -> {
// 追踪用户引导开始
trackEvent("onboarding_started", event.meta)
}
is AdaptyOnboardingAnalyticsEvent.ScreenPresented -> {
// 追踪屏幕展示
trackEvent("screen_presented", event.meta)
}
is AdaptyOnboardingAnalyticsEvent.ScreenCompleted -> {
// 追踪屏幕完成及用户响应
trackEvent("screen_completed", event.meta, event.elementId, event.reply)
}
is AdaptyOnboardingAnalyticsEvent.OnboardingCompleted -> {
// 追踪用户引导成功完成
trackEvent("onboarding_completed", event.meta)
}
is AdaptyOnboardingAnalyticsEvent.Unknown -> {
// 处理未知事件
trackEvent(event.name, event.meta)
}
// 按需处理其他情况
}
}
```
事件示例(点击展开)
```javascript
// OnboardingStarted
{
"name": "onboarding_started",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "welcome_screen",
"screen_index": 0,
"total_screens": 4
}
}
// ScreenPresented
{
"name": "screen_presented",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "interests_screen",
"screen_index": 2,
"total_screens": 4
}
}
// ScreenCompleted
{
"name": "screen_completed",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "profile_screen",
"screen_index": 1,
"total_screens": 4
},
"params": {
"element_id": "profile_form",
"reply": "success"
}
}
// SecondScreenPresented
{
"name": "second_screen_presented",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "profile_screen",
"screen_index": 1,
"total_screens": 4
}
}
// UserEmailCollected
{
"name": "user_email_collected",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "profile_screen",
"screen_index": 1,
"total_screens": 4
}
}
// OnboardingCompleted
{
"name": "onboarding_completed",
"meta": {
"onboarding_id": "onboarding_123",
"screen_cid": "final_screen",
"screen_index": 3,
"total_screens": 4
}
}
```
---
# File: android-onboarding-input
---
---
title: "在 Android SDK 中处理用户引导的数据"
description: "使用 Adapty SDK 在 Android 应用中保存并使用用户引导的数据。"
---
:::tip
**从 SDK v4 开始**,你可以构建[流程](android-get-pb-paywalls),作为用户引导更强大的替代方案。与运行在 WebView 中的用户引导不同,流程在设备上原生渲染——带来更流畅的动画、一致的 Android 外观体验、更快的加载速度,以及无需 WebView 运行时依赖。请参阅[获取流程与付费墙](android-get-pb-paywalls)和[展示流程与付费墙](android-present-paywalls)以快速上手。
:::
当用户回答测验问题或在输入框中填写数据时,`onStateUpdatedAction` 方法会被调用。你可以在代码中保存或处理字段类型。
例如:
```kotlin
override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) {
// Store user preferences or responses
when (val params = action.params) {
is AdaptyOnboardingStateUpdatedParams.Select -> {
// Handle single selection
}
is AdaptyOnboardingStateUpdatedParams.MultiSelect -> {
// Handle multiple selections
}
is AdaptyOnboardingStateUpdatedParams.Input -> {
// Handle text input
}
is AdaptyOnboardingStateUpdatedParams.DatePicker -> {
// Handle date selection
}
}
}
```
请参阅[此处](https://android.adapty.io/adapty-ui/com.adapty.ui.onboardings.actions/-adapty-onboarding-state-updated-action/)的操作格式说明。
已保存数据示例(格式可能因您的实现方式而有所不同)
```javascript
// Example of a saved select action
{
"elementId": "preference_selector",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "preferences_screen",
"screenIndex": 1,
"screensTotal": 3
},
"params": {
"type": "select",
"value": {
"id": "option_1",
"value": "premium",
"label": "Premium Plan"
}
}
}
// Example of a saved multi-select action
{
"elementId": "interests_selector",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "interests_screen",
"screenIndex": 2,
"screensTotal": 3
},
"params": {
"type": "multiSelect",
"value": [
{
"id": "interest_1",
"value": "sports",
"label": "Sports"
},
{
"id": "interest_2",
"value": "music",
"label": "Music"
}
]
}
}
// Example of a saved input action
{
"elementId": "name_input",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 0,
"screensTotal": 3
},
"params": {
"type": "input",
"value": {
"type": "text",
"value": "John Doe"
}
}
}
// Example of a saved date picker action
{
"elementId": "birthday_picker",
"meta": {
"onboardingId": "onboarding_123",
"screenClientId": "profile_screen",
"screenIndex": 0,
"screensTotal": 3
},
"params": {
"type": "datePicker",
"value": {
"day": 15,
"month": 6,
"year": 1990
}
}
}
```
## 使用场景 \{#use-cases\}
### 用户画像数据补充 \{#enrich-user-profiles-with-data\}
如果你希望立即将用户填写的信息与其用户画像关联,避免重复询问同样的内容,可以在处理操作时[更新用户画像](android-setting-user-attributes),将输入数据写入其中。
例如,你让用户在 ID 为 `name` 的文本框中输入姓名,并希望将该字段的值设为用户的名字;同时,你还让用户在 `email` 字段中输入邮箱。在应用代码中,实现方式如下:
```kotlin showLineNumbers
override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) {
// Store user preferences or responses
when (val params = action.params) {
is AdaptyOnboardingStateUpdatedParams.Input -> {
// Handle text input
val builder = AdaptyProfileParameters.Builder()
// Map elementId to appropriate profile field
when (action.elementId) {
"name" -> {
when (val inputParams = params.params) {
is AdaptyOnboardingInputParams.Text -> {
builder.withFirstName(inputParams.value)
}
}
}
"email" -> {
when (val inputParams = params.params) {
is AdaptyOnboardingInputParams.Email -> {
builder.withEmail(inputParams.value)
}
}
}
}
Adapty.updateProfile(builder.build()) { error ->
if (error != null) {
// handle the error
}
}
}
}
}
```
### 根据答案自定义付费墙 \{#customize-paywalls-based-on-answers\}
通过在用户引导中加入问卷测验,你还可以根据用户完成引导后的答案来定制展示给他们的付费墙。
例如,你可以询问用户的运动经验,并向不同用户群展示不同的行动号召文案(CTA)和产品。
1. 在用户引导编辑工具中[添加问卷测验](onboarding-quizzes),并为各选项设置有意义的 ID。
2. 根据 ID 处理测验响应,并为用户[设置自定义属性](android-setting-user-attributes)。
```kotlin showLineNumbers
override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) {
// Handle quiz responses and set custom attributes
when (val params = action.params) {
is AdaptyOnboardingStateUpdatedParams.Select -> {
// Handle quiz selection
val builder = AdaptyProfileParameters.Builder()
// Map quiz responses to custom attributes
when (action.elementId) {
"experience" -> {
// Set custom attribute 'experience' with the selected value (beginner, amateur, pro)
builder.withCustomAttribute("experience", params.params.value)
}
}
Adapty.updateProfile(builder.build()) { error ->
if (error != null) {
// handle the error
}
}
}
}
}
```
3. 为每个自定义属性值[创建市场细分](segments)。
4. 创建一个[版位](placements),并为每个已创建的市场细分添加[目标受众](audience)。
5. 在应用代码中为该版位[展示付费墙](android-paywalls)。如果你的用户引导中有一个按钮用于打开付费墙,请将付费墙代码实现为[该按钮操作的响应](android-handle-onboarding-events#opening-a-paywall)。
---
# File: android-sdk-call-order
---
---
title: "Android SDK 调用顺序"
description: "通过按正确顺序调用 Adapty SDK 方法,避免丢失高级访问权限、归因数据缺失以及间歇性 ADAPTY_NOT_INITIALIZED 错误。"
---
`Adapty.activate()` 必须在调用任何其他 Adapty SDK 方法之前完成。在其完成之前,SDK 没有任何状态。在 `activate()` 之前或与之并行发出的任何调用都会以 [`ADAPTY_NOT_INITIALIZED`](android-sdk-error-handling) 错误失败。
如果你的应用需要用户认证,并在启动后才能获取到 customer user ID,请在获取到后调用 `Adapty.identify()`。在 `identify` 的完成回调触发之前,不要调用任何用户操作相关的方法。与 `identify` 并发的调用要么在回调中返回错误,要么会落到激活时创建的匿名用户画像上。一旦发生这种情况,归因数据、`appsflyer_id` 等 MMP ID 以及安装归属不一定会转移到已识别的用户画像。如果你的应用不需要用户认证,可以跳过 `identify`,直接使用匿名用户画像。
MMP 和分析 SDK(AppsFlyer、Adjust、Branch、PostHog)遵循同样的规则。请先初始化它们,等待 UID 回调后再调用 `Adapty.activate`。否则 MMP ID 会落到一个短暂的匿名用户画像上,并不总能转移到已识别的用户画像中。有关 AppsFlyer 的具体说明,请参阅 [AppsFlyer](appsflyer)。
## 正确的操作顺序 \{#the-correct-order\}
您的路径取决于两件事:何时获取客户用户 ID,以及是否使用了 MMP 或数据分析 SDK。
- **步骤 2 和 5**:每个应用都必须执行。先激活 SDK,再调用 SDK 方法。
- **步骤 1 和 3**:仅在集成 MMP 或数据分析 SDK(AppsFlyer、Adjust、Branch、PostHog)时需要。
- **步骤 4**:仅在应用需要用户身份验证、且在启动后才能获取客户用户 ID 时需要。
如果在应用启动时已知用户的 customer user ID,可在调用 `activate()` 前将其传入 `AdaptyConfig.Builder`(步骤 2a)。这种方式不会创建匿名用户画像,因此无需执行步骤 4。
| 步骤 | 调用 | 时机 | 说明 |
|------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
| 1 | 初始化你的 MMP 或分析 SDK(AppsFlyer、Adjust、PostHog、Branch) | 应用启动时,第一步 | 等待 MMP 的 UID 回调,例如 `getAppsFlyerUID`。 |
| 2a | `Adapty.activate(context, AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | 应用启动时,步骤 1 之后,如果你已有 customer user ID | 推荐。不会创建任何匿名用户画像。 |
| 2b | `Adapty.activate(context, AdaptyConfig.Builder("KEY").build())` 不传 `customerUserId` | 应用启动时,步骤 1 之后,如果你还没有 customer user ID(或从不收集) | Adapty 会创建一个匿名用户画像。 |
| 3 | 针对每个 MMP 调用 `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` | 步骤 2 之后,任何用户操作调用之前 | 必须执行,以确保 MMP ID 关联到正确的用户画像。 |
| 4 | `Adapty.identify("YOUR_USER_ID") { error -> ... }` | 步骤 3 之后(若无 MMP 则步骤 2 之后),步骤 5 之前 — 仅适用于走路径 2b 且需要身份验证的情况 | 使用完成回调。在 `identify` 执行期间并发调用可能会落到匿名用户画像上。 |
| 5 | `getPaywall`、`getPaywallProducts`、`restorePurchases`、`makePurchase`、`updateAttribution`、`updateProfile` | 如果调用了 `identify`,则在步骤 4 之后;否则在步骤 3 之后(若无 MMP 则步骤 2 之后) | 这些调用需要一个稳定的用户画像。 |
:::important
跳过这些步骤会导致回归用户失去高级访问权限、用户画像缺少 `appsflyer_id`,以及付费墙被错误的目标受众所匹配。
:::
## Web2app 与网页漏斗安装 \{#web2app-and-web-funnel-installs\}
如果用户在网页结账(Stripe、Paddle)完成购买后再安装原生应用,设备首次调用 `activate()` 时会创建一个新的匿名用户画像,该画像不会与网页端的用户画像关联。如果你能在应用启动前(通过认证流程或安装来源)获取到 customer user ID,可以直接将其传入 `AdaptyConfig.Builder`。否则,在你调用 `identify("YOUR_USER_ID")` 并执行 `restorePurchases` 之前,设备上将看不到该网页购买记录。
关于每次网页结账需要传递的元数据,请参考:
- [Stripe](stripe)
- [Paddle](paddle)
---
# File: android-optimize-paywall-fetching
---
---
title: "优化 Android SDK 中的付费墙获取"
description: "可靠地获取 Adapty 付费墙:Android 的时机、缓存与备用方案。"
---
在 Android 上可靠地获取付费墙需要做到三点:渲染速度快、返回针对目标受众的付费墙,以及在网络较慢时能优雅降级。以下规则涵盖了实现这些目标所需的时机、缓存和备用方案。
:::tip
以下规则假定 `Adapty.activate()` 和 `Adapty.identify()` 均已完成。详情请参阅 [Android SDK 调用顺序](android-sdk-call-order)。
:::
## 注意事项与常见陷阱 \{#rules-and-pitfalls\}
| 应该这样做 | 不要这样做 | 原因 |
|---|---|---|
| 仅在即将展示时获取对应的版位。 | 启动时并发预取所有版位。 | 批量预取会阻塞主线程,导致启动期间出现黑屏。 |
| 在归因数据有机会解析后再调用 `getPaywall`,例如在 `activate` 之后等待 1–2 秒,或等待 `setOnProfileUpdatedListener` 触发后再调用。 | 在 `Application.onCreate()` 中调用 `getPaywall`。 | 此时归因数据尚未就绪,付费墙将按默认目标受众进行解析,静默绕过市场细分和 ASA 个性化配置。 |
| 为每个版位设置 `loadTimeout` 并配置[备用付费墙](fallback-paywalls)。 | 无限等待 `getPaywall` 返回。 | 没有超时限制时,网络较差的用户会看到空白屏幕,直到网络恢复——或者直接关闭应用。 |
有关 `fetchPolicy` 和 `loadTimeout` 参数的说明,请参阅[获取付费墙和产品](fetch-paywalls-and-products-android);有关选择合适版位的说明,请参阅[版位](placements)。
## 针对弱网环境进行调优 \{#tune-for-poor-connectivity\}
针对网络状况持续较差的市场(农村地区、交通途中、受路由问题影响的地区):
- 除首次请求外,每次获取付费墙时将 `fetchPolicy` 设置为 `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad`。
- 在 Adapty 看板中为每个版位配置[备用付费墙](fallback-paywalls)。
- 将 `loadTimeout` 设置为 3–5 秒,并在超时触发时接受备用付费墙。
- 不要将付费墙的展示逻辑依赖于 `getProfile` 的返回结果。独立调用 `getPaywall`,避免因用户画像加载缓慢而阻塞界面。
---
# File: android-test
---
---
title: "在 Android SDK 中测试与发布"
description: "了解如何在 Android 应用中使用 Adapty 检查订阅状态。"
---
如果你已经在 Android 应用中集成了 Adapty SDK,接下来需要测试所有配置是否正确,以及购买流程是否按预期运行。这包括测试 SDK 集成以及使用 Google Play 沙盒环境验证实际的购买流程。
## 测试你的应用 \{#test-your-app\}
关于应用内购买的全面测试指南,包括沙盒测试和封闭轨道验证,请参阅我们的[测试指南](testing-on-android)。
## 发布前准备 \{#prepare-for-release\}
在将应用提交到应用商店之前,请按照[发布检查清单](release-checklist)确认以下事项:
- 已配置应用商店连接和服务器通知
- 购买已完成并上报至 Adapty
- 访问等级能够正确解锁和恢复
- 已满足隐私和审核要求
---
# File: android-sdk-error-handling
---
---
title: "处理 Android SDK 错误"
description: "借助 Adapty 的故障排查指南,有效处理 Android SDK 错误。"
---
SDK 返回的所有错误均为 `AdaptyError` 类型。
:::tip
**在调试前开启详细日志。** 大多数 `AdaptyError` 都封装了底层的 Play Billing、网络或后端错误。开启详细日志(`Adapty.logLevel = AdaptyLogLevel.VERBOSE` — 参见[日志记录](sdk-installation-android#logging))后,封装的错误会打印到控制台,通常能直接告诉你真正的原因。
:::
:::important
如果以上方案未能解决你的问题,请查看[其他问题](#other-issues),了解联系支持前需要做哪些准备,以便我们更高效地为你提供帮助。
:::
| 错误 | 解决方案 |
|----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| UNKNOWN | 发生了未知或意外错误。 |
| [ITEM_UNAVAILABLE](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_UNAVAILABLE()) | 此错误多发生在测试阶段,可能表示产品未发布至生产环境,或该用户不在 Google Play 的 Testers 分组中。 |
| ADAPTY_NOT_INITIALIZED | Adapty SDK 未激活。
最常见的情况是启动页或早期 UI 钩子在 `Adapty.activate` 返回之前就调用了 Adapty 方法。此问题偶发,在模拟器上可能无法复现,因为真机的时序不同。请等待 `Adapty.activate` 完成后再发起其他 SDK 调用。完整调用顺序请参阅 [Android SDK 调用顺序](android-sdk-call-order)。同时需要通过 `Adapty.activate` 方法正确[配置 Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk)。 |
| PROFILE_WAS_CHANGED | 操作期间用户画像发生了变更。
当 `Adapty.identify` 仍在执行时调用其他方法会触发此错误——进行中的调用落在即将被替换的用户画像上,SDK 会拒绝该请求。请等待 `Adapty.identify` 完成后再发起其他 SDK 调用。请参阅 [Android SDK 调用顺序](android-sdk-call-order)。 |
| PRODUCT_NOT_FOUND | 请求购买的产品在商店中不可用。 |
| INVALID_JSON | 本地备用付费墙 JSON 格式无效。
请先修复默认的英文付费墙,再替换无效的本地付费墙。如何修复付费墙,请参阅[使用远程配置自定义付费墙](customize-paywall-with-remote-config);如何替换本地付费墙,请参阅[定义本地备用付费墙](fallback-paywalls)。
|
| CURRENT_SUBSCRIPTION_TO_UPDATE
\_NOT_FOUND_IN_HISTORY
| 需要替换的原始订阅在活跃订阅中未找到。 |
| [BILLING_SERVICE_TIMEOUT](https://developer.android.com/google/play/billing/errors#service_timeout_error_code_-3) | 请求在 Google Play 响应之前已达到最大超时时间。例如,Play Billing Library 调用所请求的操作执行延迟可能导致此错误。 |
| [FEATURE_NOT_SUPPORTED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#FEATURE_NOT_SUPPORTED()) | 当前设备的 Play Store 不支持所请求的功能。 |
| [BILLING_SERVICE_DISCONNECTED](https://developer.android.com/google/play/billing/errors#service_disconnected_error_code_-1) | 客户端应用通过 `BillingClient` 与 Google Play Store 服务的连接已断开。 |
| [BILLING_SERVICE_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#service_unavailable_error_code_2) | Google Play 计费服务当前不可用。大多数情况下,这意味着客户端设备与 Google Play 计费服务之间存在网络连接问题。 |
| [BILLING_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | 购买过程中发生了计费问题,可能原因如下:
1. 用户设备上的 Play Store 应用缺失或版本过旧。
2. 用户所在国家/地区不受支持。
3. 用户属于企业账号,管理员已禁用购买功能。
4. Google Play 无法向用户的支付方式扣款(例如信用卡已过期)。
5. 用户未登录 Play Store 应用。
|
| [DEVELOPER_ERROR](https://developer.android.com/google/play/billing/errors#developer_error) | API 使用方式不正确。 |
| [BILLING_ERROR](https://developer.android.com/google/play/billing/errors#error_error_code_6) | Google Play 内部出现问题。 |
| [ITEM_ALREADY_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_ALREADY_OWNED()) | 该产品已购买。 |
| [ITEM_NOT_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_NOT_OWNED()) | 该商品不属于当前用户,无法执行所请求的操作。 |
| [BILLING_NETWORK_ERROR](https://developer.android.com/google/play/billing/errors#network_error_error_code_12) | 设备与 Play 系统之间的网络连接出现问题。 |
| NO_PRODUCT_IDS_FOUND | 付费墙中没有任何产品在商店中可用。
如果遇到此错误,请按以下步骤排查:
- 检查所有产品是否已添加到 Adapty 看板。
- 确认应用的 **Package name** 与 Google Play Console 中的一致。
- 核实应用商店中的产品标识符与看板中添加的标识符一致。注意:除非商店本身已包含 Bundle ID,否则标识符中不应包含 Bundle ID。
- 确认您的 Google 税务设置中应用的付费状态为 **Active**,税务信息是最新的,且证书有效。
- 检查应用是否已绑定银行账号,以便参与变现。
- 检查产品是否在您所在的地区可用。
- 确保应用已加入某个测试轨道。**Internal testing** 轨道是最简便的选项,无需审核且对用户不可见。
|
| NO_PURCHASES_TO_RESTORE | Google Play 未找到可恢复的购买记录。 |
| AUTHENTICATION_ERROR | 请通过 `Adapty.activate` 方法正确[配置 Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk)。 |
| BAD_REQUEST | 请求无效。
请确认已完成[与 Google Play 集成](google-play-store-connection-configuration)所需的所有步骤。 |
| SERVER_ERROR | 服务器错误。 |
| REQUEST_FAILED | 发生了无法明确定义的网络问题。 |
| DECODING_FAILED | 无法解析响应。
请检查您的代码,确认发送的参数有效。例如,此错误可能表示您使用了无效的 API 密钥。 |
| ANALYTICS_DISABLED | 由于您已[关闭该功能](analytics-integration#disabling-external-analytics-for-a-specific-customer),无法处理数据分析事件。 |
| WRONG_PARAMETER | 部分参数不正确:不能为空的字段为空,或参数类型错误等。 |
## 其他问题 \{#other-issues\}
如果您尚未找到解决方案,可以采取以下后续步骤:
- **将 SDK 升级到最新版本**:我们始终建议升级到最新的 SDK 版本,因为它们更稳定并包含已知问题的修复。
- **联系支持团队或在[支持论坛](https://adapty.featurebase.app/)中获得其他开发者的帮助**。
- **通过 [support@adapty.io](mailto:support@adapty.io) 或在线聊天联系支持团队**:如果您还不打算升级 SDK 或升级后问题仍未解决,请联系我们的支持团队。请注意,如果您[启用详细日志记录](sdk-installation-android#logging)并与团队共享日志,您的问题将得到更快解决。您也可以附上相关代码片段。
---
# File: migration-to-android-sdk-v4
---
---
title: "将 Adapty Android SDK 迁移至 v. 4.0"
description: "通过将付费墙 API 替换为流程 API,迁移至 Adapty Android SDK v4.0,兼容流程编辑工具和付费墙编辑工具。"
---
Adapty Android SDK 4.0 引入了流程功能,并相应地对付费墙 API 进行了重命名。新 API 同时兼容新版流程编辑工具和现有的付费墙编辑工具——无需在 Adapty 看板端进行任何配置变更。
## 快速参考 \{#quick-reference\}
| v3 | v4 |
|---|---|
| `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` |
| `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` |
| `AdaptyUI.getViewConfiguration(paywall)` | `AdaptyUI.getFlowConfiguration(flow, locale)` |
| `AdaptyUI.LocalizedViewConfiguration` | `AdaptyUI.FlowConfiguration` |
| `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` |
| `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` |
| `AdaptyPaywall` | `AdaptyFlow` |
| `AdaptyUI.getPaywallView(...)` | `AdaptyUI.getFlowView(...)` |
| `AdaptyPaywallView` | `AdaptyFlowView` |
| `AdaptyPaywallScreen` (Compose) | `AdaptyFlowScreen` |
| `showPaywall(...)` | `showFlow(...)` |
| `AdaptyPaywallInsets` | `AdaptyFlowInsets` |
| `AdaptyUiEventListener` | `AdaptyFlowEventListener` |
| `AdaptyUiDefaultEventListener` | `AdaptyFlowDefaultEventListener` |
| `onPaywallShown` / `onPaywallClosed` | `onFlowShown` / `onFlowClosed` |
| `onRenderingError` | `onError` |
| `Adapty.updateAttribution(attribution, source)` (`source: String`) | `Adapty.updateAttribution(attribution, source)` (`source: AdaptyAttributionSource`) |
| `Adapty.setIntegrationIdentifier(key, value)` | `Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier)` |
`AdaptyPaywallProduct` 保持原名不变——产品仍属于某个 flow,`getPaywallProducts` 现在接收 `AdaptyFlow` 参数。其他 `AdaptyFlowEventListener` 方法(`onProductSelected`、`onPurchaseStarted`、`onPurchaseFinished`、`onPurchaseFailure`、`onRestoreSuccess`、`onRestoreFailure`、`onActionPerformed`、`onAwaitingPurchaseParams`、`onLoadingProductsFailure` 等)保持原有名称和签名不变。
## 安装 \{#installation\}
将 `adapty-bom` 版本设置为 `4.0.0`(或更高版本)并同步项目。BOM 会自动为你解析匹配的 `android-sdk` 和 `android-ui` 版本。依赖项声明请参见[安装 Adapty SDK](sdk-installation-android)。
## 已移除和废弃的 API \{#removed-and-deprecated-apis\}
- **`Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized, callback)`** — 已移除。此重载方法在 v3 中已废弃。请改用 `AdaptyPurchaseParameters` 传入相同的选项:
```diff showLineNumbers
- Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized) { result -> /* ... */ }
+ val params = AdaptyPurchaseParameters.Builder()
+ .withSubscriptionUpdateParams(subscriptionUpdateParams)
+ .withOfferPersonalized(isOfferPersonalized)
+ .build()
+ Adapty.makePurchase(activity, product, params) { result -> /* ... */ }
```
- **用户引导已弃用。** `AdaptyUI.getOnboardingView` 和 `AdaptyUI.getOnboardingConfiguration` 在 4.0 中标记为 `@Deprecated` — 请将用户引导迁移到在[流程编辑工具](adapty-flow-builder)中构建的流程。
## 获取流程 \{#fetching-flows\}
### getPaywall + getViewConfiguration → getFlow + getFlowConfiguration
获取返回类型从 `AdaptyPaywall` 变更为 `AdaptyFlow`,配置加载器从 `AdaptyUI.getViewConfiguration` 重命名为 `AdaptyUI.getFlowConfiguration`(返回 `AdaptyUI.FlowConfiguration` 而非 `AdaptyUI.LocalizedViewConfiguration`)。`locale` 参数从获取调用中移出,改为传入 `getFlowConfiguration`:
```diff showLineNumbers
- Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result ->
+ Adapty.getFlow("YOUR_PLACEMENT_ID") { result ->
if (result is AdaptyResult.Success) {
- val paywall = result.value
- if (!paywall.hasViewConfiguration) return@getPaywall
- AdaptyUI.getViewConfiguration(paywall) { configResult ->
+ val flow = result.value
+ if (!flow.hasViewConfiguration) return@getFlow
+ AdaptyUI.getFlowConfiguration(flow, locale = "en") { configResult ->
if (configResult is AdaptyResult.Success) {
val flowConfiguration = configResult.value
}
}
}
}
```
### getPaywallProducts(paywall) → getPaywallProducts(flow)
`getPaywallProducts` 现在接受由 `Adapty.getFlow` 返回的 `AdaptyFlow`:
```diff showLineNumbers
- Adapty.getPaywallProducts(paywall) { result -> /* products */ }
+ Adapty.getPaywallProducts(flow) { result -> /* products */ }
```
## 跟踪流程浏览数据 \{#tracking-flow-views\}
### logShowPaywall → logShowFlow
`logShowPaywall` 已重命名为 `logShowFlow`,现在接受 `AdaptyFlow` 而非 `AdaptyPaywall`。事件仍会记录在同一个实验变体下,因此现有的漏斗和 A/B 测试数据图表无需修改看板即可继续正常使用。
```diff showLineNumbers
- Adapty.logShowPaywall(paywall)
+ Adapty.logShowFlow(flow)
```
与 v3 一样,当展示由[流程编辑工具](adapty-flow-builder)或[付费墙编辑工具](adapty-paywall-builder)渲染的流程或付费墙时,无需手动调用此方法——Adapty 会自动追踪这些页面的浏览记录。
## 显示流程 \{#displaying-flows\}
### getPaywallView / AdaptyPaywallView → getFlowView / AdaptyFlowView
重命名工厂方法和视图类型,并传入 `AdaptyUI.FlowConfiguration`:
```diff showLineNumbers
- val paywallView = AdaptyUI.getPaywallView(
- activity,
- viewConfiguration,
- products,
- eventListener,
- )
+ val flowView = AdaptyUI.getFlowView(
+ activity,
+ flowConfiguration,
+ products,
+ eventListener,
+ )
```
如果直接创建视图,show 方法也需要重命名:
```diff showLineNumbers
- val paywallView = AdaptyPaywallView(activity)
- paywallView.showPaywall(viewConfiguration, products, eventListener)
+ val flowView = AdaptyFlowView(activity)
+ flowView.showFlow(flowConfiguration, products, eventListener)
```
在 XML 布局中,更新视图标签:
```diff showLineNumbers
-
+
```
可选参数 `personalizedOfferResolver` 已从 `getFlowView` / `showFlow` / `AdaptyFlowScreen` 中移除。如需标记个性化定价,请通过 `onAwaitingPurchaseParams` 为每个产品单独设置(`AdaptyPurchaseParameters.Builder().withOfferPersonalized(true)`)。新增的可选参数 `customAssets` 允许你在运行时覆盖图片和视频——详见[自定义资源](android-get-pb-paywalls#customize-assets)。
### AdaptyPaywallScreen → AdaptyFlowScreen
在 Jetpack Compose 中,重命名 composable 并更新配置参数:
```diff showLineNumbers
- AdaptyPaywallScreen(
- viewConfiguration,
+ AdaptyFlowScreen(
+ flowConfiguration,
products,
eventListener,
)
```
## 处理事件 \{#handling-events\}
事件监听器已从 `AdaptyUiEventListener` 重命名为 `AdaptyFlowEventListener`(`AdaptyUiDefaultEventListener` 也相应重命名为 `AdaptyFlowDefaultEventListener`)。大多数方法名保持不变;生命周期和渲染相关的回调已重命名:
```diff showLineNumbers
- class YourListener : AdaptyUiDefaultEventListener() {
+ class YourListener : AdaptyFlowDefaultEventListener() {
- override fun onPaywallShown(context: Context) {}
- override fun onPaywallClosed() {}
+ override fun onFlowShown(context: Context) {}
+ override fun onFlowClosed() {}
- override fun onRenderingError(error: AdaptyError, context: Context) {}
+ override fun onError(error: AdaptyError, context: Context) {}
}
```
现有的处理器主体无需修改代码——只需重命名类型和重写方法即可。`onError` 触发的场景与 `onRenderingError` 相同,还额外涵盖其他非购买类运行时错误。完整的回调列表请参阅[处理流程与付费墙事件](android-handling-events)。
v4 还新增了 `onBackPressed(context): Boolean` 回调,其默认行为也改变了系统返回键的处理方式。此前,返回键(或返回手势)会透传给 Activity 或 Fragment,通常会关闭付费墙。v4 中默认实现会消费该按键事件,因此**系统返回键不再自动关闭流程**——与 iOS 保持一致(iOS 上流程无法通过系统手势关闭)。请为用户提供明确的退出方式(添加 **Close** 按钮或 `on_device_back` 操作),或者重写 `onBackPressed` 并返回 `false` 以恢复旧有行为。详情请参阅[系统返回键](android-handling-events#system-back-button)。
默认购买处理器也不再自动关闭屏幕。在 v3 中,默认的 `onPurchaseFinished` 会在任何非用户主动取消的购买完成后(成功或待处理的购买)关闭付费墙。在 v4 中它是一个空操作,因此**流程在购买完成后会保持打开状态,直到你手动关闭它**——这与 iOS 的行为一致。如果你之前依赖自动关闭功能,请在购买完成后自行关闭屏幕。具体示例请参阅[购买成功、取消或待处理](android-handling-events#successful-canceled-or-pending-purchase)。
## 归因与集成标识符 \{#attribution-and-integration-identifiers\}
### updateAttribution
`source` 参数从 `String` 类型变更为新的 `AdaptyAttributionSource` 类型,`attribution` 现在是 `Map`(同时也提供 JSON `String` 重载)。请使用以下预定义来源之一:
```diff showLineNumbers
- Adapty.updateAttribution(attribution, "appsflyer") { error -> /* handle the error */ }
+ Adapty.updateAttribution(attribution, AdaptyAttributionSource.APPSFLYER) { error -> /* handle the error */ }
```
预定义来源:`AdaptyAttributionSource.APPLE_ADS`、`.ADJUST`、`.APPSFLYER`、`.BRANCH`、`.TENJIN`。如需使用其他来源,可通过字符串构建:`AdaptyAttributionSource("your_source")`。
### setIntegrationIdentifier
`setIntegrationIdentifier(key, value)` 已被替换为接受一个或多个 `AdaptyIntegrationIdentifier` 值的方法。请使用便捷方法构建每个标识符,而不是传入原始字符串键:
```diff showLineNumbers
- Adapty.setIntegrationIdentifier("appsflyer_id", appsFlyerId) { error -> /* handle the error */ }
+ Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId)) { error -> /* handle the error */ }
```
你可以在一次调用中设置多个标识符:
```kotlin showLineNumbers
Adapty.setIntegrationIdentifier(
listOf(
AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId),
AdaptyIntegrationIdentifier.adjustDeviceId(adjustDeviceId),
)
) { error -> /* handle the error */ }
```
将每个旧的键字符串替换为对应的便捷方法:
| v3 键名 | v4 `AdaptyIntegrationIdentifier` 方法 |
|---|---|
| `"adjust_device_id"` | `adjustDeviceId(value)` |
| `"airbridge_device_id"` | `airbridgeDeviceId(value)` |
| `"amplitude_user_id"` | `amplitudeUserId(value)` |
| `"amplitude_device_id"` | `amplitudeDeviceId(value)` |
| `"appmetrica_device_id"` | `appmetricaDeviceId(value)` |
| `"appmetrica_profile_id"` | `appmetricaProfileId(value)` |
| `"appsflyer_id"` | `appsflyerId(value)` |
| `"branch_id"` | `branchId(value)` |
| `"facebook_anonymous_id"` | `facebookAnonymousId(value)` |
| `"firebase_app_instance_id"` | `firebaseAppInstanceId(value)` |
| `"mixpanel_user_id"` | `mixpanelUserId(value)` |
| `"one_signal_subscription_id"` | `oneSignalSubscriptionId(value)` |
| `"one_signal_player_id"` | `oneSignalPlayerId(value)` |
| `"posthog_distinct_user_id"` | `posthogDistinctUserId(value)` |
| `"pushwoosh_hwid"` | `pushwooshHWID(value)` |
| `"tenjin_analytics_installation_id"` | `tenjinAnalyticsInstallationId(value)` |
对于不在此列表中的键,可以直接使用自定义 `Key` 来构建标识符:`AdaptyIntegrationIdentifier(AdaptyIntegrationIdentifier.Key("custom"), customValue)`。
---
# File: migration-to-android-312
---
---
title: "将 Adapty Android SDK 迁移至 v3.12"
description: "迁移至 Adapty Android SDK v3.12,获得更好的性能与全新的变现功能。"
---
在 Adapty SDK 3.12.0 中,我们已从 SDK 中移除了 `logShowOnboarding` 方法。
如果你之前使用过该方法,升级至 SDK 3.12 或更高版本后将无法继续使用它。
作为替代,您可以[在 Adapty 无代码用户引导编辑工具中创建用户引导](onboardings)。这些用户引导的分析数据会自动追踪,并且您拥有丰富的自定义选项。
---
# File: migration-to-android-310
---
---
title: "Android Adapty SDK 3.10.0 迁移指南"
description: ""
---
Adapty SDK 3.10.0 是一个主要版本,带来了一些改进,但可能需要您执行一些迁移步骤:
1. `AdaptyUiPersonalizedOfferResolver` 已被移除。如果您正在使用它,请在 `onAwaitingPurchaseParams` 回调中传入。
2. 更新付费墙编辑工具付费墙的 `onAwaitingSubscriptionUpdateParams` 方法签名。
## 更新购买参数回调 \{#update-purchase-parameters-callback\}
`onAwaitingSubscriptionUpdateParams` 方法已重命名为 `onAwaitingPurchaseParams`,现在使用 `AdaptyPurchaseParameters` 代替 `AdaptySubscriptionUpdateParameters`。这使您可以指定订阅替换参数(跨级别升降级)并指示价格是否为个性化价格([了解更多](https://developer.android.com/google/play/billing/integrate#personalized-price)),以及其他购买参数。
```diff showLineNumbers
- override fun onAwaitingSubscriptionUpdateParams(
- product: AdaptyPaywallProduct,
- context: Context,
- onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
- ) {
- onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
- }
+ override fun onAwaitingPurchaseParams(
+ product: AdaptyPaywallProduct,
+ context: Context,
+ onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
+ ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
+ onPurchaseParamsReceived(
+ AdaptyPurchaseParameters.Builder()
+ .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
+ .withOfferPersonalized(true)
+ .build()
+ )
+ return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
+ }
```
如果不需要额外的参数,您可以直接使用:
```kotlin showLineNumbers
+ override fun onAwaitingPurchaseParams(
product: AdaptyPaywallProduct,
context: Context,
onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
---
# File: migration-to-android-sdk-34
---
---
title: "迁移 Adapty Android SDK 至 v3.4"
description: "迁移至 Adapty Android SDK v3.4,享受更好的性能和全新的变现功能。"
---
Adapty SDK 3.4.0 是一个主要版本,引入了需要你进行迁移操作的改进内容。
## 更新备用付费墙文件 \{#update-fallback-paywall-files\}
更新您的备用付费墙文件以确保与新 SDK 版本的兼容性:
1. 从 Adapty 看板[下载更新后的备用付费墙文件](fallback-paywalls)。
2. 用新文件[替换移动应用中现有的备用付费墙](android-use-fallback-paywalls)。
## 更新观察者模式的实现 \{#update-implementation-of-observer-mode\}
如果你正在使用观察者模式,请确保更新其实现方式。
在之前的版本中,你需要手动恢复购买,Adapty 才能识别通过自有基础设施完成的交易——因为在观察者模式下,Adapty 无法直接访问这些交易。如果你使用了付费墙,还需要手动将每笔交易与触发它的付费墙关联起来。
在新版本中,您必须明确上报每笔交易,Adapty 才能识别它。如果您使用付费墙,还需要传入 variation ID,以便将交易与所用付费墙关联起来。
:::warning
**不要跳过交易上报!**
如果不调用 `reportTransaction`,Adapty 将无法识别该交易,它不会出现在数据分析中,也不会发送到各集成渠道。
:::
```diff showLineNumbers
- Adapty.restorePurchases { result ->
- if (result is AdaptyResult.Success) {
- // success
- }
- }
-
- Adapty.setVariationId(transactionId, variationId) { error ->
- if (error == null) {
- // success
- }
- }
+ val transactionInfo = TransactionInfo.fromPurchase(purchase)
+
+ Adapty.reportTransaction(transactionInfo, variationId) { result ->
+ if (result is AdaptyResult.Success) {
+ // success
+ }
+ }
```
```diff showLineNumbers
- Adapty.restorePurchases(result -> {
- if (result instanceof AdaptyResult.Success) {
- // success
- }
- });
-
- Adapty.setVariationId(transactionId, variationId, error -> {
- if (error == null) {
- // success
- }
- });
+ TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase);
+
+ Adapty.reportTransaction(transactionInfo, variationId, result -> {
+ if (result instanceof AdaptyResult.Success) {
+ // success
+ }
+ });
```
---
# File: migration-to-android330
---
---
title: "迁移 Adapty Android SDK 至 v3.3"
description: "迁移至 Adapty Android SDK v3.3,获得更好的性能和新的变现功能。"
---
Adapty SDK 3.3.0 是一个重大版本更新,带来了若干改进,但可能需要你执行一些迁移步骤。
1. 更新在非付费墙编辑工具创建的付费墙中处理购买的方式。停止处理 `USER_CANCELED` 和 `PENDING_PURCHASE` 错误码。取消购买不再被视为错误,现在将出现在非错误的购买结果中。
2. 对于使用付费墙编辑工具创建的付费墙,将 `onPurchaseCanceled` 和 `onPurchaseSuccess` 事件替换为新的 `onPurchaseFinished` 事件。此更改的原因相同:取消购买不再被视为错误,将包含在非错误的购买结果中。
3. 更改付费墙编辑工具付费墙的 `onAwaitingSubscriptionUpdateParams` 方法签名。
4. 如果直接传递文件 URI,请更新用于提供备用付费墙的方法。
5. 更新 Adjust、AirBridge、Amplitude、AppMetrica、Appsflyer、Branch、Facebook Ads、Firebase 和 Google Analytics、Mixpanel、OneSignal、Pushwoosh 的集成配置。
## 更新购买流程 \{#update-making-purchase\}
之前,已取消和待处理的购买会被视为错误,分别返回 `USER_CANCELED` 和 `PENDING_PURCHASE` 错误码。
现在引入了新的 `AdaptyPurchaseResult` 类,用于表示已取消、成功和待处理的购买状态。请按以下方式更新购买相关代码:
~~~diff
Adapty.makePurchase(activity, product) { result ->
when (result) {
is AdaptyResult.Success -> {
- val info = result.value
- val profile = info?.profile
-
- if (profile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true) {
- // Grant access to the paid features
- }
+ when (val purchaseResult = result.value) {
+ is AdaptyPurchaseResult.Success -> {
+ val profile = purchaseResult.profile
+ if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
+ // Grant access to the paid features
+ }
+ }
+
+ is AdaptyPurchaseResult.UserCanceled -> {
+ // Handle the case where the user canceled the purchase
+ }
+
+ is AdaptyPurchaseResult.Pending -> {
+ // Handle deferred purchases (e.g., the user will pay offline with cash
+ }
+ }
}
is AdaptyResult.Error -> {
val error = result.error
// Handle the error
}
}
}
~~~
如需完整代码示例,请参阅[在移动应用中进行购买](android-making-purchases#make-purchase)页面。
## 修改付费墙编辑工具的购买事件 \{#modify-paywall-builder-purchase-events\}
1. 添加 `onPurchaseFinished` 事件:
```diff showLineNumbers
+ public override fun onPurchaseFinished(
+ purchaseResult: AdaptyPurchaseResult,
+ product: AdaptyPaywallProduct,
+ context: Context,
+ ) {
+ when (purchaseResult) {
+ is AdaptyPurchaseResult.Success -> {
+ // Grant access to the paid features
+ }
+ is AdaptyPurchaseResult.UserCanceled -> {
+ // Handle the case where the user canceled the purchase
+ }
+ is AdaptyPurchaseResult.Pending -> {
+ // Handle deferred purchases (e.g., the user will pay offline with cash)
+ }
+ }
+ }
```
有关完整代码示例,请查看[成功、取消或待处理的购买](android-handling-events#successful-canceled-or-pending-purchase)及事件说明。
2. 移除 `onPurchaseCancelled` 事件的处理:
```diff showLineNumbers
- public override fun onPurchaseCanceled(
- product: AdaptyPaywallProduct,
- context: Context,
- ) {}
```
3. 移除 `onPurchaseSuccess`:
```diff showLineNumbers
- public override fun onPurchaseSuccess(
- profile: AdaptyProfile?,
- product: AdaptyPaywallProduct,
- context: Context,
- ) {
- // Your logic on successful purchase
- }
```
## 修改 onAwaitingSubscriptionUpdateParams 方法的签名 \{#change-the-signature-of--onawaitingsubscriptionupdateparams-method\}
现在,如果在已有活跃订阅的情况下购买新订阅,请在以下情况下调用相应方法:若新订阅应替换当前活跃订阅,调用 `onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters...))`;若活跃订阅应保持不变、新订阅单独新增,则调用 `onSubscriptionUpdateParamsReceived(null)`:
```diff showLineNumbers
- public override fun onAwaitingSubscriptionUpdateParams(
- product: AdaptyPaywallProduct,
- context: Context,
- ): AdaptySubscriptionUpdateParameters? {
- return AdaptySubscriptionUpdateParameters(...)
- }
+ public override fun onAwaitingSubscriptionUpdateParams(
+ product: AdaptyPaywallProduct,
+ context: Context,
+ onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
+ ) {
+ onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
+ }
```
请参阅[升级订阅](android-handling-events#upgrade-subscription)文档章节以查看完整代码示例。
## 更新备用付费墙的提供方式 \{#update-providing-fallback-paywalls\}
如果你通过文件 URI 来提供备用付费墙,请按以下方式更新相关代码:
```diff showLineNumbers
val fileUri: Uri = // Get the URI for the file with fallback paywalls
- Adapty.setFallbackPaywalls(fileUri, callback)
+ Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback)
```
```diff showLineNumbers
Uri fileUri = // Get the URI for the file with fallback paywalls
- Adapty.setFallbackPaywalls(fileUri, callback);
+ Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback);
```
## 更新第三方集成 SDK 配置 \{#update-third-party-integration-sdk-configuration\}
为确保集成能与 Adapty Android SDK 3.3.0 及更高版本正常工作,请按照以下各节所述更新以下集成的 SDK 配置。
### Adjust
按照以下方式更新您的移动应用代码。完整代码示例请参阅 [Adjust 集成的 SDK 配置](adjust#connect-your-app-to-adjust)。
```diff showLineNumbers
- Adjust.getAttribution { attribution ->
- if (attribution == null) return@getAttribution
-
- Adjust.getAdid { adid ->
- if (adid == null) return@getAdid
-
- Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST, adid) { error ->
- // Handle the error
- }
- }
- }
+ Adjust.getAdid { adid ->
+ if (adid == null) return@getAdid
+
+ Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+ }
+
+ Adjust.getAttribution { attribution ->
+ if (attribution == null) return@getAttribution
+
+ Adapty.updateAttribution(attribution, "adjust") { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+ }
```
```diff showLineNumbers
val config = AdjustConfig(context, adjustAppToken, environment)
config.setOnAttributionChangedListener { attribution ->
attribution?.let { attribution ->
- Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST) { error ->
+ Adapty.updateAttribution(attribution, "adjust") { error ->
if (error != null) {
// Handle the error
}
}
}
}
Adjust.onCreate(config)
```
### AirBridge
按以下方式更新您的移动应用代码。完整代码示例请参阅 [AirBridge 集成的 SDK 配置](airbridge#connect-your-app-to-airbridge)。
```diff showLineNumbers
Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback() {
override fun onSuccess(result: String) {
- val params = AdaptyProfileParameters.Builder()
- .withAirbridgeDeviceId(result)
- .build()
- Adapty.updateProfile(params) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
override fun onFailure(throwable: Throwable) {
}
})
```
### Amplitude
按照以下方式更新您的移动应用代码。完整的代码示例请参阅 [Amplitude 集成的 SDK 配置](amplitude#sdk-configuration)。
```diff showLineNumbers
// For Amplitude maintenance SDK (obsolete)
val amplitude = Amplitude.getInstance()
val amplitudeDeviceId = amplitude.getDeviceId()
val amplitudeUserId = amplitude.getUserId()
//for actual Amplitude Kotlin SDK
val amplitude = Amplitude(
Configuration(
apiKey = AMPLITUDE_API_KEY,
context = applicationContext
)
)
val amplitudeDeviceId = amplitude.store.deviceId
val amplitudeUserId = amplitude.store.userId
//
- val params = AdaptyProfileParameters.Builder()
- .withAmplitudeDeviceId(amplitudeDeviceId)
- .withAmplitudeUserId(amplitudeUserId)
- .build()
- Adapty.updateProfile(params) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+ Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
```
### AppMetrica
按如下方式更新您的移动应用代码。完整代码示例,请参阅 [AppMetrica 集成的 SDK 配置](appmetrica#sdk-configuration)。
```diff showLineNumbers
val startupParamsCallback = object: StartupParamsCallback {
override fun onReceive(result: StartupParamsCallback.Result?) {
val deviceId = result?.deviceId ?: return
- val params = AdaptyProfileParameters.Builder()
- .withAppmetricaDeviceId(deviceId)
- .withAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID")
- .build()
- Adapty.updateProfile(params) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+
+ Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
override fun onRequestError(
reason: StartupParamsCallback.Reason,
result: StartupParamsCallback.Result?
) {
// Handle the error
}
}
AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID))
```
### AppsFlyer
按照以下步骤更新您的移动应用代码。完整的代码示例,请参阅 [AppsFlyer 集成的 SDK 配置](appsflyer#connect-your-app-to-appsflyer)。
```diff showLineNumbers
val conversionListener: AppsFlyerConversionListener = object : AppsFlyerConversionListener {
override fun onConversionDataSuccess(conversionData: Map) {
- Adapty.updateAttribution(
- conversionData,
- AdaptyAttributionSource.APPSFLYER,
- AppsFlyerLib.getInstance().getAppsFlyerUID(context)
- ) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ val uid = AppsFlyerLib.getInstance().getAppsFlyerUID(context)
+ Adapty.setIntegrationIdentifier("appsflyer_id", uid) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
+ Adapty.updateAttribution(conversionData, "appsflyer") { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
}
```
### Branch
按照以下方式更新您的移动应用代码。完整的代码示例,请查看 [Branch 集成的 SDK 配置](branch#connect-your-app-to-branch)。
```diff showLineNumbers
// Login and update attribution
Branch.getAutoInstance(this)
.setIdentity("YOUR_USER_ID") { referringParams, error ->
referringParams?.let { data ->
- Adapty.updateAttribution(data, AdaptyAttributionSource.BRANCH) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.updateAttribution(data, "branch") { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
}
// Logout
Branch.getAutoInstance(context).logout()
```
### Facebook Ads
按如下方式更新您的移动应用代码。完整代码示例请参考 [Facebook Ads 集成的 SDK 配置](facebook-ads#connect-your-app-to-facebook-ads)。
```diff showLineNumbers
- val builder = AdaptyProfileParameters.Builder()
- .withFacebookAnonymousId(AppEventsLogger.getAnonymousAppDeviceGUID(context))
-
- Adapty.updateProfile(builder.build()) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier(
+ "facebook_anonymous_id",
+ AppEventsLogger.getAnonymousAppDeviceGUID(context)
+ ) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
```
### Firebase 与 Google Analytics \{#firebase-and-google-analytics\}
按照以下示例更新您的移动应用代码。完整代码示例请参阅 [Firebase 与 Google Analytics 集成的 SDK 配置](firebase-and-google-analytics)。
```diff showLineNumbers
// After Adapty.activate()
FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId ->
- Adapty.updateProfile(
- AdaptyProfileParameters.Builder()
- .withFirebaseAppInstanceId(appInstanceId)
- .build()
- ) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
}
```
```diff showLineNumbers
// After Adapty.activate()
- FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> {
- AdaptyProfileParameters params = new AdaptyProfileParameters.Builder()
- .withFirebaseAppInstanceId(appInstanceId)
- .build();
-
- Adapty.updateProfile(params, error -> {
- if (error != null) {
- // Handle the error
- }
- });
- });
+ FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> {
+ Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> {
+ if (error != null) {
+ // Handle the error
+ }
+ });
+ });
```
### Mixpanel \{#mixpanel\}
按如下所示更新您的移动应用代码。完整代码示例,请查看 [Mixpanel 集成的 SDK 配置](mixpanel#sdk-configuration)。
```diff showLineNumbers
- val params = AdaptyProfileParameters.Builder()
- .withMixpanelUserId(mixpanelAPI.distinctId)
- .build()
-
- Adapty.updateProfile(params) { error ->
- if (error != null) {
- // Handle the error
- }
- }
+ Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error ->
+ if (error != null) {
+ // Handle the error
+ }
+ }
```
### OneSignal
按照以下方式更新您的移动应用代码。完整代码示例请参阅 [OneSignal 集成的 SDK 配置](onesignal#sdk-configuration)。
```diff showLineNumbers
// SubscriptionID
val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver {
override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) {
- val params = AdaptyProfileParameters.Builder()
- .withOneSignalSubscriptionId(state.current.id)
- .build()
-
- Adapty.updateProfile(params) { error ->
+ Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error ->
if (error != null) {
// Handle the error
}
}
}
}
```
```diff showLineNumbers
// SubscriptionID
IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> {
- AdaptyProfileParameters params = new AdaptyProfileParameters.Builder()
- .withOneSignalSubscriptionId(state.getCurrent().getId())
- .build();
- Adapty.updateProfile(params, error -> {
+ Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> {
if (error != null) {
// Handle the error
}
});
};
```
```diff showLineNumbers
// PlayerID
val osSubscriptionObserver = OSSubscriptionObserver { stateChanges ->
stateChanges?.to?.userId?.let { playerId ->
- val params = AdaptyProfileParameters.Builder()
- .withOneSignalPlayerId(playerId)
- .build()
-
- Adapty.updateProfile(params) { error ->
+ Adapty.setIntegrationIdentifier("one_signal_player_id", playerId) { error ->
if (error != null) {
// Handle the error
}
- }
}
}
```
```diff showLineNumbers
// PlayerID
OSSubscriptionObserver osSubscriptionObserver = stateChanges -> {
OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null;
String playerId = to != null ? to.getUserId() : null;
if (playerId != null) {
- AdaptyProfileParameters params1 = new AdaptyProfileParameters.Builder()
- .withOneSignalPlayerId(playerId)
- .build();
-
- Adapty.updateProfile(params1, error -> {
+ Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> {
if (error != null) {
// Handle the error
}
- });
}
};
```
### Pushwoosh
按以下方式更新您的移动应用代码。完整代码示例请参阅 [Pushwoosh 集成的 SDK 配置](pushwoosh#sdk-configuration)。
```diff showLineNumbers
- val params = AdaptyProfileParameters.Builder()
- .withPushwooshHwid(Pushwoosh.getInstance().hwid)
- .build()
- Adapty.updateProfile(params) { error ->
+ Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error ->
if (error != null) {
// Handle the error
}
}
```
```diff showLineNumbers
- AdaptyProfileParameters params = new AdaptyProfileParameters.Builder()
- .withPushwooshHwid(Pushwoosh.getInstance().getHwid())
- .build();
-
- Adapty.updateProfile(params, error -> {
+ Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> {
if (error != null) {
// Handle the error
}
});
```
---
# File: migration-to-android-sdk-v3
---
---
title: "迁移 Adapty Android SDK 至 v3.0"
description: "迁移至 Adapty Android SDK v3.0,享受更优性能与全新变现功能。"
---
Adapty SDK v3.0 带来了重大改进,在迁移之前,请务必了解以下关键变更。
## 更换弃用方法 \{#replace-deprecated-methods\}
### activateAdapty → Adapty.activate \{#activateadapty--adaptyadapty\}
`activateAdapty` 函数已被弃用,请改用 `Adapty.activate`。
```kotlin
override fun onCreate() {
super.onCreate()
Adapty.activate(
applicationContext,
AdaptyConfig.Builder("PUBLIC_SDK_KEY")
.withObserverMode(false)
.withCustomerUserId(customerUserId)
.withIpAddressCollectionDisabled(false)
.build()
)
}
```
```kotlin
override fun onCreate() {
super.onCreate()
Adapty.activate(applicationContext, "PUBLIC_SDK_KEY", observerMode = false, customerUserId = "YOUR_USER_ID")
}
```
### getPaywalls → getPaywall \{#getpaywalls--getpaywall\}
`getPaywalls` 函数已被弃用,请改用 `getPaywall`。
```kotlin
Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result ->
when (result) {
is AdaptyResult.Success -> {
val paywall = result.value
// 使用付费墙
}
is AdaptyResult.Error -> {
val error = result.error
// 处理错误
}
}
}
```
```kotlin
Adapty.getPaywalls(forceUpdate = false) { result ->
when (result) {
is AdaptyResult.Success -> {
val paywalls = result.value.paywalls
// 使用付费墙
}
is AdaptyResult.Error -> {
val error = result.error
// 处理错误
}
}
}
```
### getPaywallProducts → getPaywallProducts \{#getpaywallproducts--getpaywallproducts\}
`getPaywallProducts` 函数签名已更新。
```kotlin
Adapty.getPaywallProducts(paywall) { result ->
when (result) {
is AdaptyResult.Success -> {
val products = result.value
// 使用产品
}
is AdaptyResult.Error -> {
val error = result.error
// 处理错误
}
}
}
```
```kotlin
Adapty.getPaywallProducts(paywall) { result ->
when (result) {
is AdaptyResult.Success -> {
val products = result.value
// 使用产品
}
is AdaptyResult.Error -> {
val error = result.error
// 处理错误
}
}
}
```
## 更新后的模型 \{#updated-models\}
### AdaptyPaywall \{#adaptypaywall\}
`AdaptyPaywall` 模型已更新,包含以下变更:
| 属性 | v2.x | v3.0 |
|------|------|------|
| `name` | 已弃用 | 已移除 |
| `variationId` | 保留 | 保留 |
| `revision` | 保留 | 保留 |
| `isPromo` | 已弃用 | 已移除 |
### AdaptyProduct \{#adaptyprodcut\}
`AdaptyProduct` 模型已更新,包含以下变更:
| 属性 | v2.x | v3.0 |
|------|------|------|
| `variationId` | 保留 | 已移除(移至 `AdaptyPaywall`)|
| `paywallName` | 保留 | 已移除 |
| `paywallABTestName` | 保留 | 已移除 |
## 移除的功能 \{#removed-functionality\}
以下功能已在 v3.0 中移除:
- **Promo campaigns**:不再支持促销活动功能
- **getPromo**:`getPromo` 方法已移除
- **setExternalAnalyticsEnabled**:此方法已移除,请参阅[分析集成文档](analytics-integration)了解替代方案
## 迁移步骤 \{#migration-steps\}
1. 将 `build.gradle` 中的 SDK 版本更新至 `3.0.0`
2. 将所有 `activateAdapty` 调用替换为 `Adapty.activate`,并使用新的 `AdaptyConfig.Builder`
3. 将所有 `getPaywalls` 调用替换为 `getPaywall`,并传入版位 ID
4. 移除所有对已弃用属性的引用
5. 测试所有购买流程,确保功能正常
如需获取完整的迁移支持,请联系 [Adapty 支持团队](https://adapty.io/contact)。
Adapty SDK v3.0 带来了全新的 [Adapty 付费墙编辑工具](adapty-paywall-builder),这是用于创建付费墙的全新无代码工具,操作简单、功能强大。凭借其极高的灵活性和丰富的设计能力,你的付费墙将变得更加高效、更具盈利潜力。
Adapty SDK 以 BoM(Bill of Materials)方式分发,确保应用中 Adapty SDK 与 AdaptyUI SDK 的版本始终保持一致。
如需迁移至 v3.0,请按以下步骤更新代码:
```diff showLineNumbers
dependencies {
...
- implementation 'io.adapty:android-sdk:2.11.5'
- implementation 'io.adapty:android-ui:2.11.3'
+ implementation platform('io.adapty:adapty-bom:3.0.4')
+ implementation 'io.adapty:android-sdk'
+ implementation 'io.adapty:android-ui'
}
```
```diff showLineNumbers
dependencies {
...
- implementation("io.adapty:android-sdk:2.11.5")
- implementation("io.adapty:android-ui:2.11.3")
+ implementation(platform("io.adapty:adapty-bom:3.0.4"))
+ implementation("io.adapty:android-sdk")
+ implementation("io.adapty:android-ui")
}
```
```diff showLineNumbers
//libs.versions.toml
[versions]
..
- adapty = "2.11.5"
- adaptyUi = "2.11.3"
+ adaptyBom = "3.0.4"
[libraries]
..
- adapty = { group = "io.adapty", name = "android-sdk", version.ref = "adapty" }
- adapty-ui = { group = "io.adapty", name = "android-ui", version.ref = "adaptyUi" }
+ adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" }
+ adapty = { module = "io.adapty:android-sdk" }
+ adapty-ui = { module = "io.adapty:android-ui" }
//module-level build.gradle.kts
dependencies {
...
+ implementation(libs.adapty.bom)
implementation(libs.adapty)
implementation(libs.adapty.ui)
}
```
---
# End of Documentation
_Generated on: 2026-07-24T13:01:53.291Z_
_Successfully processed: 41/41 files_