Logo
Published on
·11 min read

애드몹(AdMob) GDPR 적용 - React Native(안드로이드)

개요

애드몹(AdMob)에서 계속 GDPR 메시지를 게시하라고 경고를 보여줍니다.

애드센스에서의 GDPR 메시지 작성은 간단했기에 애드몹도 비슷하게 적용할 수 있을 것이라고 생각했습니다.

하지만, 애드몹에서는 GDPR 메시지 작성 외에, 직접 어플리케이션에 동의 여부를 확인하는 코드 작성이 필요했습니다. 😱

소소한 단어장 앱에 적용한 내용을 정리해봅니다.

Android로 구현한 내용은 애드몹(AdMob) GDPR 적용 - 안드로이드 에서 확인할 수 있습니다.

🛑 주의

❗ 법적인 책임을 지지 않습니다. ❗

구글 문서와 샘플 코드 및 검색등을 이용하여 구현하였습니다. 하지만 정책이나 구현에 대해 잘못된 해석이 있을 수 있습니다. 구현에 대해 참고만 하시길 바랍니다.

테스트 기기에서, 테스트 모드로 진행시에는 정상 동작하는 것을 확인했지만 EEA 지역에서 실제 기기의 동작은 확인하지 못했습니다. 또한 iOS에서 동작은 확인 하지 못했습니다.

GDPR이란?

2018년 5월 25일부터 시행되고있는 EU(유럽연합)의 개인정보보호 법령으로 위반시 과징금 등 행정처분이 부과될 수 있으며, EU내 사업장이 없더라도 EU를 대상으로 사업을 하는 경우 적용대상이 될 수 있어 우리 기업의 주의가 필요함

KISA, GDPR 소개

GDPR 메시지 설정

Google AdMob > GDPR > [메시지 만들기] 버튼을 클릭합니다.

설정은 본인 앱에 맞게 설정하시길 바랍니다. 제 설정은 아래와 같습니다.

메시지 만들기
  • 내 앱: 메시지를 적용할 앱을 선택했습니다.
  • 언어: 메시지를 표시할 언어를 '영어'로 지정했습니다.
  • 동의하지 않음: 사용
  • 닫기(동의하지 않음): 사용 안함

메시지를 만들고 게시하고, 코드 구현을 해야 설정한 GDPR 메시지가 표시됩니다.

결과 화면

테스트 모드로 진행하면서 확인한 화면입니다.

왼쪽은 앱 실행시 표시된 메시지입니다.
오른쪽은 설정에서 GDPR 설정을 변경할 수 있도록 Change GDPR Settings 버튼을 추가했습니다. 해당 버튼 클릭시 왼쪽 이미지와 같은 메시지 화면이 표시됩니다.

결과 화면

구현

1. proguard-rules.pro 수정

React Native Google Mobile Ads - European User Consent의 안내대로 android/app/proguard-rules.pro 파일에 아래 내용을 추가합니다.

-keep class com.google.android.gms.internal.consent_sdk.** { *; }

2. 앱 측정 지연

app.json 파일의 react-native-google-mobile-ads 설정에 delay_app_measurement_init 값을 true로 설정합니다.

{
  "react-native-google-mobile-ads": {
    "android_app_id": "ca-app-pub-xxxxxxxx~xxxxxxxx",
    "ios_app_id": "ca-app-pub-xxxxxxxx~xxxxxxxx",
    "delay_app_measurement_init": true
  }
}

3. 코드

GDPR 메시지 로드를 위한 공통 함수를 작성했습니다.

  • gdprAdsConsent.ts
// https://docs.page/invertase/react-native-google-mobile-ads/european-user-consent
import mobileAds, {AdsConsent, AdsConsentDebugGeography} from 'react-native-google-mobile-ads';

const debugParams = {
  debugGeography: AdsConsentDebugGeography.EEA,
  testDeviceIdentifiers: ['본인의-테스트-디바이스-해시-아이디'],
}

// AdsMob 초기화
function initializeMobileAdsSdk() {
  mobileAds()
    .initialize()
    .then(adapterStatuses => {
      console.log("mobileAds initialize:", adapterStatuses);
    });
}

// 동의 여부 확인
async function checkIsNotAgreement() {
  const {
    activelyScanDeviceCharacteristicsForIdentification,
    applyMarketResearchToGenerateAudienceInsights,
    createAPersonalisedAdsProfile,
    createAPersonalisedContentProfile,
    developAndImproveProducts,
    measureAdPerformance,
    measureContentPerformance,
    selectBasicAds,
    selectPersonalisedAds,
    selectPersonalisedContent,
    storeAndAccessInformationOnDevice,
    usePreciseGeolocationData,
  } = await AdsConsent.getUserChoices();

  return (
    applyMarketResearchToGenerateAudienceInsights === false ||
    createAPersonalisedAdsProfile === false ||
    createAPersonalisedContentProfile === false ||
    developAndImproveProducts === false ||
    measureAdPerformance === false ||
    measureContentPerformance === false ||
    selectBasicAds === false ||
    selectPersonalisedAds === false ||
    selectPersonalisedContent === false ||
    storeAndAccessInformationOnDevice === false ||
    usePreciseGeolocationData === false
  )
}

async function requestConsent() {
  return await AdsConsent.requestInfoUpdate();
  //return await AdsConsent.requestInfoUpdate(debugParams); // 테스트 시에는 debugParams 추가
}

// 최초 로드 및 동의 여부 확인
export async function loadGdprAdsConsent(
) {
  try{
    //await AdsConsent.reset(); // 테스트 시에만 사용
    const data = await requestConsent();

    if(data.isConsentFormAvailable){
      const resultForm = await AdsConsent.loadAndShowConsentFormIfRequired();

      // 이미 동의 또는 동의 거부한 상태일 경우, 동의 거부 상태 확인하여 동의 요청
      if(data.status === 'OBTAINED'){
        const isNotAgreement = await checkIsNotAgreement();

        if(isNotAgreement) {
          await AdsConsent.showForm();
        }
      }

      if(resultForm.canRequestAds === true) {
        initializeMobileAdsSdk()
      }
    } else {
      initializeMobileAdsSdk()
    }
  }
  catch(error) {
    console.log('loadGdprAdsConsent > error: ', error);
    initializeMobileAdsSdk();
  }
}

// Consent Available 확인 (EEA 지역 여부)
export async function checkIsConsentAvailable() {
  try{
    const data = await requestConsent();
    //console.log('checkConsentAvailable > data: ', data);

    return data.isConsentFormAvailable;
  }catch (error) {
    console.log('checkConsentAvailable > error: ', error);
    return false;
  }
}

// 버튼 클릭 및 화면 이동 등 액션에 따라 동의 여부 확인 후 동의 요청
export async function showAdsConsentForm(
  consentCallback?: () => void,
  notConsentCallback?: () => void,
) {
  try{
    const data = await requestConsent();

    if(data.isConsentFormAvailable){
      const isNotAgreement = await checkIsNotAgreement();

      if(isNotAgreement) {
        await AdsConsent.showForm();

        // 동의 결과 재 확인
        const isNotAgreementResult = await checkIsNotAgreement();

        if(isNotAgreementResult) {
          // 동의 거부시 처리
          notConsentCallback && notConsentCallback();
        } else {
          // 동의 완료시 처리
          consentCallback && consentCallback();
        }
      } else {
        // 동의 완료시 처리
        consentCallback && consentCallback();
      }
    }
    else {
      // 동의 폼 없을 시 처리
      consentCallback && consentCallback();
    }
  }catch (error) {
    console.log('showAdsConsentForm > error: ', error);
  }
}

// 설정에서 동의 여부 변경
export async function showPrivacyOptionsForm() {
  try{
    const data = await requestConsent();

    if(data.isConsentFormAvailable){
      await AdsConsent.showPrivacyOptionsForm()
    }
  }catch (error) {
    console.log('showPrivacyOptionsForm > error: ', error);
  }
}
  • 동의 여부 확인(checkIsNotAgreement)의 경우 어떤 동의가 필요한지에 따라 조건을 달리할 수 있습니다.
    • 어떤 조건에서 광고가 정상적으로 나오는지 정확히 알지 못하기 때문에, 저는 Consent 버튼 클릭시 true로 설정 되는 속성을 확인하고 그것으로 동의 여부를 판단했습니다.

4. 테스트 설정

'본인의-테스트-디바이스-해시-아이디' 변경

  1. 터미널에서 아래 명령어를 입력하여 디바이스 해시 아이디를 확인합니다.
    • 로그 중 Use RequestConfiguration.Builder().setTestDeviceIds(Arrays.asList("본인의-테스트-디바이스-해시-아이디")) to get test ads on this device. 부분을 확인하면 됩니다.
adb shell "logcat -d | grep Ads"
  1. 테스트 설정이 적용되도록 debugParams 사용
  • ❗ 테스트 시에만 사용하고, 배포시에는 debugParams를 제거해야 합니다. ❗
async function requestConsent() {
  //return await AdsConsent.requestInfoUpdate();
  return await AdsConsent.requestInfoUpdate(debugParams); // 테스트 시에는 debugParams 추가
}

5. 적용

  1. 앱 로드시 GDPR 로드 및 동의하지 않은 경우 동의 요청
// App.tsx
import React, {useEffect} from 'react';
import AppNavigator from './navigation/AppNavigator';
import {loadGdprAdsConsent} from './api/gdprAdsConsent';

export default function App() {

  useEffect(() => {
    loadGdprAdsConsent().then(() => {
      console.log("loadGdprAdsConsent completed");
    }).catch((err) => {
      console.log("loadGdprAdsConsent err: ", err);
    });
  }, []);

  return (
    <AppNavigator/>
  );
}
  1. "설정", "그룹 생성" 버튼 클릭 시 비동의 상태일 경우 동의 요청
  // 그룹 생성 버튼 클릭 시
  const onClickNewGroup = () => {
    showAdsConsentForm(() => {
      GlobalModal.showPopup(<GroupCreateModal refresh={refresh} />, t('create_group'))
    }).then(() => {})
  }
  // 설정으로 이동 시
  const goToSetting = () => {
    showAdsConsentForm(() => {
      navigation.navigate('Setting');
    }).then(() => {})
  }
  1. 설정에서 동의 여부 변경할 수 있도록 설정 화면에 버튼 추가
export const SettingScreen = () => {
  const [isConsentAvailable, setIsConsentAvailable] = useState(false);

  useEffect(() => {
    checkIsConsentAvailable().then((isAvailable) => {
      setIsConsentAvailable(isAvailable);
    })
  }, []);

  return (
    <View style={globalStyles.container}>
      {isConsentAvailable &&
      <TouchableOpacity style={styles.listContainer} onPress={() => showPrivacyOptionsForm()}>
        <AppText style={styles.listTitle}>Change GDPR Settings</AppText>
      </TouchableOpacity>}
    </View>
  );
}
  • GDPR 메시지 로드가 안된다면?

    • Google AdMob 사이트에서, 해당 앱에 대해 GDPR을 게시했는 지 확인하세요.
    • '본인의-테스트-디바이스-해시-아이디'가 맞는 지 확인하세요.
  • 테스트 모드 ↔ 테스트 모드가 아닐 때를 전환하며 테스트 할 때 이전 설정이 남아있어서 테스트가 안될 경우

    • 테스트 모드: AdsConsent.requestInfoUpdate(debugParams); 사용, await AdsConsent.reset(); 주석 풀어 설정을 초기화 후, 다시 주석 막기
    • 비 테스트 모드: AdsConsent.requestInfoUpdate(); 사용, await AdsConsent.reset(); 주석 풀어 설정을 초기화 후, 다시 주석 막기
  • 로드 되지 않아야 할때 로드 되거나, 로드 되야하는데 로드되지 않는 경우

    • 선택한 정보들은 Local Storage에 저장됩니다. 따라서, 해당 앱의 Storage를 삭제 후 다시 실행하면 정상 동작했습니다.
    • 그것으로도 안될 경우 최후의 수단, 해당 앱을 Uninstall 후 다시 실행하면 정상적으로 동작했습니다.

참고