AdMob에서 광고 게재 제한의 철퇴를 받았다... 언제까지나 게재 제한인 상태로 광고를 아예 죽여둘 수는 없어서 다른 광고 서비스를 찾아보다가, 시행착오 끝에 AppLovin을 적용하기로 결정했다.
후보로 고려했던 서비스로는 Meta Audience Network, IronSource, Unity Ads, AppLovin이 있는데, 이 중 React Native 연동이 가장 용이해 보여 AppLovin으로 최종 결정되었다.
yarn install react-native-applovin-max
MAX Ad Review는 유저의 더 나은 광고 경험을 위한 서비스이다. 어플리케이션에 표시된 광고를 보고 문제가 있는 광고 소재를 추적할 수 있다.
android/build.gradle
buildscript {
repositories {
maven { url 'https://artifacts.applovin.com/android' }
}
dependencies {
classpath "com.applovin.quality:AppLovinQualityServiceGradlePlugin:+"
}
}
android/app/build.gradle
apply plugin: 'applovin-quality-service'
applovin {
apiKey "YOUR_AD_REVIEW_KEY_HERE"
}
AppLovinQualityServiceSetup-ios.rb
파일을 다운로드 받은 뒤 ios/
폴더 안에 넣고 해당 디렉토리 내에서 다음 명령어를 실행한다. (이 파일은 AppLovin 대시보드에 로그인되어 있어야 정상적으로 다운로드 받을 수 있다고 한다.)
ruby AppLovinQualityServiceSetup-ios.rb
import AppLovinMAX from "react-native-applovin-max";
AppLovinMAX.initialize("YOUR_SDK_KEY_HERE").then(configuration => {
// SDK is initialized, start loading ads
}).catch(error => {
// Failed to initialize SDK
});
더 나은 사용자 경험을 위해서는 광고 로딩에 걸리는 시간을 최대한 사용자에게 보이지 않게 해야 한다. 따라서 네트워크에 광고를 캐시할 수 있는 시간을 최대한 제공하기 위해 AppLovin SDK의 초기화는 앱이 시작할 때 이루어지는 것이 좋다고 한다.
iOS 15부터 Apple은 개발자가 SKAdNetwork 설치 포스트백 사본을 원하는 엔드포인트로 보낼 수 있도록 허용한다. MAX는 개발자가 한 곳에서 모든 네트워크 파트너의 SKAdNetwork 데이터에 액세스할 수 있도록 글로벌 SKAdNetwork 보고서를 제공하고, MAX > 중재 > 분석 > 글로벌 SKA 보고서에서 확인할 수 있다고 한다.
<key>NSAdvertisingAttributionReportEndpoint</key>
<string>https://postbacks-app.com</string>
설치까지 쉽게한 건 좋았는데 최대의 문제점... react-native-applovin-max 라이브러리는 타입이 아예 없다.
AppLovin을 적용하는 게 맞나 싶은 최대의 시련이었지만 일단은 참아내고 임시로 index.d.ts
에 정의를 추가해서 import 시 타입 에러가 뜨지 않게 조치했다. 추후에 시간이 날 때 사용하는 함수 위주로 천천히 타이핑을 추가해 보려 한다.
index.d.ts
declare module "react-native-applovin-max";
import AppLovinMAX from "react-native-applovin-max";
<AppLovinMAX.AdView
adUnitId="BANNER_AD_UNIT_ID"
adFormat={AppLovinMAX.AdFormat.MREC}
onAdLoadFailed={onApplovinFailedToLoad}
/>
배너 광고의 경우 컴포넌트 형태로 제공한다. 광고 id인 adUnitId
와 사이즈, 그리고 실패 시 핸들링 함수를 넘겨 주면 기본적으로 구현이 가능하다.
import AppLovinMAX from "react-native-applovin-max";
// AppLovin 광고 로드
useEffect(() => {
function loadInterstitial() {
AppLovinMAX.loadInterstitial("INTERSTITIAL_AD_UNIT_ID");
}
AppLovinMAX.addInterstitialAdFailedToDisplayEventListener((adInfo) => {
console.log(adInfo.adUnitId);
loadInterstitial();
});
AppLovinMAX.addInterstitialHiddenEventListener(() => {
loadInterstitial();
});
loadInterstitial();
return () => {
AppLovinMAX.removeInterstitialAdFailedToDisplayEventListener();
AppLovinMAX.removeInterstitialHiddenEventListener();
};
}, []);
const showInterstitialAd = useCallback(async () => {
const isAppLovinLoaded = await AppLovinMAX.isInterstitialReady("INTERSTITIAL_AD_UNIT_ID");
if (isAppLovinLoaded) {
AppLovinMAX.showInterstitial("INTERSTITIAL_AD_UNIT_ID");
}
}, []);
전면 광고의 구현은 약간 더 복잡하다. 로딩은 AppLovinMAX.loadInterstitial()
함수로, 광고 표시는 AppLovinMAX.showInterstitial()
함수로 하면 되지만 이외의 핸들링은 전부 이벤트 리스너를 달아주어야 한다.
addInterstitialAdFailedToDisplayEventListener
, addInterstitialHiddenEventListener
등 다양한 이벤트 리스너를 제공하는데, 별도로 정리되어 있는 문서도 없고 타이핑도 없어서 코드를 일일이 읽어가며 필요한 리스너를 찾는 과정이 조금 힘들었다. ^^;
대부분의 리스너에서는 adInfo
인자를 넘겨 주어서 이벤트를 발생시킨 광고의 adUnitId
가 무엇인지, 어느 네트워크인지 등을 알 수 있다.
AdInfo
Typeinterface AdInfoType {
rewardLabel: string;
networkName: string;
waterfall: {
testName: string;
latencyMillis: number;
name: string;
networkResponses: {
mediatedNetwork: {
adapterClassName: string;
name: string;
sdkVersion: string;
adapterVersion: string;
};
latencyMillis: number;
credentials: {
placement_id: string;
app_id: string;
always_reward_user: boolean;
};
adLoadState: number;
}[];
};
dspName: string;
creativeId: string;
placement: string;
revenue: number;
adUnitId: string;
rewardAmount: string;
}
addInterstitialAdLoadedEventListener
removeInterstitialAdLoadedEventListener
addInterstitialAdLoadFailedEventListener
removeInterstitialAdLoadFailedEventListener
addInterstitialAdClickedEventListener
removeInterstitialAdClickedEventListener
addInterstitialAdDisplayedEventListener
removeInterstitialAdDisplayedEventListener
addInterstitialAdFailedToDisplayEventListener
removeInterstitialAdFailedToDisplayEventListener
addInterstitialHiddenEventListener
removeInterstitialHiddenEventListener
addInterstitialAdRevenuePaidListener
removeInterstitialAdRevenuePaidListener
import AppLovinMAX from "react-native-applovin-max";
// AppLovin 광고 로드
useEffect(() => {
function loadRewardedAd() {
AppLovinMAX.loadRewardedAd("REWARDED_AD_UNIT_ID");
}
AppLovinMAX.addRewardedAdLoadedEventListener(() => {
// Rewarded ad is ready to show. AppLovinMAX.isInterstitialReady(REWARDED_AD_UNIT_ID) now returns 'true'
setIsAppLovinLoaded(true);
});
AppLovinMAX.addRewardedAdFailedToDisplayEventListener(() => {
loadRewardedAd();
});
AppLovinMAX.addRewardedAdHiddenEventListener(() => {
loadRewardedAd();
});
loadRewardedAd();
return () => {
AppLovinMAX.removeRewardedAdLoadedEventListener();
AppLovinMAX.removeRewardedAdFailedToDisplayEventListener();
AppLovinMAX.removeRewardedAdHiddenEventListener();
};
}, []);
const showRewardedAd = useCallback(async () => {
if (isAppLovinLoaded) {
AppLovinMAX.showRewardedAd("REWARDED_AD_UNIT_ID", null, customData);
}
}, [isAppLovinLoaded, customData]);
리워드형 광고의 구현은 전면 광고와 비슷하다. AppLovinMAX.loadRewardedAd
함수와 AppLovinMAX.showRewardedAd
함수로 광고를 보여줄 수 있고, 마찬가지로 이벤트 리스너를 통해 핸들링해야 한다.
위의 예시 코드를 보면 알 수 있지만 showRewardedAd
함수의 세번째 인자로 customData를 넘길 수 있다. 해당 함수의 정확한 타입은 다음과 같은데, 서버 단의 인증이나 처리가 필요한 경우 customData를 넘겨서 핸들링할 수 있다. showInterstitial
함수도 동일한 타입을 가진다.
showRewardedAd(adUnitId: string, placement?: string | null, customData?: string): void
AppLovin MAX에서는 서버 단으로 callback을 넘겨 주는 것을 S2S Rewarded Callbacks라고 부르는데, 광고를 생성하거나 수정할 때에 광고 타입 선택지 내 Rewarded 버튼 우측의 Add S2S Reward Callback
버튼을 눌러 Server Side Callback URL과 리워드 형식을 설정할 수 있다.
S2S Callback API 공식 가이드를 보면 URL 예시를 https://myrewardedserver.com/rewards?idfa={IDFA}&user_id={USER_ID}&event={EVENT_ID}&token={EVENT_TOKEN}
로 기재해 두었는데, 예시에서 확인할 수 있듯 정해진 키워드(매크로)를 URL에 지정하면 callback이 실행될 때 실제 유저의 정보를 넘겨주어 서버 단에서 로직을 처리할 때에 사용할 수 있다.
addRewardedAdLoadedEventListener
removeRewardedAdLoadedEventListener
addRewardedAdLoadFailedEventListener
removeRewardedAdLoadFailedEventListener
addRewardedAdClickedEventListener
removeRewardedAdClickedEventListener
addRewardedAdDisplayedEventListener
removeRewardedAdDisplayedEventListener
addRewardedAdFailedToDisplayEventListener
removeRewardedAdFailedToDisplayEventListener
addRewardedAdHiddenEventListener
removeRewardedAdHiddenEventListener
addRewardedAdReceivedRewardEventListener
removeRewardedAdReceivedRewardEventListener
addRewardedAdRevenuePaidListener
removeRewardedAdRevenuePaidListener
위 Mediation 가이드에 들어가서 연동할 네트워크를 선택한 뒤 해당하는 dependencies를 추가해 주면 된다. 나의 경우 구글 AdMob만 우선 진행하였다.
android/app/build.gradle
dependencies {
implementation "com.applovin:applovin-sdk:+"
implementation "com.applovin.mediation:google-adapter:+"
}
AndroidManifest에 Google AdMob / Google Ad Manager App ID를 추가해야 한다.
android/app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest … >
<application ...>
<!-- AppLovin MAX - AdMob mediation integration -->
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ADMOB_APPLICATION_ID"/>
</application>
</manifest>
iOS 역시 마찬가지로 위 Mediation 가이드에 들어가서 연동할 네트워크를 선택한 뒤 해당하는 dependencies를 Podfile 내에 추가해 주면 된다.
ios/Podfile
target 'YOUR_PROJECT_NAME' do
# AppLovin Mediated Networks
pod 'AppLovinSDK'
pod 'AppLovinMediationGoogleAdapter'
# ...
또한 Info.plist
내에 Google AdMob / Google Ad Manager App ID를 추가해야 한다. 만약 App Transport Security(ATS)가 설정되어 있지 않다면 NSAppTransportSecurity
에 NSAllowsArbitraryLoads
를 YES
로 추가하여 설정해 주어야 한다.
그리고 SKAdNetwork를 구성해아 하는데, SKAdNetwork Info.plist Generator에서 해당하는 네트워크를 선택하면 SKAdNetworkIdentifier
lists를 자동으로 생성해 주니 이를 복사해서 Info.plist
내에 넣으면 된다.
Info.plist
...
<key>GADApplicationIdentifier</key>
<string>ADMOB_APPLICATION_ID</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
...
</dict>
<key>SKAdNetworkItems</key>
<array>
{...SKAdNetworkIdentifiers}
</array>
글 잘 봤습니다. 저도 철퇴를 맞아 뚝배기가 즐즐새는 중입니다ㅠ 저 또한 applovin을 고민해봤는데 admob보다 수익이 안 나온다고 해서 이 부분은 미디에이션으로 극복 가능한가요? 그리고 미디에이션을 안 해봐서 그러는데 애드몹 계정이 없어도 미디에이션이 가능한지도 여쭙고 싶습니다! 읽어주셔서 감사합니다.