Mục 2

📺 Ads & luồng hiển thị

App có những loại ads nào, show ở đâu, khi nào, bao lâu mới show lại, và khi nào app sạch ads. Không cần đọc code.

Chốt theo code chiều 06/08/2026 (bản mới nhất trong ngày). Sáng 06/08 app từng đổi native ads sang dùng 1 cache chung 50 phút cho cả native ads + native full ads — nhưng ngay chiều cùng ngày chủ nhân quyết định bỏ hẳn cache đó, quay về load trực tiếp mỗi lần vào màn, load fail thì ẩn ô, không mượn ad nơi khác. Bản này chốt theo trạng thái sau khi bỏ cache — nếu bạn nghe ai nhắc "native ads có cache 50 phút" thì đó là thiết kế đã bị gỡ, không còn đúng nữa. 4 thay đổi khác vẫn giữ: inter ads không cache tạm (bỏ từ 05/08), reward ads không có ad thì tự thay bằng inter ads, native ads chỉ tính là đã show khi ô ads thật sự lọt vào màn hình — xem mục 10, và returning user (đã onboarding xong) mở app sau khi tiến trình bị dọn hẳn vẫn qua lại đúng màn logo (không còn nhánh riêng "Welcome back" cho trường hợp này) — xem mục 4A. Cần hiểu vòng đời một ad (request → load → show, không còn cache) thì đọc mục 3; chi tiết cách load ở mục 10 + mục 11. Bản cũ để đối chiếu: bản ads cũ (đã lệch, chỉ tham khảo).
Mục lục trang

1 Các loại ads

Tài liệu này dùng đúng tên tiếng Anh mà cả team (kể cả bên đối tác quảng cáo) đang dùng — bảng thuật ngữ ngay dưới đây. Ba nhóm chính, phân biệt theo cảm nhận của người dùng — còn cơ chế request/load/show/cache của từng loại thì ở mục 3.

Thuật ngữ Nghĩa
adsQuảng cáo, nói chung.
inter ads interstitialAds che kín màn hình, chèn giữa 2 thao tác; có nút đóng (✕) hoặc tự đóng.
AOA App Open AdsAds che kín màn hình lúc mở app hoặc quay lại app; tự đóng khi xem xong, không có nút đóng riêng.
native adsÔ ads nằm trong nội dung, giống một thẻ của app, không che màn.
native full adsMột native ads nhưng chiếm cả màn hình, show ngay sau khi một inter ads vừa đóng. Tắt mặc định.
reward ads rewardedNgười dùng tự bấm xem để đổi lấy quyền lợi. Không bao giờ tự bật lên.
full-screen adsCách gọi gộp 3 loại che kín màn: inter ads · AOA · native full ads.
requestApp hỏi Google "có ad cho vị trí này không". Mỗi lần request = 1 lượt ad_request.
loadGoogle trả ad về máy. Có ad = fill, hết ad = no-fill.
showAd thật sự hiện lên cho người dùng thấy. Load xong chưa chắc show được.
cacheAd đã load về mà chưa/không show được thì giữ lại cho lượt sau. Từ chiều 06/08/2026 không loại ads nào còn cache — thuật ngữ này chỉ còn dùng khi nói về lịch sử/thay đổi, không phải cơ chế đang chạy.
timeoutTrần thời gian chờ load. Quá timeout là app đi tiếp, không đợi ad nữa.
fallbackĐường lùi khi không show được ad chính. Hiện chỉ còn 1 dạng: reward ads hết ad thì thay bằng inter ads. Native ads không còn fallback nào — load fail là ẩn ô.
cappingGiãn cách — khoảng thời gian tối thiểu giữa 2 lần show cùng một loại ads (mục 5).
fill / no-fillRequest về ad để show / không có ad nào (Google hết ad cho vị trí đó).

Full-screen ads

Che kín màn hình, phải đóng (hoặc tự đóng) mới đi tiếp.

Gồm inter ads (có nút đóng / tự đóng khi hết đếm ngược) và AOA (tự đóng ngay khi xem xong). Hiện ở: mở app lần đầu, mở mini-app, quay lại app sau khi thoát ra ngoài, bấm nút đóng trên các mini-app, và một số thao tác trong Cài đặt. Có thêm 1 biến thể đặc biệt — native full ads ngay sau một lần inter ads — đang tắt mặc định.

Nguồn: ads/InterstitialAdManager.kt, ads/SplashAppOpenAdLoader.kt, ads/NativeFullAdManager.kt

Native ads

Ô ads nằm trong nội dung, không che, vẫn dùng app bình thường.

Có mặt ở 28 vị trí khác nhau trong app: Cài đặt và các màn con, chọn ngôn ngữ, mọi màn "đang tải", Thư viện ảnh, kho Theme Store/Hình nền… Mỗi lần vào màn có ô này, ô request ad mới (cuộn ra/vào hay đổi tab thì KHÔNG request lại — mục 10). Trong lúc load: ô hiện khung mờ (shimmer). Load fail thì ô tự ẩn gọn ngay — không có cache dự phòng nào để mượn.

Nguồn: ads/NativePlacements.kt (28 placement), ads/NativeAdLoader.kt, ui/components/AdMobNativeAd.kt (NativeSlotState)

Reward ads

Người dùng tự bấm xem để đổi lấy quyền lợi — không tự bật lên.

2 nơi dùng: Chat AI (hết lượt chat miễn phí trong ngày → xem để +1 lượt ngay) và Theme Store (mở khoá vĩnh viễn 1 theme/gói icon/hình nền/widget miễn phí, hoặc xem để +1 xu trong Ví xu). Không giới hạn số lần xem, trừ xem-lấy-xu (tối đa 3 lần/ngày). Không có reward ads để hiện thì app thay bằng 1 inter ads và vẫn trả thưởng — mục 11.

Nguồn: ads/RewardedAdManager.kt, data/chat/ChatCreditStore.kt, data/themestore/CoinStore.kt

Danh sách 28 vị trí native ads (tham số placement)

Tra khi lọc Firebase theo tham số placement của ad_request/ad_loaded/ ad_load_failed/ad_clicked/ad_displayed (mục 4 trang Events).

📌 Từ 06/08/2026: mỗi vị trí có nhãn riêng — trước đó 26/28 vị trí dùng chung 1 nhãn
Trước bản cập nhật này, hầu hết vị trí (trừ màn Giới thiệu) đều bắn placement = native_default ở 4 sự kiện đầu funnel (ad_request/ad_loaded/ad_load_failed/ad_clicked), trong khi sự kiện ad_displayed lại ghi đúng tên màn — nên KHÔNG lọc được matchrate/showrate/ctr riêng cho từng màn (tử số và mẫu số đếm ở hai nhãn khác nhau). Giờ cả 5 sự kiện của một vị trí đều dùng đúng một nhãn — lọc placement ra số nào là đúng luồng của riêng màn đó.
Màn hình / vị trí trong app Giá trị placement
Giới thiệu (Intro) lần đầu mở appnative_intro
Cài đặt — trang chính + màn Ứng dụngsettings
Cài đặt — các màn con (General/Đổi icon/Control Center…)settings_sub
Cài đặt — Ẩn ứng dụng (Hidden Apps)settings_hidden_apps
DailyOpen (kéo xuống từ Home)daily_open
Thư viện ứng dụng (App Library)app_library
Spotlight (kéo xuống tìm kiếm)spotlight
Tối ưu ứng dụng (App Optimize) — màn chínhapp_optimize
Tối ưu ứng dụng — bảng cài app mới (sheet)app_optimize_sheet
Thư viện ảnh — màn chính (Library)gallery_library
Thư viện ảnh — màn xin quyền truy cậpgallery_permission
Thư viện ảnh — danh sách Albumgallery_albums
Thư viện ảnh — chi tiết 1 Albumgallery_album_detail
Thư viện ảnh — Thùng rác (Recently Deleted)gallery_trash
Kho Theme Store — item trong grid gói Iconstore_iconpack_item
Kho Theme Store — màn chi tiết cuối (Xem thử theme / Áp dụng thành công)theme_store_bottom
Kho Theme Store — tab Trendingtheme_store_trending
Kho Theme Store — tab Hình nền (Wallpaper)theme_store_wallpaper_tab
Kho Theme Store — tab Themetheme_store_themes_tab
Kho Theme Store — tab Icontheme_store_icons_tab
Kho Theme Store — tab Widgettheme_store_widgets_tab
Kho Theme Store — màn "Của tôi" (Theme/Icon/Hình nền/Widget đã có)theme_store_my_content
Kho Theme Store — chi tiết danh mục Hình nềntheme_store_wallpaper_category_detail
Kho Theme Store — chi tiết danh mục Themetheme_store_theme_category_detail
Kho Theme Store — chi tiết danh mục gói Icontheme_store_icon_category_detail
Kho Theme Store — chi tiết danh mục Widgettheme_store_widget_category_detail
Kho Theme Store — Hình nền nổi bật ("Xem tất cả" của Trending)theme_store_featured_wallpapers
Ô native ads dạng thẻ lồng ghép, dùng chung khi màn không có vị trí riêngprogrammatic
Nguồn: ads/NativePlacements.kt (28 NativePlacement), ui/components/AdMobNativeAd.kt (đối chiếu slot hiển thị)
2 vị trí native ads KHÔNG nằm trong danh sách 28 (cơ chế riêng, không đổi)
  • Mọi màn "đang tải" (splash, mở mini-app, mở popup Premium…) → native_loading.
  • Màn Chọn ngôn ngữ có 2 ô, đổi khi chọn dòng đầu → native_language_1 / native_language_2.

2 Bảng format chi tiết

Bảng tra khi làm việc với đối tác quảng cáo hoặc khi cần đối chiếu doanh thu theo từng loại.

Loại Người dùng thấy gì Show ở đâu Bật/tắt từ xa
Full-screen
inter ads
Che kín màn hình, có nút đóng (✕) hoặc tự đóng khi thao tác xong.
  • Mở app lần đầu (tuỳ cấu hình, mặc định tắt — xem mục 4A)
  • Mở mini-app (mặc định bật)
  • Đóng mini-app bằng nút trên màn
  • Điều hướng trong Cài đặt
  • Đóng popup mời nâng cấp Premium
  • Thay cho reward ads khi không có reward ads (mục 11)
Có — công tắc riêng inter_ad_on_off
Full-screen
AOA
Che kín màn hình lúc mở app / quay lại app; tự đóng ngay khi xem xong, không có nút đóng riêng.
  • Mở app lần đầu (mặc định bật)
  • Mở mini-app (tuỳ cấu hình)
  • Quay lại app sau khi thoát ra ngoài rồi mở lại ("Welcome back")
Có — công tắc riêng open_ad_on_off (+ isShowAOAResume riêng cho lúc quay lại)
In-content
native ads
Một ô nội dung giống phong cách app thật, có gắn nhãn "Ad" nhỏ.
  • Cài đặt và ~12 màn con
  • Chọn ngôn ngữ, mọi màn "đang tải"
  • Thư viện ảnh (danh sách album, chi tiết album, thùng rác)
  • Kho Theme Store / Hình nền (hầu hết các tab và trang chi tiết)
Có — công tắc riêng native_ad_on_off
Full-screen
native full ads
Một native ads chiếm toàn bộ màn hình, show ngay sau khi vừa đóng 1 inter ads; có đếm ngược trước khi cho đóng (hoặc nút "Mở cửa hàng"). Ngay sau mọi lần show inter ads (nếu bật) — trừ inter ads thay cho reward ads (mục 11) Tắt mặc định — bật qua native_full_button.onoff
Reward
reward ads
Người dùng chủ động bấm "Xem quảng cáo"; xem xong nhận thưởng ngay, không tự bật lên.
  • Chat AI — hết lượt chat trong ngày
  • Theme Store — mở khoá theme/gói icon/hình nền/widget miễn phí
  • Ví xu (My Coin) — xem để nhận thêm xu
Có — công tắc riêng reward_ad_on_off
Đã bỏ
banner ads
Không dùng trong app — không có dải ads cố định ở đầu/cuối màn hình nào cả.
Nguồn: ads/InterstitialAdManager.kt · ads/SplashAppOpenAdLoader.kt · ads/RewardedAdManager.kt · ads/NativePlacements.kt · ads/NativeFullAdManager.kt · firebase/AppConfig.kt
⚠️ QA: bản debug & releaseDev đang TẮT HẾT ads
Hiện trong code, bản debug và bản releaseDev tắt cứng cả 4 loại (inter · AOA · native · reward), không phụ thuộc công tắc từ xa. Muốn test hay đo ads thì phải dùng bản release thật. Nếu cần test trên bản nội bộ thì nhờ dev bật lại công tắc dev trong ads/AdUnitIds.kt — khi bật, bản nội bộ luôn chạy ads mẫu của Google (không phải ads thật), nên đừng báo lỗi "nội dung ads sai" trên bản đó.
Nguồn: ads/AdUnitIds.kt (adsEnabled() / interEnabled() / rewardEnabled() / appOpenEnabled() — nhánh isDevMode()) · utils/Utils.kt (isDevMode() = debug hoặc releaseDev)

3 Vòng đời từng loại ads — request · load · show · cache

Mục này trả lời đúng 4 câu cho mỗi loại: request khi nào · timeout bao lâu · điều kiện gì mới show · load xong mà không show được thì đi đâu (fallback). Đọc mục này là hiểu vì sao có lúc bấm mà không thấy ads.

📖 4 từ khoá, hiểu đúng thì đọc phần dưới rất nhanh
  • request — app hỏi Google "có ad cho vị trí này không". Mỗi lần request đếm 1 lượt ad_request.
  • load — Google trả ad về. Có ad = fill; hết ad = no-fill. Mỗi loại có timeout riêng, quá timeout là app đi tiếp, không đợi nữa.
  • show — ad thật sự đập vào mắt người dùng. Load xong chưa chắc show được (xem cột "điều kiện show").
  • cache — ad load về mà chưa/không show được thì giữ lại cho lượt sau dùng (fallback). Từ chiều 06/08/2026: KHÔNG loại ads nào còn cache nữa — native ads có dùng cache 50 phút trong một khoảng ngắn buổi sáng cùng ngày, nhưng đã bị gỡ. Giờ mọi loại đều là request → load → show hoặc bỏ, không giữ ad lại cho lượt sau.

Bảng so sánh 5 loại — xem nhanh

Loại Request khi nào Timeout Điều kiện mới show Fallback (load xong không show được)
inter ads Ngay lúc cần show (bấm nút đóng mini-app, chuyển màn Cài đặt, mở mini-app…). Không request trước. 30 giây Công tắc bật · qua capping 20s · không có full-screen ads nào đang hiện Bỏ luôn — không có cache, lượt sau request lại từ đầu
AOA Mở app (màn logo) · mở mini-app · quay lại app từ nền 15 giây (quay lại mini-app: 6 giây) Công tắc bật · qua capping 25s (màn logo miễn) · không có full-screen ads nào đang hiện Bỏ luôn — không có cache
native ads Mỗi lần vào màn có ô ads. Cuộn ra/vào, đổi tab: không request lại Không có timeout cứng — trong lúc chờ ô hiện khung mờ Chỉ cần công tắc bật (không capping, không tranh khe với full-screen) Không có cache — load fail thì ẩn ô, không mượn ad nơi khác
native full ads Ngay lúc một inter ads bắt đầu show (load nền trong lúc người dùng đang xem inter) 15 giây khung mờ (shimmer) sau khi inter đóng Bật từ xa (mặc định tắt) · inter ads phía trước đã show thật Không có cache — load fail thì bỏ luôn lượt native full này
reward ads Chỉ khi người dùng bấm nút xem 45 giây Công tắc bật · không có capping · không có full-screen ads nào đang hiện Không có cache — nhưng no-fill thì thay bằng inter ads (mục 11)
Nguồn: ads/InterstitialAdManager.kt (LOAD_TIMEOUT_MS=30s) · ads/SplashAppOpenAdLoader.kt · ads/RewardedAdManager.kt (LOAD_TIMEOUT_MS=45s) · ads/NativeAdLoader.kt (không cache) · ads/NativeFullAdManager.kt (SHIMMER_TIMEOUT_MS=15s, không borrow) · firebase/AppConfig.kt

inter ads — request đúng lúc cần, không cache sẵn

🟦
Load-and-show
Request → load → show luôn. Không cache, không preload.
1 · Request
Người dùng làm thao tác cần chèn ads (đóng mini-app bằng nút, chuyển màn trong Cài đặt, mở mini-app, đóng popup Premium). App kiểm 3 cửa trước khi request: công tắc còn bật? · đã qua 20 giây kể từ lần show trước? · có full-screen ads nào đang trên màn không? Trượt 1 cửa là bỏ luôn lượt này, thao tác của người dùng chạy tiếp ngay (báo cáo ghi ad_capped).
2 · Load
Timeout 30 giây. Ở những nơi có bật màn chờ, người dùng thấy màn "đang tải" kèm 1 ô native ads, giữ tối thiểu 4 giây để không nháy.
3 · Show
Ad về trong timeout → app giữ "khe" full-screen (chỉ 1 ad được giữ, xem mục 8) → hiện. Nếu bật native full ads thì ad đó được request ngay lúc này để nối tiếp phía sau.
4 · Fallback
Bỏ ad, không cache — xảy ra khi: quá 30 giây mới về · màn đã đóng · khe full-screen bị ad khác giữ. (Bản 04/08 từng cache lại, đã gỡ ngày 05/08.) Từ 06/08 mỗi lần bỏ ad kiểu này ghi 1 lượt ad_dropped kèm reason (timeout / lost_race / activity_dead / show_failed_<mã lỗi>) — xem mục 4 trang Events.
5 · Capping
Đồng hồ capping 20 giây đóng dấu lúc người dùng thật sự thấy lại màn — nếu có native full ads nối sau thì tính từ lúc cái đó đóng, không phải lúc inter đóng.

AOA — gắn với việc mở app

🟪
Load-and-show
Cùng cơ chế inter ads, khác thời điểm + timeout.
1 · Request
3 thời điểm: mở app (màn logo) · mở mini-app (nếu cấu hình chọn AOA thay inter) · quay lại app từ nền. Cùng 3 cửa kiểm như inter nhưng capping là 25 giây — riêng màn logo miễn capping, luôn được phép hiện.
2 · Load
Timeout: màn logo 15 giây · mở mini-app 15 giây · quay lại mini-app 6 giây. Ngoài ra cả chuỗi màn logo có timeout chung 20 giây — hết là vào app luôn.
3 · Show
Che kín màn, không có nút đóng — tự đóng khi xem xong. Nếu ad không phủ hết màn (tuỳ máy/đối tác), app phủ thêm 1 lớp trắng để không lộ nội dung phía sau.
4 · Fallback
Bỏ ad, đi tiếp vào app. Không có cache. Từ 06/08 ghi 1 lượt ad_dropped kèm reason — cùng cơ chế với inter ads ở trên.

native ads — direct-load, không còn cache

🟩
Direct-load, không cache
Chi tiết đầy đủ + bảng 2 trường hợp ở mục 10.
1 · Request
Mỗi lần vào một màn có ô ads. Không request lại khi cuộn ô ra/vào hay đổi tab (quay lại trong 3 phút là dùng đúng ad cũ — ô chưa bị dispose, không phải mượn từ cache). Request lại khi: rời hẳn màn rồi vào lại · ad đã cũ hơn 50 phút · lần trước lỗi và đã qua 30 giây.
2 · Load
Ô hiện khung mờ trong lúc chờ. Không tranh khe với full-screen ads nên không bao giờ bị chặn vì "đang có ads khác".
3 · Show
Ad về là gắn vào ô ngay nếu ô đang nằm trong màn hình; ô còn ở dưới màn (danh sách dài) thì chờ người dùng cuộn tới mới gắn — xem mục 10. Không cache lại — ad về sau khi người dùng đã rời màn (ô đã dispose) thì bị destroy luôn, không giữ cho màn sau dùng.
4 · Load fail
Không có cache để mượn — load fail hoặc no-fill là ô tự ẩn gọn ngay (không để khung trống, không lấy ad ô khác gắn vào).
5 · Giữ ad trong lúc còn ở màn
Ô tự giữ đúng ad của chính nó (không chia cho ô khác) miễn còn ở trong màn/tab đó và ad chưa quá 50 phút tuổi — đây là lý do cuộn ra/vào hay đổi tab nhanh không thấy load lại (mục 10), không phải một "cache" dùng chung. Rời hẳn màn (hoặc quá 50 phút) thì ad bị destroy, không chuyển cho ô nào khác dùng tiếp.

native full ads — load nền trong lúc người dùng xem inter

🟨
Mặc định TẮT
Bật bằng native_full_button.onoff.
1 · Request
Đúng lúc một inter ads bắt đầu phủ màn — load song song trong lúc người dùng còn đang xem inter, nên lúc inter đóng thường đã có ad, đỡ phải chờ. Load thẳng, không thử mượn ad cache nào cả — kho cache riêng cho native full đã bị gỡ, và từ chiều 06/08/2026 cũng không còn dùng chung cache với native ads nữa (mục cache đã bỏ hẳn, xem mục 10).
2 · Show
Ngay khi inter đóng. Chưa có ad → khung mờ tối đa 15 giây. Có 2 biến thể luân phiên: đếm ngược rồi ra ✕, hoặc nút "Mở cửa hàng" (✕ hiện sau khi bấm / hết giờ).
3 · Close
Chỉ đóng bằng — nút back của hệ thống không đóng được (đúng thiết kế). Có cơ chế canh giờ để ✕ luôn xuất hiện, không bao giờ kẹt người dùng trong ads.
4 · Không dùng hết
Ad load về mà lượt đó đã kết thúc (user rời màn / bị bỏ lượt) thì destroy luôn, không giữ lại cho lượt sau (bỏ cache chiều 06/08/2026 — trước đó có gộp chung kho với native ads, giờ gỡ hẳn). `native_full_id` hiện vẫn chưa cấu hình riêng trên Remote Config nên tạm dùng lại cùng ad unit với `native_ad_id` — nhưng đây chỉ là trùng ad unit vật lý, hai vị trí không còn chia nhau 1 ad dự phòng nào cả.

reward ads — chỉ chạy khi người dùng bấm

🎁
Người dùng chủ động
Không capping, không tự bật lên.
1 · Request
Chỉ khi bấm "Xem quảng cáo" (mở khoá món trong Theme Store, lấy xu, thêm lượt chat AI). Không có capping — bấm liên tục vẫn xem được (trừ xem-lấy-xu tối đa 3 lần/ngày).
2 · Load
Timeout 45 giây, kèm màn "đang tải" + 1 ô native ads để người dùng không thấy trống.
3 · Show
Xem hết ad → thưởng vào ngay, ghi 1 lượt reward_complete (mới 06/08) — tách riêng khỏi ad_displayed/reward_show (bắn lúc ad vừa hiện ra, chưa chắc xem hết). Đóng ads giữa lúc xem → không nhận thưởng, không bắn reward_complete (đúng luật Google).
4 · No-fill
Hết reward ads mà còn trong 45 giây → app thay bằng 1 inter ads, ad đó phủ màn là vẫn trả thưởng. Chi tiết ở mục 11. Không có cache.
✅ Gộp lại thành 3 câu để nhớ
  • Full-screen ads (inter · AOA · native full) — request đúng lúc cần, tranh nhau 1 khe duy nhất, bị capping theo giây, load xong mà trượt thì bỏ luôn — không loại nào trong 3 loại này còn cache để mượn.
  • Native ads (kể cả native full) — request mỗi lần vào màn, không tranh khe, không capping, và không có cache đỡ cho lúc load fail — fail là ẩn ô ngay.
  • Reward ads — chỉ chạy khi người dùng bấm, không capping, hết ad thì mượn inter ads để vẫn trả được thưởng.

4 Kịch bản từng luồng

Mỗi luồng đọc theo 4 dòng: người dùng làm gì → sẽ thấy gì → quy tắc thời gian → sau đó đi đâu.

A · Mở app (màn logo)

🚀
Mở app từ ngoài
Chỉ ở lần khởi động
Người dùng
Bấm icon app, app hiện màn logo.
Sẽ thấy
Trước tiên là popup xin đồng ý riêng tư nếu cần (mục 7). Sau đó, mặc định hiện AOA (đổi được sang inter ads qua cấu hình từ xa, hiện đang tắt). Trong lúc chờ, màn logo có kèm 1 ô native ads nhỏ nếu tải kịp.
Quy tắc thời gian
Giữ màn logo tối thiểu 3 giây. Chờ AOA tối đa 15 giây. Toàn bộ màn logo (xin phép riêng tư + tải cấu hình + ads) có timeout cứng 20 giây — quá mốc này app đi tiếp luôn dù ads chưa xong.
Sau đó
Vào màn Giới thiệu (bỏ qua được nếu cấu hình tắt) → Chọn ngôn ngữ → Chọn hình nền → Hướng dẫn dùng app → Màn hình chính. (Luồng này là cho lần mở đầu tiên, tức chưa hoàn tất onboarding — xem callout ngay dưới cho lần mở thứ 2+.)
Nguồn: MainActivity.kt (startSplashFlow) · firebase/AppConfig.kt (SplashConfig)
📌 Lần mở thứ 2+ (đã onboarding xong) mà tiến trình app bị dọn hẳn — vẫn qua đúng màn logo này, KHÔNG có luồng riêng
Trước đây, cold-start lần 2+ có nhánh riêng bỏ qua thẳng màn logo, hiện overlay "Welcome back" + AOA (nhãn aoa_resume) đè lên Home. Từ 06/08/2026 nhánh đó bị bỏ: mọi lần mở app từ icon/Recents sau khi tiến trình đã bị hệ điều hành dọn hẳn — kể cả user đã onboarding xong — đều đi qua đúng màn logo (consent → native → AOA nhãn aoa_splash, không bị capping) như mục này mô tả; chỉ khác là sau khi xong màn logo, user đã onboarding thì bỏ qua thẳng Giới thiệu/Chọn ngôn ngữ/Chọn hình nền, vào ngay Màn hình chính. Overlay "Welcome back" + AOA nhãn aoa_resume (có capping 25 giây) giờ chỉ còn dùng cho lúc quay lại app khi tiến trình vẫn còn sống — xem mục 4C ngay dưới, đừng nhầm hai luồng này khi test. Nguồn: bug_pool/misc/BP-0174, BP-0175 · ui/LauncherViewModel.kt (scene init luôn = SPLASH) · MainActivity.kt (startSplashFlow / onSplashComplete)

B · Mở mini-app

📲
Bấm icon mini-app
Đồng hồ · Thời tiết · Máy tính · Thư viện ảnh · …
Người dùng
Bấm icon một mini-app trên màn hình chính.
Sẽ thấy
Màn "đang tải" hiện trước, kèm 1 ô native ads nhỏ. Mặc định sau đó hiện inter ads (đổi được sang AOA qua cấu hình từ xa); xong ads mới lộ nội dung mini-app.
Quy tắc thời gian
Màn "đang tải" giữ tối thiểu 3 giây. Nếu là AOA, chờ tối đa 15 giây. Dùng chung đồng hồ capping của loại ads tương ứng (mục 5).
Sau đó
Lần đầu mở một mini-app (trừ Chat AI): thêm màn giới thiệu 3 step kèm 1 ô native ads, sau đó mới vào nội dung — không hiện lại các lần mở sau. Thoát dở giới thiệu thì lần mở kế tiếp vẫn hiện lại (chưa tính là "đã xem").
Nguồn: InhouseTaskActivity.kt (startOpenAdGate) · firebase/AppConfig.kt (InHouseAppConfig)

C · Quay lại app

🔄
Thoát ra rồi mở lại
Overlay "Welcome back"
Người dùng
Đang trong app, thoát ra ngoài, lát sau quay lại — tiến trình app vẫn còn sống (chuyển app nền rồi mở lại ngay, KHÔNG phải mở lại từ icon sau khi hệ điều hành đã dọn hẳn tiến trình — trường hợp đó đi qua mục 4A, không phải mục này).
Sẽ thấy
Nếu đang ở trong một mini-app: luôn hiện overlay "Welcome back" + AOA nhãn aoa_resume (không phụ thuộc cấu hình inter/AOA của lúc mở app). Nếu đang ở màn hình chính: chỉ show khi app chưa được đặt làm launcher mặc định.
Quy tắc thời gian
Capping 25 giây — tính riêng cho lúc quay lại app, không dùng chung đồng hồ với AOA lúc mở app lần đầu (mốc đó không bị capping).
KHÔNG show khi
Màn hình chính khi app đã là launcher mặc định (tránh spam ads mỗi lần bấm nút Home); màn Tối ưu ứng dụng; các màn Giới thiệu/Chọn ngôn ngữ/Onboarding; đang có sẵn 1 full-screen ads khác trên màn hình; ngay sau khi vừa bấm "Đặt làm launcher mặc định" hoặc vừa đóng một mini-app quay về màn hình chính (bỏ qua đúng 1 lần).
Nguồn: ads/AppOpenAdManager.kt · ads/ResumeAdSuppressor.kt · InhouseTaskActivity.kt (startResumeAd)

D · Đóng mini-app bằng nút trên màn

🚪
Bấm nút đóng / Xong trong app
KHÔNG phải nút back của hệ thống
Khi nào
Mũi tên quay lại (chevron), "Xong", "Về màn hình chính" trên các mini-app: Thời tiết, Đồng hồ, Máy tính, Hình nền. Riêng Chat AI, Cài đặt, Control Center, Thư viện ảnh KHÔNG kèm ads khi đóng bằng nút — xem lý do riêng theo từng app ở mục 6.
Sẽ thấy
inter ads show SAU khi thao tác đã hoàn tất (ví dụ đã lưu báo thức/ảnh xong), không chặn hành động của người dùng.
Quy tắc thời gian
Dùng chung đồng hồ capping inter ads của toàn app — mặc định 20 giây (mục 5), không có đồng hồ riêng cho riêng nút đóng.
Lưu ý
Đóng ads thì thao tác luôn hoàn tất — không bao giờ kẹt. Xem thêm hai quy tắc bất di bất dịch.
Nguồn: InhouseTaskActivity.kt (closeCurrent/closeWithAd/closesWithInterstitial)

E · Điều hướng trong Cài đặt

⚙️
Mở Cài đặt · vào màn con · quay lại
Gồm cả đổi tên / đổi icon app
Khi nào
Bấm vào một dòng cài đặt để mở màn con, bấm nút quay lại/về màn hình chính, bấm "Xong" ở màn Chọn ngôn ngữ, hoặc đóng popup mời nâng cấp Premium.
Sẽ thấy
Màn "đang tải" (kèm ô native ads nhỏ) rồi tới inter ads, xong ads mới chuyển màn thật.
Quy tắc thời gian
Dùng chung 1 đồng hồ capping với mọi nơi khác hiện inter ads trong toàn app (mở/đóng mini-app, đóng Premium…) — không có đồng hồ riêng cho Cài đặt.
Nguồn: ui/settings/SettingsHost.kt (SettingsActions.onShowInterstitialThen)

F · Xem reward ads để nhận thưởng

Không tự bật. Chỉ show khi người dùng chủ động bấm nút "Xem quảng cáo".

💬 Chat AI — hết lượt

Mỗi ngày được 5 lượt chat miễn phí (số này chỉnh được từ xa). Hết lượt → hộp thoại "Hết lượt chat hôm nay" với 2 nút: Xem quảng cáo (xem xong +1 lượt ngay, cho phép chat tiếp) hoặc Nâng cấp Premium. Không giới hạn số lần xem reward ads trong ngày — hết lượt lại xem tiếp được.

Nguồn: data/chat/ChatCreditStore.kt, ui/chat/ChatAiViewModel.kt (onWatchRewardForCredit)

🎨 Theme Store — mở khoá

Áp dụng cho món miễn phí nhưng chưa sở hữu (theme / gói icon / hình nền / widget). Bấm món đó → hộp thoại "Xem quảng cáo để mở khoá" hoặc "Mở khoá tất cả" (Premium). Xem xong → mở khoá vĩnh viễn đúng món vừa bấm, lần sau dùng lại không cần xem nữa. Riêng màn Ví xu có thêm mục xem reward ads để +1 xu, tối đa 3 lần/ngày (xu dùng để mua các món tính phí bằng xu thay vì xem ads).

Nguồn: data/themestore/CoinStore.kt, .code_index/modules/theme_store/coin.md (ThemeStoreViewModel.gateApply)
📌 Hết reward ads thì sao?
Bấm "Xem quảng cáo" mà Google không còn reward ads để trả (no-fill) → app thay bằng 1 inter ads, xem xong vẫn nhận thưởng như thường. Chi tiết + cách nhận biết ở mục 11.

5 Quy tắc capping (giãn cách)

"Capping" = khoảng thời gian tối thiểu giữa 2 lần show ads cùng loại. Mọi con số ở đây chỉnh được từ xa — xem Tần suất & thời gian chờ.

Loại ads Capping Áp dụng cho
inter ads 20 giây — đổi từ xa bằng inter_ad_capping_time Dùng 1 đồng hồ chung cho MỌI nơi hiện inter ads trong toàn app (mở mini-app, đóng mini-app, Cài đặt, đóng Premium…) — tính từ lúc người dùng thực sự thấy lại màn, không phải lúc bấm đóng.
AOA (lúc quay lại app) 25 giây — đổi từ xa bằng open_ad_capping_time Đồng hồ riêng, chỉ áp cho lúc quay lại app. Lúc mở app lần đầu (màn logo) hoặc mở mini-app lần đầu KHÔNG bị capping này — luôn được phép hiện.
native ads Không giới hạn Mỗi lần vào 1 màn có ô này, ô tự request ad mới — không đợi hết giờ. Cuộn ra/vào, đổi tab thì không request lại (mục 10). Load fail thì ô tự ẩn gọn — không còn cache nào để mượn.
reward ads Không giới hạn theo giây Xem được nhiều lần liên tiếp. Ngoại lệ: "xem lấy xu" trong Ví xu giới hạn 3 lần/ngày, reset lúc sang ngày mới. Inter ads thay cho reward ads bỏ qua capping 20 giây (mục 11).
native full ads (sau inter ads) Không có đồng hồ riêng Dùng chung 1 "khe" full-screen với inter ads/AOA — nên không bao giờ chồng lên 2 loại kia (mục 8). Đồng hồ capping của inter ads được đóng dấu lại lúc native full ads đóng, không phải lúc inter ads đóng.
Nguồn: firebase/AppConfig.kt · firebase/RemoteConfigManager.kt (khoá inter_ad_capping_time = 20, open_ad_capping_time = 25) · ads/InterstitialAdManager.kt · ads/SplashAppOpenAdLoader.kt
📌 Làm rõ cho BA/QA
  • Capping chỉ tính theo giây, không có giới hạn "mỗi ngày N lần" hay "mỗi phiên N lần" cho inter ads/AOA — 2 ngoại lệ theo-ngày duy nhất là Chat AI (5 lượt chat free/ngày) và xem-lấy-xu (3 lần/ngày), cả hai đều không phải full-screen ads.
  • Tắt app rồi mở lại: đồng hồ capping KHÔNG reset nếu tiến trình app vẫn còn sống trong bộ nhớ máy (chuyển app nền rồi quay lại). Nếu hệ thống đã giết hẳn tiến trình (mở lại như lần đầu) thì đồng hồ coi như về 0 vì nó chỉ tồn tại trong bộ nhớ, không lưu file.

6 Màn nào có ads gì

Bảng tra nhanh khi test hoặc khi giải thích cho khách. Full-screen Native Reward

Màn hình Full-screen (inter / AOA) Native ads Reward ads
Màn logo (Splash) — AOA — ô nhỏ lúc chờKhông
Giới thiệu (Intro)KhôngKhông
Chọn ngôn ngữKhông — 2 ô, đổi khi chọn dòng đầuKhông
Màn hình chính (Home) — chỉ khi chưa đặt làm launcher mặc định — Thư viện ứng dụng, DailyOpen, SpotlightKhông
Cài đặt (trang chính + ~12 màn con) — mỗi lần chuyển màn/back/về Home — hầu hết màn conKhông
Ẩn ứng dụng (Hidden Apps) (kế thừa Cài đặt)Không
Tối ưu ứng dụng (App Optimize)Cố tình không (chặn AOA lúc quay lại)Không
Thời tiết — mở + đóng bằng nútKhông
Đồng hồ — mở + đóng bằng nút + lưu báo thứcCố tình không (đề xuất, đã từ chối)Không
Máy tính — mở + đóng bằng nútKhôngKhông
Thư viện ảnh (Gallery, album, thùng rác) — chỉ lúc mở, đóng bằng nút KHÔNG có ads — danh sách album, chi tiết album, thùng rácKhông
Chat AI lúc mở — đóng bằng nút cố tình không có adsKhông — thêm lượt chat
Theme Store / Hình nền (Trending, Themes, Wallpaper, Icons, Widgets…) — mở + đóng bằng nút — rất nhiều vị trí — mở khoá món miễn phí
Ví xu (My Coin) (kế thừa mở Theme Store)Cố tình không (đã gỡ bỏ) — xem lấy xu
Control Center (thiết lập + tuỳ chỉnh) lúc mở — đóng do Cài đặt tự lo, không lặpCố tình không (giống overlay hệ thống, tránh bấm nhầm)Không
Popup mời nâng cấp Premium (mọi nơi) — chỉ lúc đóng ("Tiếp tục với quảng cáo")KhôngKhông
Xem ảnh/video toàn màn, cắt ảnh trong Thư viện ảnhKhôngCố tình không (giữa các ảnh)Không
Nguồn: .code_index/apps/_meta.md · ads_firebase/native.md · ads_firebase/flows.md · ui/LauncherViewModel.kt (Scene)

7 Khi nào KHÔNG có ads

QA gặp mấy trường hợp này là đúng, không phải lỗi.

🚫 Tắt toàn bộ ads
  • Người dùng Premium — sạch ads ở mọi màn.
  • Tắt từ xa bằng công tắc tổng ads_enabled — tắt là sạch cả app, không ngoại lệ. Xem Công tắc bật / tắt.
  • Chưa đồng ý popup riêng tư — chưa đồng ý thì chưa show ads nào.
  • Bản debug / releaseDev — tắt cứng trong code, xem cảnh báo ở mục 2.
📌 Làm rõ cho BA/QA
  • Nút "Xem quảng cáo" khi công tắc tổng tắt: ẩn/mất tác dụng, vì công tắc tổng tắt là tắt hết — không có ngoại lệ riêng cho reward ads. Reward ads có công tắc bật/tắt riêng tách khỏi 3 loại kia, có thể tắt độc lập từ xa.
  • Popup xin đồng ý riêng tư chỉ hiện ở những khu vực luật yêu cầu (Google tự nhận diện, chủ yếu EU/Anh và các nước có luật tương tự) — hiện ngay từ lần mở app đầu tiên, TRƯỚC khi bất kỳ ads nào được yêu cầu. Ở các khu vực không yêu cầu, app bỏ qua popup và chạy ads bình thường.
  • Cố tình không có ads ở một số vị trí dù công tắc đang bật: Đồng hồ (không có native ads), Ví xu (không có native ads), Control Center overlay (không có native ads — tránh bấm nhầm/vi phạm chính sách), Thư viện ảnh + Chat AI + Cài đặt + Control Center (đóng bằng nút KHÔNG kèm ads — xem mục 6), trình xem ảnh/video toàn màn (không chèn ads giữa các ảnh).

8 Hai quy tắc bất di bất dịch

Hai điều này luôn đúng. Thấy sai là bug, báo ngay.

✅ 1. Nút back của hệ thống không bao giờ ra ads
Bấm back bằng cử chỉ / nút của điện thoại thì chỉ đóng màn, không show ads. Ads lúc đóng chỉ thuộc về nút đóng trên màn (mũi tên quay lại trên header, "Xong", "Về màn hình chính").
✅ 2. Không bao giờ có 2 full-screen ads đè nhau
Đang có 1 full-screen ads trên màn hình thì cái thứ 2 bị bỏ, không đè lên. Thấy 2 cái chồng nhau → bug.

9 Checklist test nhanh

Danh sách rút gọn để QA chạy một vòng, đã kèm số giây/số lượt cụ thể lấy từ code. Phải chạy trên bản release — bản debug/releaseDev đang tắt hết ads (mục 2).

  • Premium: bật Premium → rà đủ các màn ở mục 6, phải không còn ads nào.
  • Công tắc tổng: tắt từ xa → cả app sạch ads.
  • Mở app: vào được app kể cả mạng yếu — timeout cứng của màn logo là 20 giây, không treo quá mốc này.
  • Mở mini-app: màn "đang tải" giữ tối thiểu 3 giây rồi mới tới ads, chờ tối đa 15 giây, không che ngay lập tức.
  • Capping: làm 2 thao tác liền nhau trong vòng 20 giây (inter ads) / 25 giây (AOA lúc quay lại app) → chỉ show 1 lần.
  • Không đè nhau: không bao giờ thấy 2 full-screen ads cùng lúc.
  • Nút back hệ thống: back bằng cử chỉ / nút máy → không có ads.
  • Không kẹt: đóng ads giữa một thao tác → thao tác vẫn hoàn tất bình thường.
  • Reward ads: Chat AI xem xong +1 lượt ngay (mặc định 5 lượt free/ngày); Theme Store mở khoá vĩnh viễn đúng món đã bấm, không hỏi lại; Ví xu +1 xu/lần, tối đa 3 lần/ngày.
  • Reward ads no-fill: nếu hiện ra inter ads thay vì reward ads thì vẫn phải nhận thưởng (mục 11). Không nhận được = bug.
  • Native ads: đang load thấy ô mờ, lỗi mạng thì ô tự ẩn, không để ô trống hay crash.
  • Native ads không request lại: cuộn ô ads ra khỏi màn rồi cuộn lại, hoặc đổi tab rồi quay lại trong vòng 3 phút → vẫn đúng ad cũ, không thấy khung mờ load lại (mục 10).
  • Popup riêng tư: máy mới cài (ở khu vực yêu cầu) → hiện popup trước, chưa đồng ý thì chưa có ads.
🔗 Kiểm sự kiện kèm theo
Test ads xong nên kiểm luôn sự kiện gửi lên có đúng không → Checklist QA kiểm sự kiện.

10 Cách app load ads — cập nhật chiều 06/08/2026

Bản cập nhật này không thêm vị trí ads nàokhông tăng tần suất show — chỉ đổi cách load để mỗi ad show ra chắc chắn tính được tiền (không còn bind ad đã show/đã hết hạn ở nơi khác). Đọc mục này để không báo sai bug và để hiểu vì sao request tăng nhưng số lần ads đập vào mắt người dùng không tăng (thậm chí no-fill thì ẩn ô hẳn, không còn ad nào để show thay).

📖 Luật gốc của native ads bây giờ — nhớ đúng một dòng này
Ad load trực tiếp > không show gì. Không còn bước "ad trong cache" ở giữa — native ads (kể cả native full) không còn cache từ chiều 06/08/2026.

❌ Trước đây (tới sáng 06/08)

  • Ô native ads preload trước rồi giữ lại dùng sau, hoặc mượn ad từ một kho cache chung.
  • Ad về muộn (người dùng đã rời màn) → từng có lúc mất trắng, có lúc được cache lại cho màn sau.
  • Load fail thì có lúc mượn ad từ cache để vẫn show được — nhưng ad mượn có thể đã show ở màn khác rồi, Google không tính thêm lượt xem lần mượn lại.
  • Cuộn ô ads ra khỏi màn rồi cuộn lại, hoặc đổi tab rồi quay lại → load lại từ đầu, tốn một lượt request mà không thêm lượt show.

✅ Bây giờ (từ chiều 06/08)

  • Mỗi lần vào màn, ô request ad mới và gắn ngay khi ad về. Không preload, không cache, không mượn ad của ô khác.
  • Load fail / no-fill → ô tự ẩn gọn ngay, không có gì để lấy ra show thay.
  • Ad về muộn (ô đã dispose) → destroy luôn, không giữ lại cho màn sau dùng.
  • Ô vẫn tự giữ đúng ad của chính nó trong lúc còn ở màn/tab đó — cuộn ra/vào và đổi tab trong thời gian ngắn không request lại (đây KHÔNG phải cache dùng chung, chỉ là ô chưa bị dispose) — xem phần dưới. Ở lại quá 50 phút thì ô tự refresh ad mới (né hạn thật ~1 giờ của native AdMob).
  • (06/08) Ad chỉ được gắn vào ô khi ô đó thật sự lọt vào màn hình — xem phần dưới.

2 trường hợp — bảng tra cho QA

Không còn cache nên chỉ còn đúng 2 nhánh: load về có ad hay không.

# Load trực tiếp Người dùng thấy
1fillShow ad mới ngay khi về
2no-fill / load failẨn ô — không để khung trống, không show ad nào khác thay
Nguồn: ads/NativeAdLoader.kt · ui/components/AdMobNativeAd.kt (NativeSlotState) · ads/NativeFullAdManager.kt

Vì sao bỏ cache — trade-off chủ nhân đã chọn

Mục Giá trị / nghĩa là
Lịch sử ngắn Sáng 06/08/2026 app từng đổi sang 1 cache CHUNG 50 phút cho native ads + native full ads (ad load trực tiếp ghi đè ad trong cache, load fail thì mượn cache). Chiều cùng ngày, chủ nhân yêu cầu gỡ hẳn — file giữ cache đó (ads/NativeFallbackPool.kt) đã bị xoá khỏi code. Đừng cấu hình/tài liệu nào nhắc lại kho cache 50 phút này như đang chạy — nó không còn tồn tại.
Đổi lại là gì Mỗi placement tự sở hữu ad nó load, tự destroy khi rời màn. Không còn khái niệm "kho dự phòng dùng chung" — inter ads, AOA, native ads, native full ads đều không có cache.
Cái được Không ad nào bị bind 2 lần, không có màn nào show lại một ad đã show ở màn khác (mất tiền lượt đó) — mỗi lượt show chắc chắn là một lượt impression mới, tính tiền được.
Cái đánh đổi Số lượt request tăng (mỗi lần vào màn là 1 request mới, không có cách nào tránh) và no-fill thì màn đó show hẳn không có ads (trước còn có thể mượn cache để vẫn có gì hiện ra). Đây là lựa chọn có chủ đích của chủ nhân, không phải bug.
Nguồn: ads/NativePlacements.kt (28 vị trí native ads) · ads/NativeAdLoader.kt (không cache) · ui/components/AdMobNativeAd.kt (NativeSlotState) · ads/NativeFullAdManager.kt (không borrow) · ads/InterstitialAdManager.kt (không có cache)
✅ Marketing cần biết: không còn rủi ro "1 ad show 2 lần không ra tiền thêm"
Vì không còn cache nên không còn tình huống ad đã show ở màn A bị mượn ra show lại ở màn B. Mỗi lượt show giờ luôn gắn với một request/load mới của riêng màn đó. Nếu trước đây bạn từng nghe cảnh báo "showrate tụt vì ad bị mượn lại" thì cảnh báo đó đã hết hiệu lực kể từ khi bỏ cache (chiều 06/08/2026) — số request tăng lên là đánh đổi thấy được, không phải hiện tượng lạ cần điều tra thêm.

Không request lại khi cuộn hoặc đổi tab

Đây là phần thay đổi nhiều nhất cho QA — trước đây cứ cuộn/đổi tab là thấy khung mờ load lại.

Người dùng làm gì Ads xử lý thế nào
Cuộn ô ads ra khỏi màn rồi cuộn lại Dùng lại đúng ad cũ, không request ad mới, không show khung mờ (shimmer).
Đổi tab rồi quay lại (Theme Store 5 tab · Thư viện ảnh Library/Albums · Cài đặt Chính/Tính năng) Dùng lại đúng ad cũ nếu quay lại trong 3 phút. Rời tab quá 3 phút → app nhả ad ra cho đỡ tốn bộ nhớ, quay lại thì request ad mới.
Rời hẳn màn / đóng app con rồi mở lại Request ad mới.
Ở lại một màn rất lâu (> 50 phút) rồi quay lại ô ads Ad đã quá hạn → request ad mới.
Lần load trước bị fail Chờ 30 giây mới retry, không request dồn dập.
Vuốt giữa DailyOpen ⟷ Thư viện ứng dụng ở màn hình chính Vẫn request lại — trường hợp đã biết, chủ động chưa sửa. Không phải bug mới.
Nguồn: bug_pool/misc/BP-0157 (cuộn lazy list) · BP-0158 (đổi tab) · ui/components/AdMobNativeAd.kt (NativeSlotState) · ui/themestore/ThemeStoreRoot.kt · ui/gallery/GalleryRoot.kt · ui/settings/SettingsHost.kt
🎛 Đổi được từ xa: 3 phút "rời tab thì nhả ad"
Công tắc mới native_tab_idle_seconds (giây, mặc định 180). Đặt 0 = nhả ad ngay khi rời tab → tab nào quay lại cũng request ad mới (quay về hành vi cũ, dùng khi cần cứu bộ nhớ). Chi tiết ở Tần suất & thời gian chờ.

Chỗ bắn ad_displayed của native ads đổi — vẫn cùng thời điểm, đúng hơn khi ad bị dùng lại nhiều nơi (06/08/2026)

Khác thay đổi "vào màn hình" ngay dưới đây (đổi lúc nào gắn ad) — mục này chỉ đổi ai bắn log, không đổi thời điểm hay số lượng thật.

❌ Trước đây

Log bắn từ chỗ Compose gắn ad vào ô hiển thị (setNativeAdTracked trong AdMobNativeAd.kt) — mỗi lần một NativeAdView nhận ad mới (hoặc đổi ad) là bắn. Ô nào bị Compose recompose/rebind nhiều lần (đổi cấu hình, tái sử dụng view trong LazyList) có nguy cơ tính dư.

✅ Bây giờ

Log bắn từ onAdImpression của GMA (AdListener trong ads/NativeAdLoader.kt) — cùng chỗ bắn af_native_displayed. GMA tự đảm bảo mỗi NativeAd chỉ bắn đúng 1 lần, bất kể Compose bind/rebind bao nhiêu lần. Chỗ Compose cũ giờ chỉ còn 1 counter dev-log (NativeAdView BOUND) để debug, không liên quan analytics.

Nguồn: ads/NativeAdLoader.kt (displayedCount + onAdImpression) · ui/components/AdMobNativeAd.kt (setNativeAdTracked, comment tại đầu file)

Chỉ tính "đã show" khi ô ads thật sự lọt vào màn hình (mới 06/08/2026)

Thay đổi này không đụng gì tới số lượt requestkhông đổi vị trí ads — chỉ đổi thời điểm app gắn ad vào ô và bắn sự kiện đã show.

❌ Trước đây

  • Màn hình dựng sẵn cả những ô còn nằm dưới màn (danh sách dài, trang kế bên của Home) để cuộn cho mượt.
  • Ô dựng sẵn đó gắn ad luôn và bắn ad_displayed — dù người dùng chưa nhìn thấy.
  • Google không tính lượt xem kiểu đó → số ad_displayed của app cao hơn số lượt hiển thị trên AdMob, nhìn như đang mất tiền.

✅ Bây giờ

  • Ô chỉ gắn ad khi khung của nó thật sự chạm vào vùng nhìn thấy của màn hình.
  • Chưa cuộn tới thì ô giữ nguyên chiều cao (không nhảy layout), chưa bắn sự kiện nào.
  • Cuộn tới một lần rồi thì thôi theo dõi — cuộn qua cuộn lại không bắn thêm lần nữa.
📉 Ảnh hưởng tới số liệu — đọc trước khi so với tháng trước
  • ad_displayed / native_show của native ads giảm từ 06/08. Phần giảm là những lượt trước đây đếm thừa, Google vốn không tính tiền → doanh thu không giảm theo.
  • ad_request / ad_loaded không đổi — app vẫn request y như cũ.
  • Tỷ lệ displayed / loaded vì thế tụt xuống, và số của app từ nay sát với AdMob hơn. Muốn so với trước 06/08 thì so doanh thu, đừng so ad_displayed.
📍 Áp dụng ở đâu
Các ô native ads nằm trong màn cuộn được / có tab: Cài đặt và các màn con, Theme Store, Thư viện ảnh, trang DailyOpen, App Library, Spotlight. Những ô luôn nằm giữa màn (màn đang tải, màn chọn ngôn ngữ, Thời tiết, native full ads) không đổi gì — chúng vốn đã ở trong tầm nhìn ngay khi hiện. Nguồn: ui/components/AdMobNativeAd.kt (trackOnScreen / isWithinWindow) · ui/themestore/components/StoreItemNativeAd.kt · bug_pool/misc/BP-0159
✅ QA: mấy triệu chứng này là ĐÚNG, không phải bug
  • Cuộn xuống tới ô ads mới thấy ad hiện ra (trước đó là khung mờ) — đúng, ô chờ vào màn hình rồi mới gắn ad.
  • Cuộn ra/vào hoặc đổi tab mà ad không đổi, không có khung mờ — cố tình như vậy để đỡ tốn lượt request (ô chưa bị dispose nên vẫn giữ đúng ad của chính nó, không phải cache dùng chung). Đúng.
  • Đổi tab để lâu (>3 phút) rồi quay lại thì thấy ad mới — đúng, app đã nhả ad cũ.
  • Ô native ads tự ẩn — xảy ra ngay khi load fail hoặc no-fill, vì không còn cache nào để mượn nữa. Vẫn đúng.
  • Bấm "Xem quảng cáo" mà hiện ra inter ads — hết reward ads nên thay thế, vẫn nhận thưởng (mục 11). Đúng.
🚫 Còn đây là bug — báo ngay
  • Cùng một ad show hai lần liền trên cùng một màn mà không rời màn.
  • Hai full-screen ads đè nhau (xem mục 8).
  • Inter ads show trong vòng 20 giây kể từ lần trước (trừ inter ads thay reward ads — mục 11).
  • Ô native ads để khung trống trắng mãi không ẩn cũng không show gì.
  • Cuộn ra/vào một ô ads mà mỗi lần lại thấy khung mờ load lại.
⚠️ Công tắc từ xa đã hết tác dụng
native_isCacheLoading — trước dùng để bật/tắt việc giữ sẵn ad cho màn "đang tải". Cơ chế giữ sẵn đó đã bị bỏ, nên đổi công tắc này không còn tác dụng gì. Xem bảng công tắc theo màn.

11 Hết reward ads → thay bằng inter ads

Thay đổi ngày 05/08/2026. Trước đây hết reward ads thì người dùng nhận thông báo "chưa có quảng cáo, thử lại sau" và không nhận được thưởng — mất cả doanh thu lẫn thiện chí. Giờ app thay bằng một inter ads và vẫn trả thưởng.

🎁
Bấm "Xem quảng cáo" nhưng Google hết reward ads
no-fill
Người dùng
Bấm nút xem quảng cáo để mở khoá / lấy xu / thêm lượt chat.
Sẽ thấy
Màn "đang tải" (kèm ô native ads) như thường. Không có reward ads → app request luôn 1 inter ads, ad đó phủ màn thì tính là đã xem xong → thưởng vào ngay.
Timeout
Timeout của reward ads là 45 giây; hết mốc đó thì không đổi sang inter ads nữa. Nếu đã đổi, inter ads có timeout 30 giây của riêng nó — nên xấu nhất màn "đang tải" sống khoảng 75 giây trước khi báo "chưa có quảng cáo".
Khác gì inter ads thường
Bỏ qua capping 20 giây (vì người dùng chủ động bấm, chặn thì lượt thay thế thành vô nghĩa) và không nối native full ads phía sau (lượt đó đã "nợ" người dùng một phần thưởng, chồng thêm màn chắn nữa là quá nhiều).
KHÔNG đổi khi
Inter ads đang bị tắt từ xa (inter_ad_on_off), hoặc đang có một full-screen ads khác trên màn, hoặc màn hình vừa bị đóng. Lúc đó về đúng luồng cũ: báo "chưa có quảng cáo", không trả thưởng.
✅ Điều luôn đúng
Người dùng xem xong ads (loại nào cũng vậy) là phải nhận được thưởng. Nếu ads đã phủ kín màn mà thưởng không vào → bug, báo ngay.
📊 Đọc số thế nào
Lượt thay thế này ghi nhận dưới tên vị trí inter_reward_fallback trong các sự kiện ads (ad_request / ad_loaded / ad_displayed…). Muốn biết "bao nhiêu lượt reward bị hụt phải thay bằng inter" thì lọc placement = inter_reward_fallback — xem nhóm sự kiện Ads.
⚠️ Lượt fallback KHÔNG bắn reward_complete
Nhánh này chỉ trả thưởng qua callback trong app ngay lúc inter phủ màn (onShown) — code hiện không gọi Analytics.rewardComplete() ở nhánh này (khác nhánh reward ads thật, có gọi ở OnUserEarnedRewardListener). Nghĩa là reward_complete đang đếm thiếu đúng số lượt fallback này — muốn đếm tổng "bao nhiêu lượt user thật sự nhận được thưởng" phải cộng thêm ad_displayed lọc placement = inter_reward_fallback. Cân nhắc báo dev thêm Analytics.rewardComplete() vào nhánh tryInterFallback (ads/RewardedAdManager.kt) để số liệu gộp về đúng 1 sự kiện.
Nguồn: ads/RewardedAdManager.kt (tryInterFallback) · ads/InterstitialAdManager.kt (allowNativeFull=false, onShown) · ads/AdUnitIds.kt (INTER_REWARD_FALLBACK)