Language / Ngôn ngữ / भाषा: English | Tiếng Việt | हिन्दी
Tài liệu này là chuẩn tham chiếu bắt buộc dành cho đối tác phát triển khi tích hợp quảng cáo trên các sản phẩm của Infinity. Mọi thay đổi liên quan Ads phải tuân thủ kiến trúc, luồng load/show và các rule gating được mô tả trong project base này.
Project Example-AdLogic-Partner được xây dựng như template/base cho mọi app Android trong hệ sinh thái. Đối tác fork hoặc nhân bản từ base này để đảm bảo:
- Cùng một cách tổ chức package Ads (
AdRemoteConfig,RemoteConfigUtils,AdsManager,AdExtension). - Cùng cơ chế đọc config từ asset và Firebase Remote Config.
- Cùng pattern quan sát kết quả load (LiveData) và populate native ad.
- Cùng entry QA qua DevSetting trên màn Language.
Mục tiêu: giảm sai lệch giữa các app, dễ bảo trì, dễ audit và dễ hỗ trợ kỹ thuật tập trung.
Luồng hiện tại trong base — khởi tạo sớm tại GlobalApp, đồng bộ config tại Splash, preload theo màn kế tiếp, gate tập trung trong AdsManager, organic qua ERainAd.getShouldDisplay*(enableUaCheck) — đã được chuẩn hóa sau nhiều vòng tối ưu về thời điểm load, tránh jank UI, fallback khi mất mạng/mua hàng, và điều kiện hiển thị theo cohort.
Đối tác không tự ý thay đổi flow cốt lõi (ví dụ: gọi trực tiếp SDK bỏ qua AdsManager, bỏ gate organic, hoặc load/show không đúng thứ tự màn) trừ khi có phê duyệt kỹ thuật từ Infinity.
Các màn sau đã được implement đầy đủ; đối tác phải giữ nguyên cách gọi load/show, vị trí preload và điều kiện gate tương ứng:
| Màn hình | Placement / hành vi |
|---|---|
| Splash | inter_splash, preload native_language, cấu hình open_resume |
| Language | Native language / click, preload onboarding page 1, DevSetting (tvTitle) |
| Onboarding | Native page 1 & 4, native full, inter_onboarding, widget uninstall |
| Welcome / Resume | native_welcome, inter_welcome, rule ResumeAdsEntryRule |
Banner (Home và màn extend BaseActivityWithBanner) |
Banner thường / collapsible, reload theo config |
Khi customize UI, chỉ được thay layout/container; không được bỏ các điều kiện isEnable, purchase, network và getShouldDisplay*(config.enableUaCheck) đã gắn sẵn.
Với màn hình do app tự thêm (không có sẵn trong base), đối tác vẫn phải tuân thủ cùng bộ rule:
- Khai báo placement trong
ad_config.json/ad_config_debug.jsonvà property tương ứng trongAdRemoteConfig. - Thêm method load trong
AdsManager(native qualoadNativeInternal, inter qua patternload+show). - Activity/Fragment: gọi load ở
initViews(có thểpostDelayedngắn), observe LiveData,populateNativeAdViewkhi có ad; ẩn container khinull. - Nếu placement thuộc nhóm nhạy cảm (onboarding-like, welcome, home, permission, widget…): bắt buộc 100% gắn
ERainAd.getInstance().getShouldDisplay*(config.enableUaCheck)đúng mapping mục 4. - Banner: extend
BaseActivityWithBanner, cấu hìnhBannerConfig, không tự load banner ngoàiAdsManager.loadBanner.
Tài liệu UI/Ads chi tiết (kích thước CTA, delay nút Done, vị trí native theo page): Infinity UI Documentation — Language & Onboarding.
- Debug: đọc
ad_config_debug.json. - Release: đọc
ad_config.json, sau đó có thể override bằng Firebase Remote Config (ad_remote_config).
Thứ tự trong GlobalApp.onCreate() (bắt buộc follow):
| Bước | Gọi tại | Mục đích |
|---|---|---|
| 1 | MobileAds.initialize(this) |
Khởi tạo Google Mobile Ads SDK |
| 2 | DevConfig.init(...) |
DevConfig UI — version libs ads (xem mục 1.3) |
| 3 | initAdRemoteConfig() |
AdRemoteConfig.initializeFromAssets(this) |
| 4 | initAds() |
ERainAd + rule resume/inter (xem mục 1.5) |
| 5 | ResumeAdsEntryRule.shouldShowWelcomeOnResume() |
Đăng ký AppLifecycleObserver nếu cần welcome flow |
SplashActivity.checkRemoteConfigResult():AdRemoteConfig.initialize(this, RemoteConfigUtils.getAdRemoteConfig())để apply config mới nhất từ remote.
Gọi sớm trong onCreate(), trước initAdRemoteConfig() và initAds(). Ba tham số version lấy từ BuildConfig (phải khai báo trong app/build.gradle — xem mục 1.4):
DevConfig.init(
context = this,
nkhStudioVersion = BuildConfig.ERAIN_STUDIO_VERSION,
playServicesAdsVersion = BuildConfig.PLAY_SERVICES_ADS_VERSION,
gdprModuleVersion = BuildConfig.GDPR_MODULE_VERSION
)| Tham số | Nguồn BuildConfig |
Hiển thị trên DevConfig UI |
|---|---|---|
nkhStudioVersion |
ERAIN_STUDIO_VERSION |
ERain Studio / ads module version |
playServicesAdsVersion |
PLAY_SERVICES_ADS_VERSION |
Google Play Services Ads version |
gdprModuleVersion |
GDPR_MODULE_VERSION |
GDPR module version |
LanguageActivity:mBinding.tvTitle.setOnAdminAdToggleListener().- Tại đây QA có thể check: version sdk ads, mediation, config id, ad id, reset organic.
Bắt buộc cấu hình trong
app/build.gradle: để DevConfig UI hiển thị đúng thông tin version, đối tác phải khai báo đủ 3 dòngbuildConfigFieldbên dưới (ở cảdebugvàrelease):buildConfigField "String", "ERAIN_STUDIO_VERSION", "\"$erain_studio_version\"" buildConfigField "String", "PLAY_SERVICES_ADS_VERSION", "\"$play_services_ads_version\"" buildConfigField "String", "GDPR_MODULE_VERSION", "\"$module_update_gdpr_version\""
Hướng dẫn test DevConfig (PO / Tester): DevConfig Testing Guide
Trong base hiện tại, phần tích hợp chính nằm ở GlobalApp.initAds(). Đối tác nên giữ nguyên pattern này khi tạo app mới:
- Chọn
environmenttheo build type (ERainAdConfig.ENVIRONMENT_DEVELOP/ERainAdConfig.ENVIRONMENT_PRODUCTION). - Tạo
mERainAdConfig = ERainAdConfig(this, environment). - Set các trường config cần thiết trước khi
ERainAd.init(...):adjustConfigfacebookClientTokenadjustTokenTiktokintervalInterstitialAdidAdResume
- Gọi
ERainAd.getInstance().init(this, mERainAdConfig). - Set các rule bổ sung cho resume/inter:
Admob.getInstance().setDisableAdResumeWhenClickAds(true)Admob.getInstance().setOpenActivityAfterShowInterAds(true)AppOpenManager.getInstance().disableAppResumeWithActivity(...)cho các màn cần loại trừ.
Snippet tham chiếu:
private fun initAds() {
val environment =
if (BuildConfig.DEBUG) ERainAdConfig.ENVIRONMENT_DEVELOP else ERainAdConfig.ENVIRONMENT_PRODUCTION
mERainAdConfig = ERainAdConfig(this, environment)
mERainAdConfig.adjustConfig = AdjustConfig(true, resources.getString(R.string.adjust_token))
mERainAdConfig.facebookClientToken = resources.getString(R.string.facebook_client_token)
mERainAdConfig.adjustTokenTiktok = resources.getString(R.string.event_token)
mERainAdConfig.intervalInterstitialAd = 35
mERainAdConfig.idAdResume = ""
ERainAd.getInstance().init(this, mERainAdConfig)
}Lưu ý:
initAdRemoteConfig()vẫn cần gọi trướcinitAds(), và config remote vẫn được đồng bộ lại ởSplashActivityquaRemoteConfigUtils.init(...)+AdRemoteConfig.initialize(...).
- Inter Splash:
- Điều kiện:
AdRemoteConfig.inter_splash.isEnable == truevà có mạng. - API:
ERainAd.getInstance().loadSplashInterstitialAds(...). - Sau khi load thành công (
onAdLoaded) thì preloadnative_language.
- Điều kiện:
- Open Resume:
- Bật/tắt theo
ResumeAdsEntryRule.shouldEnableOpenResume().
- Bật/tắt theo
- Native language:
- preload từ Splash:
AdsManager.loadNativeLanguage(...). - native click variant:
AdsManager.loadNativeLanguageClick(...).
- preload từ Splash:
- Native page onboarding 1 được load sớm:
AdsManager.loadNativeOnboarding1(...).
AdsManager.loadNativeOnboarding4(...).AdsManager.loadNativeOnboardingFull(...).AdsManager.loadInterOnboarding(...)và show bằngAdsManager.showInterOnboarding(...)khi kết thúc onboarding.
- Native welcome:
AdsManager.loadNativeWelcome(...), gategetShouldDisplayNativeWelcomeBack(config.enableUaCheck).
- Inter welcome:
AdsManager.loadInterWelcome(...),AdsManager.showInterWelcome(...).- Flow welcome được kích hoạt bởi
AppLifecycleObservernếuResumeAdsEntryRule.shouldShowWelcomeOnResume()vàgetShouldDisplayInterWelcomeBack(AdRemoteConfig.inter_welcome.enableUaCheck)cho phép.
- Dùng
BaseActivityWithBanner. AdsManager.loadBanner(..., isCollapse = false)=> banner thường.AdsManager.loadBanner(..., isCollapse = true)=> collapsible banner (expand/collapse theo SDK).- Reload theo
reloadIntervalSeconds.
Trong AdsManager, một ad chỉ load khi thỏa đủ:
adUnitConfig.isEnable == true.!AppPurchase.getInstance().isPurchased(...).- Có mạng.
- Với các vị trí bắt buộc gate organic:
getShouldDisplay*(config.enableUaCheck) == true.
Nếu fail 1 điều kiện, native LiveData trả null để UI ẩn ad container.
Bắt buộc: 100% các vị trí dưới đây phải check thêm biến
getShouldDisplay*của SDK.
Param truyền vào làenableUaChecklấy từ config placement trongad_config.json/ad_config_debug.json(map sangAdUnitConfig.enableUaCheck).
Đây là cờ organic/UA check (force organic theo config ads) — không được hard-codetrue/false, phải lấy từ config của đúng placement đang load/show.
| Vị trí Ads | Method SDK bắt buộc | Default enable_ua_check trong ad_config.json |
Param từ ad_config | Method / chỗ dùng trong code |
|---|---|---|---|---|
| NativeOnboardingFull1 | getShouldDisplayNativeOnboardingFull1(...) |
true |
config.enableUaCheck |
AdsManager.loadNativeOnboardingFull (+ chèn page full ở OnBoardingActivity) |
| NativeOnboardingFull2 | getShouldDisplayNativeOnboardingFull2(...) |
true |
config.enableUaCheck |
AdsManager.loadNativeOnboardingFull2 (+ chèn page full ở OnBoardingActivity) |
| NativeOnboardingNormal2 | getShouldDisplayNativeOnboardingNormal2(...) |
false |
config.enableUaCheck |
AdsManager.loadNativeOnboarding4 |
| NativeHome | getShouldDisplayNativeHome(...) |
false |
config.enableUaCheck |
AdsManager.loadNativeHome |
| NativePermission | getShouldDisplayNativePermission(...) |
false |
config.enableUaCheck |
AdsManager.loadNativePermission |
| InterOnboarding | getShouldDisplayInterOnboarding(...) |
true |
config.enableUaCheck |
AdsManager.loadInterOnboarding / showInterOnboarding |
| NativeWelcomeBack | getShouldDisplayNativeWelcomeBack(...) |
false |
config.enableUaCheck |
AdsManager.loadNativeWelcome |
| InterWelcomeBack | getShouldDisplayInterWelcomeBack(...) |
false |
config.enableUaCheck |
AppLifecycleObserver (chuyển hướng màn Welcome) |
| WidgetUninstall | getShouldDisplayWidgetUninstall(...) |
false |
config.enableUaCheck |
OnBoardingActivity widget shortcut; loadNativeSurvey / loadNativeConfirmUninstall |
Default trong
ad_config: khi khai báo JSON, các placement trên phải setenable_ua_checkđúng default cột trên trừ khi Infinity chỉ định khác. Ví dụ Full1/Full2/inter_onboardingmặc địnhtrue; các vị trí còn lại mặc địnhfalse.
Trong JSON mỗi placement:
"native_onboarding_fullscreen_1_3": {
"id": "ca-app-pub-xxx/yyy",
"isEnable": true,
"enable_ua_check": true
}Trong code:
val config = AdRemoteConfig.native_onboarding_fullscreen_1_3
ERainAd.getInstance().getShouldDisplayNativeOnboardingFull1(config.enableUaCheck)| JSON key | Field Kotlin | Ý nghĩa |
|---|---|---|
enable_ua_check |
AdUnitConfig.enableUaCheck |
Bật/tắt organic (UA) check cho đúng placement đó khi gọi getShouldDisplay* |
loadNativeInternal(
activity,
config,
layoutRes,
liveData,
ERainAd.getInstance().getShouldDisplayNativeOnboardingFull1(config.enableUaCheck)
)Không đạt chuẩn nếu:
- Bỏ qua
getShouldDisplay*ở các vị trí bảng trên. - Gọi
getShouldDisplay*(true/false)hard-code thay vìconfig.enableUaCheck. - Dùng nhầm method gate giữa các vị trí (ví dụ Full1 dùng Normal2).
Organic là cơ chế phân loại user từ Ads SDK / logic tăng trưởng để:
- Giảm tần suất hoặc tắt một số ad slot nhạy cảm với một nhóm user.
- Cân bằng retention, UX và revenue.
- Cho phép rule theo cohort mà không sửa từng màn hình.
Cách hoạt động trong app:
- App không tự tính organic bằng local rule.
- App gọi
ERainAd.getInstance().getShouldDisplay*(enableUaCheck)vớienableUaChecklấy từad_config. - Khi organic/cohort rule đổi, kết quả các method này đổi theo và ảnh hưởng trực tiếp load/show từng slot.
- DevSetting / Unlimited Ads +
reset organicgiúp QA verify lại toàn bộ vị trí ads + widget uninstall.
if (AdRemoteConfig.inter_splash.isEnable && isNetwork(this)) {
ERainAd.getInstance().loadSplashInterstitialAds(
this, AdRemoteConfig.inter_splash.id, 30000, 5000, object : AdCallback() {
override fun onNextAction() { moveActivity() }
}
)
} else moveActivity()AdsManager.loadNativeOnboarding1(this, appSharedPref.firstOnBoarding, R.layout.layout_native_onboarding)
AdsManager.nativeOnboarding1AdLive.observe(this) { ad ->
if (ad == null) hideAd() else showAd(ad)
}AdsManager.loadInterOnboarding(this)
AdsManager.showInterOnboarding(this) {
goNextScreen()
}override val bannerConfig = BannerConfig(
adUnitConfig = AdRemoteConfig.banner_home,
isCollapse = false
)override val bannerConfig = BannerConfig(
adUnitConfig = AdRemoteConfig.banner_home,
isCollapse = true
)- Infinity UI Documentation — Language & Onboarding — UI, Remote Config và điều kiện hiển thị từng ad unit (đối chiếu tên method/placement với tài liệu này).