如何将 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);

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

当 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();

将同意状态同步到 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(移动应用)?