Capacitor 主页

如何在 Capacitor 中处理 AdMob GDPR 许可

在 Capacitor 应用程序中处理 AdMob GDPR 许可:使用 Google 的 UMP SDK: 许可表单、隐私选项、ATT 顺序、调试地理位置和错误处理,使用 code。

文章来源

马丁·多纳迪尤

作者

瓦莱里亚

审阅者

乔丹

编辑

如何在 Capacitor 中处理 AdMob GDPR 许可

要在 Capacitor 应用程序中处理 AdMob GDPR 许可,请使用 Google 的用户消息平台 (UMP) SDK: 在每次启动时请求许可信息,显示许可表单,如果需要,则只初始化广告 canRequestAds 当 is 为 true 时。您还需要一个隐私选项入口点,以便用户可以更改他们的选择,并且在 iOS 上,UMP 流程应在 App Tracking Transparency 提示之前。这个指南展示了两个设置:一个将 UMP 包装在 JavaScript 中的插件,以及一个小型本机许可桥 @capgo/capacitor-admob 使用一个小型本地同意桥梁。

什么是 UMP,为什么 AdMob 需要它

自 2024 年 1 月起,Google 需要发布广告的出版商在欧洲经济区(EEA)、英国和瑞士使用 Google 批准的同意管理平台(CMP)和 IAB 透明度和同意框架(TCF)集成的 CMP。UMP SDK 是 Google 自己的批准 CMP,免费且内置于 AdMob 控制台中。

UMP 为您做三件事:

  1. 检测用户是否位于需要同意的地区。
  2. 在用户的语言中显示您在 AdMob 中配置的同意消息。
  3. 将用户的选择存储为设备上的 TC 字符串(标准 IABTCF_ keys 在 UserDefaults iOS 上和 SharedPreferences Android 上),Google Mobile Ads SDK 和中介合作伙伴在请求广告时读取。

您还可以使用 UMP 来处理美国州的隐私法规,AdMob 称之为“美国州”消息,从同一个控制台中。

步骤 1:在 AdMob 中创建消息

context 隐私 & 通知:

  • 创建一个 欧洲法规 消息 (GDPR)。选择它适用的应用程序、语言和广告合作伙伴列表。
  • 可选创建一个 美国州 消息。
  • 在 iOS 上,创建一个 IDFA 解释 消息,如果您计划请求 App Tracking Transparency。UMP 会在系统 ATT 提示之前显示它。
  • 发布消息。未发布的消息永远不会显示。

如果表单在您的应用程序中永远不会出现,未发布或未分配的消息是首先要检查的。

第 2 步:确定操作顺序

大多数应用程序都适用的顺序是:

  1. 应用程序启动
  2. 每次启动时使用 UMP 请求同意信息更新
  3. 如果需要,请加载并显示同意表单
  4. 如果 canRequestAds 是 true,则启动移动广告 SDK 并加载广告
  5. 在 UMP 表示隐私选项需要时,在设置屏幕上显示一个“隐私设置”条目

Google 还允许在同意请求时并行启动移动广告 SDK,当 canRequestAds 从前一次会话中已经为 true 时,这可以缩短返回用户的首次广告时间

选项 A:将 UMP 包装在 JavaScript 中的插件

@capacitor-community/admob Capacitor requestConsentInfo, showConsentForm, showPrivacyOptionsForm 和 resetConsentInfo.

bun add @capacitor-community/admob
bunx cap sync
import {
  AdMob,
  AdmobConsentStatus,
  AdmobConsentDebugGeography,
} from '@capacitor-community/admob';

export async function initAdsWithConsent(isDebug: boolean) {
  await AdMob.initialize();

  let info = await AdMob.requestConsentInfo(
    isDebug
      ? {
          debugGeography: AdmobConsentDebugGeography.EEA,
          testDeviceIdentifiers: ['YOUR_TEST_DEVICE_ID'],
        }
      : undefined,
  );

  if (info.isConsentFormAvailable && info.status === AdmobConsentStatus.REQUIRED) {
    info = await AdMob.showConsentForm();
  }

  // The enum value is the string 'REQUIRED'
  const showPrivacyEntry = String(info.privacyOptionsRequirementStatus) === 'REQUIRED';

  return { canRequestAds: info.canRequestAds, showPrivacyEntry };
}

export async function openPrivacyOptions() {
  await AdMob.showPrivacyOptionsForm();
}

本插件是上述顺序中的唯一例外:其README要求 AdMob.initialize() 在 requestConsentInfo(),并且在iOS上,直到 initialize() 已经运行。 initialize() 启动SDK,但不请求广告,因此关注的规则是直到 canRequestAds SDK AdMob.start().

Check canRequestAds 为真之前加载广告。如果您需要__CAPGO_KEEP_0__保持停止状态直到用户回答,请使用选项B,whereconsent在

选项 B:@capgo/capacitor-admob 使用原生 UMP 桥

@capgo/capacitor-admob 检查 AdMob.start()在每次广告加载之前,仅在启动时不够,因为用户可以从隐私选项表单中撤回同意。 AdMob.start().

bun add @capgo/capacitor-admob
bunx cap sync

添加 UMP SDK

  • iOS: 添加 Google 的 GoogleUserMessagingPlatform 使用 Swift Package Manager (Capacitor 默认情况下有 8 个项目使用 SPM) 或 CocoaPods 中相同名称的 pod,如果您的项目仍然使用 CocoaPods。
  • Android: 在 android/app/build.gradle:
dependencies {
    implementation "com.google.android.ump:user-messaging-platform:<latest version>"
}

使用 Google Maven 仓库的最新版本。

创建 ios/App/App/ConsentPlugin.swift:

import Capacitor
import UserMessagingPlatform

@objc(ConsentPlugin)
public class ConsentPlugin: CAPPlugin, CAPBridgedPlugin {
    public let identifier = "ConsentPlugin"
    public let jsName = "Consent"
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "gather", returnType: CAPPluginReturnPromise),
        CAPPluginMethod(name: "showPrivacyOptions", returnType: CAPPluginReturnPromise),
    ]

    @objc func gather(_ call: CAPPluginCall) {
        let parameters = RequestParameters()
        DispatchQueue.main.async {
            ConsentInformation.shared.requestConsentInfoUpdate(with: parameters) { error in
                if let error = error {
                    call.reject(error.localizedDescription)
                    return
                }
                guard let vc = self.bridge?.viewController else {
                    call.reject("No view controller")
                    return
                }
                ConsentForm.loadAndPresentIfRequired(from: vc) { formError in
                    if let formError = formError {
                        call.reject(formError.localizedDescription)
                        return
                    }
                    call.resolve(self.state())
                }
            }
        }
    }

    @objc func showPrivacyOptions(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            guard let vc = self.bridge?.viewController else {
                call.reject("No view controller")
                return
            }
            ConsentForm.presentPrivacyOptionsForm(from: vc) { formError in
                if let formError = formError {
                    call.reject(formError.localizedDescription)
                    return
                }
                call.resolve(self.state())
            }
        }
    }

    private func state() -> [String: Any] {
        return [
            "canRequestAds": ConsentInformation.shared.canRequestAds,
            "privacyOptionsRequired":
                ConsentInformation.shared.privacyOptionsRequirementStatus == .required,
        ]
    }
}

从一个 CAPBridgeViewController 子类中注册它,并在视图控制器上设置该类 Main.storyboard:

import Capacitor

class MainViewController: CAPBridgeViewController {
    override open func capacitorDidLoad() {
        bridge?.registerPluginInstance(ConsentPlugin())
    }
}

创建 android/app/src/main/java/com/example/app/ConsentPlugin.java (使用您的包名):

package com.example.app;

import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;
import com.google.android.ump.ConsentInformation;
import com.google.android.ump.ConsentRequestParameters;
import com.google.android.ump.UserMessagingPlatform;

@CapacitorPlugin(name = "Consent")
public class ConsentPlugin extends Plugin {

    @PluginMethod
    public void gather(PluginCall call) {
        ConsentInformation info = UserMessagingPlatform.getConsentInformation(getContext());
        ConsentRequestParameters params = new ConsentRequestParameters.Builder().build();

        getActivity().runOnUiThread(() ->
            info.requestConsentInfoUpdate(
                getActivity(),
                params,
                () -> UserMessagingPlatform.loadAndShowConsentFormIfRequired(getActivity(), formError -> {
                    if (formError != null) {
                        call.reject(formError.getMessage());
                        return;
                    }
                    call.resolve(state(info));
                }),
                requestError -> call.reject(requestError.getMessage())
            )
        );
    }

    @PluginMethod
    public void showPrivacyOptions(PluginCall call) {
        getActivity().runOnUiThread(() ->
            UserMessagingPlatform.showPrivacyOptionsForm(getActivity(), formError -> {
                if (formError != null) {
                    call.reject(formError.getMessage());
                    return;
                }
                call.resolve(state(UserMessagingPlatform.getConsentInformation(getContext())));
            })
        );
    }

    private JSObject state(ConsentInformation info) {
        JSObject ret = new JSObject();
        ret.put("canRequestAds", info.canRequestAds());
        ret.put(
            "privacyOptionsRequired",
            info.getPrivacyOptionsRequirementStatus() ==
                ConsentInformation.PrivacyOptionsRequirementStatus.REQUIRED
        );
        return ret;
    }
}

在 MainActivity.java 之前 super.onCreate:

public class MainActivity extends BridgeActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        registerPlugin(ConsentPlugin.class);
        super.onCreate(savedInstanceState);
    }
}

context: 在Capgo UI中创建一个问题并讨论在开发新功能之前

import { registerPlugin } from '@capacitor/core';
import { AdMob, BannerAd } from '@capgo/capacitor-admob';

interface ConsentState {
  canRequestAds: boolean;
  privacyOptionsRequired: boolean;
}

interface ConsentPlugin {
  gather(): Promise<ConsentState>;
  showPrivacyOptions(): Promise<ConsentState>;
}

const Consent = registerPlugin<ConsentPlugin>('Consent');

export async function startAds() {
  let state: ConsentState;
  try {
    state = await Consent.gather();
  } catch (err) {
    console.warn('Consent flow failed', err);
    return { showPrivacyEntry: false };
  }

  if (state.canRequestAds) {
    await AdMob.start();
    const banner = new BannerAd({
      adUnitId: 'ca-app-pub-3940256099942544/2934735716', // Google test banner (iOS)
      position: 'bottom',
    });
    await banner.show();
  }

  return { showPrivacyEntry: state.privacyOptionsRequired };
}

export const openPrivacyOptions = () => Consent.showPrivacyOptions();

原生code变化需要重新构建应用商店。之后,用户同意复制到设置屏幕,广告插入逻辑可以与应用一起发布。 Capgo 实时更新.

原生__CAPGO_KEEP_0__更改需要一个新的商店构建。之后,Consent复制到您的设置屏幕和广告放置逻辑可以通过

context: Capgo营销网站。角色:按钮或链接标签。见:页面agents/[slug].astro。保留Capgo产品/品牌和开发者术语的原始形式。

const { showPrivacyEntry } = await startAds();
document.querySelector('#privacy-row')?.toggleAttribute('hidden', !showPrivacyEntry);
document.querySelector('#privacy-row')?.addEventListener('click', () => openPrivacyOptions());

步骤3:添加隐私选项入口点 canRequestAds context: 关于Capgo页面。角色:UI标签。见:页面about.astro。

步骤 4:iOS App 跟踪透明度

ATT与GDPR是不同的。ATT控制iOS设备上的IDFA访问权限。GDPR同意决定个人数据是否可以处理以及如何处理。通常,需要在iOS设备上为欧盟用户获取两者。

  • 添加 NSUserTrackingUsageDescription 到 Info.plist.
  • 让UMP显示IDFA解释消息并触发ATT,如果您在AdMob中配置了该消息。否则,在同意流程之后请求ATT。

With @capgo/capacitor-admob __CAPGO_KEEP_0__也具有独立的

import { AdMob, TrackingAuthorizationStatus } from '@capgo/capacitor-admob';

const { status } = await AdMob.trackingAuthorizationStatus();
if (status === TrackingAuthorizationStatus.notDetermined) {
  await AdMob.requestTrackingAuthorization();
}

Capgo 也有一个独立的 如果您需要ATT其他SDK,请不要忘记在隐私清单和App Store隐私标签中声明跟踪,见我们的 隐私清单指南 第5步:使用调试地理位置进行测试.

UMP仅在EEA、英国或瑞士用户面前显示GDPR表单。要测试其他地区:

UMP仅在EEA、英国或瑞士用户面前显示GDPR表单。要测试其他地区,请使用调试地理位置

  • 运行应用程序一次,找到UMP中与您的设备的哈希ID相关的日志行。
  • 将其添加为测试设备,并将调试地理设置为EEA。使用社区插件,传递 debugGeography 并 testDeviceIdentifiers 到 requestConsentInfo. 在本机桥中,设置 DebugSettings 在iOS上和 ConsentDebugSettings 在Android上(仅限调试构建)。
  • 在运行之间重置同意(resetConsentInfo() 在社区插件中 ConsentInformation.reset() 本机)以便表单再次显示。

不要在发布构建中发送调试设置。将它们门控在一个构建标志上。

第6步:在应用程序中处理错误而不阻塞应用程序

请求许可可能会失败:无网络、配置错误的应用 ID 或未发布的消息。将失败视为“暂时不显示广告”,而不是崩溃:

  • 如果 requestConsentInfoUpdate 失败但 canRequestAds 之前会显示的,则仍可以请求广告。
  • 如果表单无法加载,请记录日志并在下次启动时重试。
  • 在显示许可表单时,不要显示广告。

故障排除

上下文:支持/高级支持页面或底部支持部分。角色:部分或页面标题。见于:页面 support-policy.astro。消息键 `support_policy_troubleshooting_title` (支持政策故障排除标题)。 表单永远不会显示。 Info.plist and AndroidManifest.xml 和

上下文:Capgo UI 中的 HTML 文本片段(父键 `compare_step_capacitor_text`)。页面/区域:Capgo 市场网站。角色:网站副本句子。见于:页面 alternatives/expo.astro,页面 alternatives/median.astro。消息键 `compare_step_capacitor_text` (Capacitor 比较步骤文本)。| 页面/区域:Capgo 市场网站。角色:短 UI 标签或导航项。见于:页面 trust.astro。消息键 `and` (和)。 用户关闭了它,没有选择,或者你在生产环境中调用了重置 code。

canRequestAds 对于美国用户,值为 false。 您发布了一个美国州的消息,用户选择了不出售或共享。通过 SDK 仅服务有限广告,不要绕过它。

媒体合作伙伴没有获得同意。 确保每个合作伙伴适配器都是最新的,并且在您的 GDPR 消息的广告合作伙伴列表中列出。

iOS ATT 提示永远不会出现。 NSUserTrackingUsageDescription 缺少了“允许应用程序请求跟踪”设置中的选项,或者用户在设置中禁用了“允许应用程序请求跟踪”。

形式之外的合规性

UMP 处理广告侧。您的应用程序仍然需要一个隐私政策,列出 AdMob、数据保留规则和处理数据请求的方式。我们的 GDPR 合规性清单 涵盖了应用程序的其余部分。

实时更新 Capacitor 应用

当 Web 层 Bug 活跃时,通过 Capgo 直接将修复推送给用户,而不是等待几天的 App Store 审核。用户在后台接收更新,而原生变化仍在正常审查路径中。

最新博客

Capgo 为您提供创建真正专业的移动应用所需的最佳见解。