如何将 UniConsent CMP SDK 集成到 Android 移动应用

UniConsent CMP 是用于在 Android 应用中处理 GDPR IAB TCF 2.3 同意管理的软件包。您可以在 "demo" 目录中找到集成了 UniConsent CMP 的演示应用。

前提条件

  • 支持移动应用的 UniConsent CMP 方案
  • Android API level 21 或更高版本
  • UniConsent CMP SDK 软件包(请联系支持团队获取)

开始使用

UniConsentSDK-release.aar 添加到项目的 libs/ 目录中,并更新 build.gradle

implementation files('libs/UniConsentSDK-release.aar')
implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'com.google.android.material:material:1.13.0'
implementation 'com.iabtcf:iabtcf-core:2.0.10'
implementation 'com.iabtcf:iabtcf-decoder:2.0.10'

自定义同意界面

在集成 SDK 之前,请在 UniConsent 控制台 中自定义同意横幅的外观,以匹配您的品牌并优化同意率。

第 1 步:在控制台中设置品牌样式

前往 Projects → 选择您的项目 → Settings → Step 5: UI & Style Settings 进行配置:

  • Main Button Colour — 设置主操作按钮颜色以匹配您的品牌
  • Main Button Text Colour — 调整主按钮上的文字颜色以确保可读性
  • Background Colour — 设置横幅背景颜色以融入您的应用
  • Text Colour — 确保正文文字具有适当的对比度

第 2 步:使用自定义 CSS 进行高级样式设置(可选)

如需更精确的控制,请在 Step 5 的 CSS Content 字段中添加自定义 CSS。建议使用此功能让横幅呈现原生应用的观感,以获得最佳同意率:

/* Example: Style the accept button to match your brand */
.unic-btn-accept {
  background-color: #4CAF50;
  border-radius: 8px;
  font-weight: 600;
}

/* Example: Adjust banner font */
.unic-banner {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

/* Example: Make the reject button less prominent */
.unic-btn-reject {
  background-color: transparent;
  border: 1px solid #ccc;
  color: #666;
}

提示: 具有原生应用观感的精心设计的同意界面通常能获得更高的同意率。用户更可能对符合预期外观和风格的横幅做出积极响应。

使用方法

要在应用中使用 UniConsent CMP,请按照以下步骤操作:

使用客户经理提供的 App ID 初始化 CMP:

UniConsent UniConsentCMP = UniConsent.getInstance();
UniConsentCMP.setAppId("YOUR_APP_ID");

显示 CMP 界面:

// Display CMP as full-screen page (default)
UniConsent.getInstance().launchCMP();

// Display CMP as a modal bottom sheet
UniConsent.getInstance().launchCMP(CMPDisplayMode.BOTTOM_SHEET);

// Display CMP as a center dialog
UniConsent.getInstance().launchCMP(CMPDisplayMode.DIALOG);

您还可以使用指定的阶段和显示模式启动:

// Launch at a specific stage with a display mode
UniConsent.getInstance().launchCMP(Stage.GDPRFirstScreen, CMPDisplayMode.BOTTOM_SHEET);

// Bottom sheet at 70% of the screen height
UniConsent.getInstance().launchCMP(Stage.GDPRFirstScreen, CMPDisplayMode.BOTTOM_SHEET, 0.7);

// Center dialog at 80% width and 60% height
UniConsent.getInstance().launchCMP(Stage.GDPRFirstScreen, CMPDisplayMode.DIALOG, 0.8, 0.6);

// Or set a default display mode for all launches
UniConsent.getInstance().setDisplayMode(CMPDisplayMode.BOTTOM_SHEET);
UniConsent.getInstance().launchCMP();

自定义尺寸(可选)

自 SDK 26.8.0 版本起可用。

两个比例参数均为可选,所有显示模式不传它们也能正常工作。仅在需要覆盖默认尺寸时才传入比例(相对屏幕的比例,需大于 0 且不超过 1);无效值会抛出 IllegalArgumentException

参数适用于省略时的默认值
heightRatioBOTTOM_SHEETDIALOG面板:屏幕的 90%。对话框:70%
widthRatioDIALOG屏幕宽度的 90%,最大 500dp

不适用于所选显示模式的比例会被忽略(FULL_SCREEN 会忽略两者)。

当 vendorList 更新时自动检查同意是否过期:

// Check if consent should be requested (e.g. first visit, or vendor list updated)
if (UniConsentCMP.shouldRequestConsent()) {
    UniConsentCMP.launchCMP();
}

获取 tcString(如需要):

// Get tcString
UniConsent.getInstance().getTCString();

读取同意状态:

// Read consent status
UniConsent.getInstance().hasIABPurposeConsent(1);
UniConsent.getInstance().hasIABVendorConsent(1);

重置同意状态(如需要):

// Reset consent status
UniConsent.getInstance().clearData();

自定义首层界面(全部同意 / 全部拒绝)

自 SDK 26.7.0 版本起可用。如果您构建自己的首层同意界面而不显示 CMP webview,可以直接保存同意状态:

// Grant consent for all purposes, special features and vendors
boolean saved = UniConsent.getInstance().agreeAll();

// Reject all purposes, special features and vendors
boolean saved = UniConsent.getInstance().rejectAll();

这两个 API 保存与 CMP UI 流程相同的同意值(IAB TCF 键、附加同意和同意模式信号),并向订阅者发送相同的关闭事件。

注意: 请仅在 init() 完成后(例如 READY 事件之后)调用这些 API。它们依赖初始化期间获取的供应商列表和项目配置,如果无法保存同意状态则返回 false

重要: 您的自定义首层界面仍须满足 IAB TCF 要求(披露目的和供应商,并以同等显著程度提供接受和拒绝选项)。用户仍应能够打开完整的 CMP 界面(launchCMP())进行精细选择。

将同意状态同步到 WebView

如果您的应用打开了加载带有 UniConsent CMP 标签的网页的 WebView,您可以传递原生同意状态,这样 CMP 标签会识别已有的同意记录,不会再次显示横幅。

在页面开始加载时、CMP 标签运行之前注入同意状态:

WebView webView = findViewById(R.id.webView);
webView.getSettings().setJavaScriptEnabled(true);

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageStarted(WebView view, String url, android.graphics.Bitmap favicon) {
        UniConsent.getInstance().syncConsent(view);
    }
});

webView.loadUrl("https://example.com");

使用 AppCompatActivity 的示例

import com.uniconsent.sdk.CMPDisplayMode;
import com.uniconsent.sdk.Event;
import com.uniconsent.sdk.EventHandler;
import com.uniconsent.sdk.Stage;
import com.uniconsent.sdk.UniConsent;

public class MainActivity extends AppCompatActivity implements EventHandler {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        UniConsent UniConsentCMP = UniConsent.getInstance();
        UniConsentCMP.setAppId("YOUR_APP_ID");
        UniConsentCMP.init(this);
        // register callback events
        UniConsentCMP.subscribe(this);
        // check if consent should be requested
        if (UniConsentCMP.shouldRequestConsent()) {
            UniConsentCMP.launchCMP();
        }
    }

    public void openUniConsentUI(View view) {
        // Full screen
        UniConsent.getInstance().launchCMP(Stage.GDPRFirstScreen);
    }

    public void openAsBottomSheet(View view) {
        // Bottom sheet
        UniConsent.getInstance().launchCMP(Stage.GDPRFirstScreen, CMPDisplayMode.BOTTOM_SHEET);
    }

    public void openAsDialog(View view) {
        // Center dialog
        UniConsent.getInstance().launchCMP(Stage.GDPRFirstScreen, CMPDisplayMode.DIALOG);
    }

    @Override
    public void handle(Event event) {
        UniConsent.getInstance().hasIABVendorConsent(1);
    }
}

使用 ComponentActivity 的示例

package com.example.myapplication

import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.example.myapplication.ui.theme.MyApplicationTheme
import com.uniconsent.sdk.CMPDisplayMode
import com.uniconsent.sdk.Event
import com.uniconsent.sdk.EventHandler
import com.uniconsent.sdk.Stage
import com.uniconsent.sdk.UniConsent

class MainActivity : ComponentActivity(), EventHandler {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val cmp = UniConsent.getInstance()
        cmp.appId = "YOUR_APP_ID"
        cmp.init(this)
        cmp.subscribe(this)

        if (cmp.shouldRequestConsent()) {
             cmp.launchCMP();
        }
        setContent {
            MyApplicationTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(16.dp),
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        Button(onClick = {
                            UniConsent.getInstance().launchCMP(Stage.GDPRFirstScreen)
                        }) {
                            Text("Privacy Settings")
                        }
                        Spacer(modifier = Modifier.height(16.dp))
                        Button(onClick = {
                            UniConsent.getInstance().launchCMP(
                                Stage.GDPRFirstScreen, CMPDisplayMode.BOTTOM_SHEET
                            )
                        }) {
                            Text("Privacy Settings (Bottom Sheet)")
                        }
                        Spacer(modifier = Modifier.height(16.dp))
                        Button(onClick = {
                            UniConsent.getInstance().launchCMP(
                                Stage.GDPRFirstScreen, CMPDisplayMode.DIALOG
                            )
                        }) {
                            Text("Privacy Settings (Dialog)")
                        }
                    }
                }
            }
        }
    }

    override fun handle(event: Event?) {
        Log.d("CMP_EVENT", event.toString())
        UniConsent.getInstance().hasIABVendorConsent(1)
    }
}

权限

<uses-permission android:name="android.permission.INTERNET" />

设置默认同意状态键值对:

<meta-data android:name="firebase_analytics_collection_enabled" android:value="false" />

<meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="false" />
<meta-data android:name="google_analytics_default_allow_ad_storage" android:value="false" />
<meta-data android:name="google_analytics_default_allow_ad_user_data" android:value="false" />
<meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="false" />

根据同意标志控制分析:


// <= 25.6.1
override fun handle(event: Event?) {
    Log.d("CMP_EVENT", event.toString())
    if(UniConsent.getInstance().hasIABPurposeConsent(1)) {
        // Set consent types.
        Map<FirebaseAnalytics.ConsentType, FirebaseAnalytics.ConsentStatus> consentMap = new EnumMap<>(FirebaseAnalytics.ConsentType.class);
        consentMap.put(FirebaseAnalytics.ConsentType.ANALYTICS_STORAGE, FirebaseAnalytics.ConsentStatus.GRANTED);
        consentMap.put(FirebaseAnalytics.ConsentType.AD_STORAGE, FirebaseAnalytics.ConsentStatus.GRANTED);
        consentMap.put(FirebaseAnalytics.ConsentType.AD_USER_DATA, FirebaseAnalytics.ConsentStatus.GRANTED);
        consentMap.put(FirebaseAnalytics.ConsentType.AD_PERSONALIZATION, FirebaseAnalytics.ConsentStatus.GRANTED);

        mFirebaseAnalytics.setConsent(consentMap);
        mFirebaseAnalytics.setAnalyticsCollectionEnabled(true);
    } else {
        // Set consent types.
        Map<FirebaseAnalytics.ConsentType, FirebaseAnalytics.ConsentStatus> consentMap = new EnumMap<>(FirebaseAnalytics.ConsentType.class);
        consentMap.put(FirebaseAnalytics.ConsentType.ANALYTICS_STORAGE, FirebaseAnalytics.ConsentStatus.DENIED);
        consentMap.put(FirebaseAnalytics.ConsentType.AD_STORAGE, FirebaseAnalytics.ConsentStatus.DENIED);
        consentMap.put(FirebaseAnalytics.ConsentType.AD_USER_DATA, FirebaseAnalytics.ConsentStatus.DENIED);
        consentMap.put(FirebaseAnalytics.ConsentType.AD_PERSONALIZATION, FirebaseAnalytics.ConsentStatus.DENIED);

        mFirebaseAnalytics.setConsent(consentMap);
        mFirebaseAnalytics.setAnalyticsCollectionEnabled(false);
    }

    // other logics such as send analytics events
}

// > 25.6.1
override fun handle(event: Event?) {
    Log.d("CMP_EVENT", event.toString())

    // other logics such as send analytics events

    // Example: send Firebase event or other analytics metrics
    Bundle bundle = new Bundle();
    bundle.putString(FirebaseAnalytics.Param.ITEM_ID, "id");
    bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, "name");
    bundle.putString(FirebaseAnalytics.Param.CONTENT_TYPE, "image");
    mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);
}

更多信息请参阅 为应用设置 Consent Mode

显示模式

SDK 支持三种同意界面显示模式,与 Flutter SDK API 一致:

模式枚举值说明
全屏CMPDisplayMode.FULL_SCREEN在新的全屏 Activity 中打开 CMP(默认)
底部弹出CMPDisplayMode.BOTTOM_SHEET从底部滑出的模态弹窗(85% 高度)
对话框CMPDisplayMode.DIALOG居中显示的对话框(90% 宽度,75% 高度)

注意: 底部弹出和对话框模式要求您的 Activity 继承 AppCompatActivityFragmentActivity

注意事项

  1. 用户应该能够在应用的设置部分访问"隐私设置"按钮或链接,以便打开 CMP 界面。
  2. 您可以使用 shouldRequestConsent() 函数来检查是否需要根据状态请求新的同意。在用户打开应用时,根据需要显示 CMP 界面。
  3. 从 SDK 版本 25.6.1 开始,您不再需要手动为 FirebaseAnalytics 或某些受支持的 AAP SDK 发送同意选项——SDK 会自动处理。 更多信息请参阅什么是 Google AAP 和 Google MMP(移动应用)?