From 905c97e415b6c91b04f63f706d0d2e8e11472751 Mon Sep 17 00:00:00 2001 From: ocean <503259349@qq.com> Date: Tue, 2 Dec 2025 11:49:50 +0800 Subject: [PATCH] =?UTF-8?q?1.=E6=B7=BB=E5=8A=A0admob=E5=B9=BF=E5=91=8A=202?= =?UTF-8?q?.=E6=B7=BB=E5=8A=A0=E5=B9=BF=E5=91=8A=E6=89=93=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/studiobot.xml | 6 + app/src/main/AndroidManifest.xml | 5 +- .../com/all/pdfreader/pdf/reader/PRApp.kt | 5 +- .../all/pdfreader/pdf/reader/ad/AdInstLoad.kt | 33 ++-- .../pdfreader/pdf/reader/ad/AdInstShower.kt | 33 +++- .../all/pdfreader/pdf/reader/ad/AdsInsUtil.kt | 39 +++- .../pdfreader/pdf/reader/ad/BannerManager.kt | 132 ++++++++++++++ .../pdf/reader/ad/InstAdCacheManager.kt | 50 ++++- .../pdfreader/pdf/reader/ad/NativeAdCache.kt | 74 ++++++++ .../pdf/reader/ui/act/BaseActivity.kt | 6 - .../pdf/reader/ui/act/MainActivity.kt | 92 ++++++---- .../pdf/reader/ui/act/MergePdfActivity.kt | 2 + .../pdf/reader/ui/act/PdfResultActivity.kt | 12 +- .../pdf/reader/ui/act/PdfViewActivity.kt | 34 ++++ .../pdf/reader/ui/act/SplashActivity.kt | 73 +++++++- .../pdf/reader/ui/act/SplitPdfActivity.kt | 2 + .../reader/ui/dialog/ExitDialogFragment.kt | 171 ++++++++++++++++++ .../res/drawable/dr_rc_top_12_bg_color.xml | 9 + .../main/res/drawable/gnt_outline_shape.xml | 5 + .../drawable/gnt_rounded_corners_shape.xml | 17 ++ .../gnt_top_round_12_outline_shape.xml | 11 ++ app/src/main/res/layout/activity_main.xml | 6 + app/src/main/res/layout/activity_pdf_view.xml | 8 +- .../layout/ad_admob_native_exit_layout.xml | 113 ++++++++++++ app/src/main/res/layout/dialog_exit.xml | 25 +++ app/src/main/res/values/colors.xml | 10 + app/src/main/res/values/dimens.xml | 19 ++ app/src/main/res/values/strings.xml | 2 + app/src/main/res/values/styles.xml | 12 ++ 29 files changed, 923 insertions(+), 83 deletions(-) create mode 100644 .idea/studiobot.xml create mode 100644 app/src/main/java/com/all/pdfreader/pdf/reader/ad/BannerManager.kt create mode 100644 app/src/main/java/com/all/pdfreader/pdf/reader/ad/NativeAdCache.kt create mode 100644 app/src/main/java/com/all/pdfreader/pdf/reader/ui/dialog/ExitDialogFragment.kt create mode 100644 app/src/main/res/drawable/dr_rc_top_12_bg_color.xml create mode 100644 app/src/main/res/drawable/gnt_outline_shape.xml create mode 100644 app/src/main/res/drawable/gnt_rounded_corners_shape.xml create mode 100644 app/src/main/res/drawable/gnt_top_round_12_outline_shape.xml create mode 100644 app/src/main/res/layout/ad_admob_native_exit_layout.xml create mode 100644 app/src/main/res/layout/dialog_exit.xml diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml new file mode 100644 index 0000000..539e3b8 --- /dev/null +++ b/.idea/studiobot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c7d19bc..b454418 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,7 @@ + + + + android:value="ca-app-pub-5717753826607607~1850604454" /> diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/PRApp.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/PRApp.kt index 36c7d0e..4cfe60d 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/PRApp.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/PRApp.kt @@ -46,10 +46,11 @@ class PRApp : Application() { UpLoadManager.init(context = this, tag = "PRApp_upload_task") { _, _ -> } // 广告初始化 MobileAds.initialize(this) + initMobileAds() } - private fun initMobileAds(){ - val testDeviceIds = listOf("TEST_DEVICE_ID") + private fun initMobileAds() { + val testDeviceIds = listOf("9A96E667D69B45F744FD7D724DF8B093") val configuration = RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build() MobileAds.setRequestConfiguration(configuration) } diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstLoad.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstLoad.kt index e06bb73..792c469 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstLoad.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstLoad.kt @@ -19,48 +19,55 @@ class AdInstLoad( } private fun loadAd() { - //多处调用load,也不会重复、不影响缓存广告、展示安全 + Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 开始加载") + // 是否存在缓存广告、展示安全 val cachedAd = InstAdCacheManager.instance.getAdCache(placement) if (cachedAd != null) { - Log.d("ocean","广告存在缓存,跳过加载,返回成功") + Log.d(AdsInsUtil.Placement.TAG,"广告存在缓存,跳过加载,返回成功") //缓存广告有效,跳过加载,返回成功 adLoadListener?.loaded(cachedAd) return } + // 如果正在加载(其他地方已 load) 加入等待队列 + if (InstAdCacheManager.instance.isLoading(placement)) { + Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 正在加载中,加入等待队列") + InstAdCacheManager.instance.addPendingListener(placement, adLoadListener) + return + } + + // 本次为真正开始加载 标记 loading(并加入当前 listener) + InstAdCacheManager.instance.markLoading(placement, adLoadListener) + val adUnitId = AdsInsUtil.adUnitIdMap[placement] ?: run { val errorMsg = "No AdUnitId for $placement" - Log.d("ocean","没找到对应的广告ID->$placement") - adLoadListener?.loadFailed(errorMsg) - AnalyticsUtils.logAdEvent( - placement, - AnalyticsUtils.AdEvent.LOAD_FAIL, - null, - errorMsg - ) + Log.d(AdsInsUtil.Placement.TAG,"没找到对应的广告ID->$placement") + InstAdCacheManager.instance.notifyFail(placement, errorMsg) return } AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ) - + Log.d(AdsInsUtil.Placement.TAG,"adUnitId->$adUnitId") InterstitialAd.load( activity, adUnitId, AdRequest.Builder().build(), object : InterstitialAdLoadCallback() { override fun onAdLoaded(ad: InterstitialAd) { + Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 加载成功") InstAdCacheManager.instance.setAdCache(placement, ad) AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED) - adLoadListener?.loaded(ad) + InstAdCacheManager.instance.notifySuccess(placement, ad) } override fun onAdFailedToLoad(adError: LoadAdError) { + Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 加载失败") AnalyticsUtils.logAdEvent( placement, AnalyticsUtils.AdEvent.LOAD_FAIL, adError.code, adError.message ) - adLoadListener?.loadFailed(adError.toString()) + InstAdCacheManager.instance.notifyFail(placement, adError.toString()) } }, ) diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstShower.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstShower.kt index 7262911..25701b2 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstShower.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstShower.kt @@ -1,38 +1,52 @@ package com.all.pdfreader.pdf.reader.ad import android.app.Activity -import com.all.pdfreader.pdf.reader.ad.AdsInsUtil.AdPlacement +import android.util.Log import com.all.pdfreader.pdf.reader.util.AnalyticsUtils import com.google.android.gms.ads.AdError import com.google.android.gms.ads.FullScreenContentCallback class AdInstShower( private val activity: Activity, - private val placement: AdPlacement, + private val placement: AdsInsUtil.AdPlacement, private val showListener: ShowListener? ) { + companion object { + private const val SHOW_INTERVAL = 60_000L // 60秒间隔 + private var lastShowTimestamp = 0L + } + init { showAd() } private fun showAd() { + // ======= 新增:全局展示间隔限制 启动页不处理 ======= + if(placement != AdsInsUtil.AdPlacement.SPL_AND_INTO_HOME){ + val now = System.currentTimeMillis() + if (now - lastShowTimestamp < SHOW_INTERVAL) { + val errorMsg = "广告展示过于频繁, 请等待 ${(SHOW_INTERVAL - (now - lastShowTimestamp)) / 1000}s(间隔限制60s)" + showListener?.onAdShowFailed(errorMsg) + Log.d(AdsInsUtil.Placement.TAG, "showAd->$errorMsg") + return + } + } + val interstitialAd = InstAdCacheManager.instance.getAdCache(placement) ?: run { - val errorMsg = "InterstitialAd cache is null for place = $placement" - AnalyticsUtils.logAdEvent( - placement, - AnalyticsUtils.AdEvent.SHOW_FAIL, - null, - errorMsg - ) + val errorMsg = "插页式广告缓存为空 = $placement" showListener?.onAdShowFailed(errorMsg) + Log.d(AdsInsUtil.Placement.TAG, "showAd->$errorMsg") return } interstitialAd.fullScreenContentCallback = object : FullScreenContentCallback() { override fun onAdShowedFullScreenContent() { + // ======= 新增:展示成功 记录时间 ======= + lastShowTimestamp = System.currentTimeMillis() + Log.d(AdsInsUtil.Placement.TAG, "广告 $placement 展示成功") AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.SHOW_SUC) showListener?.onAdShown() } @@ -52,6 +66,7 @@ class AdInstShower( ) InstAdCacheManager.instance.remove(placement) showListener?.onAdShowFailed(adError.toString()) + Log.d(AdsInsUtil.Placement.TAG, "广告 $placement 展示失败->${adError}") } override fun onAdClicked() { diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdsInsUtil.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdsInsUtil.kt index 774ad9c..a0a9019 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdsInsUtil.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdsInsUtil.kt @@ -5,16 +5,17 @@ import android.app.Activity object AdsInsUtil { /** 广告位定义(可扩展) */ enum class AdPlacement(val tag: String) { - SPL_AND_INTO_HOME(Placement.SPL_AND_INTO_HOME), INT_AND_TOPDF(Placement.INT_AND_TOPDF), INT_AND_PDFTOHOME( - Placement.INT_AND_PDFTOHOME - ), - INT_AND_MERGE(Placement.INT_AND_MERGE), INT_AND_SPLIT(Placement.INT_AND_SPLIT), NATIVE_AND_EXIT( - Placement.NATIVE_AND_EXIT - ), + SPL_AND_INTO_HOME(Placement.SPL_AND_INTO_HOME), + INT_AND_TOPDF(Placement.INT_AND_TOPDF), + INT_AND_PDFTOHOME(Placement.INT_AND_PDFTOHOME), + INT_AND_MERGE(Placement.INT_AND_MERGE), + INT_AND_SPLIT(Placement.INT_AND_SPLIT), + NATIVE_AND_EXIT(Placement.NATIVE_AND_EXIT), BAN_AND_HOMEPAGE(Placement.BAN_AND_HOMEPAGE) } object Placement { + const val TAG = "ocean-ad" /** * 启动页插页 */ @@ -53,7 +54,7 @@ object AdsInsUtil { // 广告位对应的广告ID val adUnitIdMap: Map = mapOf( - AdPlacement.SPL_AND_INTO_HOME to "ca-app-pub-5717753826607607/5211991318", + AdPlacement.SPL_AND_INTO_HOME to "ca-app-pub-5717753826607607/9606833210", AdPlacement.INT_AND_TOPDF to "ca-app-pub-5717753826607607/5308904672", AdPlacement.INT_AND_PDFTOHOME to "ca-app-pub-5717753826607607/7085128570", AdPlacement.INT_AND_MERGE to "ca-app-pub-5717753826607607/8928693282", @@ -74,9 +75,33 @@ object AdsInsUtil { return AdInstLoad(act, adPlacement, loadListener) } + fun showAd( + act: Activity, adPlacement: AdPlacement + ): AdInstShower { + return AdInstShower(act, adPlacement, null) + } + fun showAd( act: Activity, adPlacement: AdPlacement, listener: ShowListener? ): AdInstShower { return AdInstShower(act, adPlacement, listener) } + + fun showFinishAd(act: Activity, adPlacement: AdPlacement, onFinish: () -> Unit) { + showAd( + act = act, + adPlacement = adPlacement, + listener = object : ShowListener { + override fun onAdShown() {} + override fun onAdClicked() {} + override fun onAdShowFailed(string: String) { + onFinish() + } + + override fun onAdClosed() { + onFinish() + } + } + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/BannerManager.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/BannerManager.kt new file mode 100644 index 0000000..92598b1 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/BannerManager.kt @@ -0,0 +1,132 @@ +package com.all.pdfreader.pdf.reader.ad + +import android.app.Activity +import android.util.DisplayMetrics +import android.util.Log +import android.view.View +import android.widget.FrameLayout +import com.all.pdfreader.pdf.reader.util.AnalyticsUtils +import com.google.android.gms.ads.AdListener +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.AdSize +import com.google.android.gms.ads.AdView +import com.google.android.gms.ads.LoadAdError + +object BannerManager { + + private data class BannerInfo(val adView: AdView, val container: FrameLayout, var isLoaded: Boolean = false) + private val bannerMap = mutableMapOf() + + //因为有多个activity来展示这个banner,所以我们使用act的名称+广告位做为key + private fun key(placement: AdsInsUtil.AdPlacement, activity: Activity): String { + return "${placement.name}_${activity::class.java.simpleName}" + } + + fun loadBanner( + activity: Activity, + container: FrameLayout, + placement: AdsInsUtil.AdPlacement, + onLoaded: (() -> Unit)? = null + ) { + val currentKey = key(placement, activity)//得到当前的key + val adUnitId = AdsInsUtil.adUnitIdMap[placement] ?: run { + Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner 未找到广告ID: $placement") + container.visibility = View.GONE + return + } + + Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 开始加载") + AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ) + + container.post { + container.visibility = View.GONE + + val adSize = getAdSize(activity, container) + + // 销毁旧 AdView + bannerMap[currentKey]?.adView?.destroy() + + val adView = AdView(activity).apply { + this.adUnitId = adUnitId + setAdSize(adSize) + adListener = object : AdListener() { + override fun onAdLoaded() { + Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 加载成功") + AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED) + + val info = bannerMap[currentKey] + info?.isLoaded = true + + // 广告加载成功后显示容器 + info?.container?.visibility = View.VISIBLE + info?.container?.alpha = 0f + info?.container?.animate()?.alpha(1f)?.setDuration(300)?.start() + + onLoaded?.invoke() + } + + override fun onAdFailedToLoad(error: LoadAdError) { + Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 加载失败: ${error.message}") + AnalyticsUtils.logAdEvent( + placement, + AnalyticsUtils.AdEvent.LOAD_FAIL, + error.code, + error.message + ) + val info = bannerMap[currentKey] + info?.isLoaded = false + info?.container?.visibility = View.GONE + } + } + } + + bannerMap[currentKey] = BannerInfo(adView, container) + + container.removeAllViews() + container.addView(adView) + + adView.loadAd(AdRequest.Builder().build()) + } + } + + fun onResume(activity: Activity, placement: AdsInsUtil.AdPlacement) { + Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 恢复广告") + val info = bannerMap[key(placement, activity)] ?: return + info.adView.resume() + if (info.isLoaded) { + info.container.visibility = View.VISIBLE + info.container.alpha = 0f + info.container.animate().alpha(1f).setDuration(300).start() + } else { + info.container.visibility = View.GONE + } + } + + fun onPause(activity: Activity, placement: AdsInsUtil.AdPlacement) { + val info = bannerMap[key(placement, activity)] ?: return + info.container.visibility = View.GONE//暂停后就隐藏,让回调来显示 + info.adView.pause() + Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 暂停广告") + } + + fun onDestroy(activity: Activity, placement: AdsInsUtil.AdPlacement) { + val key = key(placement, activity) + bannerMap[key]?.adView?.destroy() + bannerMap.remove(key) + Log.d(AdsInsUtil.Placement.TAG, "${activity::class.java.simpleName} Banner $placement 销毁广告") + } + + private fun getAdSize(activity: Activity, container: FrameLayout): AdSize { + val display = activity.windowManager.defaultDisplay + val outMetrics = DisplayMetrics() + display.getMetrics(outMetrics) + + var adWidthPixels = container.width.toFloat() + if (adWidthPixels == 0f) adWidthPixels = outMetrics.widthPixels.toFloat() + + val density = outMetrics.density + val adWidth = (adWidthPixels / density).toInt() + + return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(activity, adWidth) + } +} diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/InstAdCacheManager.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/InstAdCacheManager.kt index 915dcbb..3a9ef9a 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/InstAdCacheManager.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/InstAdCacheManager.kt @@ -3,14 +3,56 @@ package com.all.pdfreader.pdf.reader.ad import com.google.android.gms.ads.interstitial.InterstitialAd class InstAdCacheManager { + private val mAdCacheDict: MutableMap = mutableMapOf() + // 是否正在加载 + private val loadingSet = mutableSetOf() + + // 等待回调的 listener(多个 load 时会加入这里) + private val pendingListeners = + mutableMapOf>() + companion object { val instance: InstAdCacheManager by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { InstAdCacheManager() } } + /** ============= 加载状态控制 ============= */ + + fun isLoading(place: AdsInsUtil.AdPlacement) = loadingSet.contains(place) + + fun markLoading(place: AdsInsUtil.AdPlacement, listener: LoadListener?) { + loadingSet.add(place) + addPendingListener(place, listener) + } + + fun addPendingListener(place: AdsInsUtil.AdPlacement, listener: LoadListener?) { + if (listener == null) return + + val list = pendingListeners[place] ?: mutableListOf() + if (!list.contains(listener)) { + list.add(listener) + } + pendingListeners[place] = list + } + + + fun notifySuccess(place: AdsInsUtil.AdPlacement, ad: InterstitialAd) { + pendingListeners[place]?.forEach { it?.loaded(ad) } + pendingListeners.remove(place) + loadingSet.remove(place) + } + + fun notifyFail(place: AdsInsUtil.AdPlacement, msg: String) { + pendingListeners[place]?.forEach { it?.loadFailed(msg) } + pendingListeners.remove(place) + loadingSet.remove(place) + } + + /** ============= 广告缓存逻辑 ============= */ + fun setAdCache(place: AdsInsUtil.AdPlacement, adCache: InterstitialAd) { mAdCacheDict[place] = CachedAd(adCache) } @@ -19,7 +61,7 @@ class InstAdCacheManager { val cached = mAdCacheDict[place] return if (cached != null && cached.isValid()) cached.ad else { - mAdCacheDict.remove(place) // 过期广告清理 + mAdCacheDict.remove(place) // 清理过期 null } } @@ -28,12 +70,14 @@ class InstAdCacheManager { mAdCacheDict.remove(place) } - + /** + * 包装广告 + 时间 + */ data class CachedAd( val ad: InterstitialAd, val loadedAt: Long = System.currentTimeMillis() ) { - // 广告有效期, 1 小时 + // 广告有效期 1 小时 fun isValid(): Boolean = System.currentTimeMillis() - loadedAt < 3600_000 } } diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/NativeAdCache.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/NativeAdCache.kt new file mode 100644 index 0000000..dfb60ab --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/NativeAdCache.kt @@ -0,0 +1,74 @@ +package com.all.pdfreader.pdf.reader.ad + +import android.app.Activity +import android.content.Context +import android.util.Log +import com.all.pdfreader.pdf.reader.util.AnalyticsUtils +import com.google.android.gms.ads.AdListener +import com.google.android.gms.ads.AdLoader +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.LoadAdError +import com.google.android.gms.ads.nativead.NativeAd + +object NativeAdCache { + private var cachedAd: NativeAd? = null + + fun load( + context: Context, + placement: AdsInsUtil.AdPlacement, + onAdLoaded: (() -> Unit)? = null, + onAdFailedToLoad: (() -> Unit)? = null + ) { + if (cachedAd != null) { + onAdLoaded?.invoke() + return + } + val adUnitId = AdsInsUtil.adUnitIdMap[placement] ?: run { + val errorMsg = "No AdUnitId for $placement" + Log.d(AdsInsUtil.Placement.TAG, "没找到对应的广告ID->$placement") + onAdFailedToLoad?.invoke() + return + } + AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ) + val adLoader = AdLoader.Builder(context, adUnitId) + .forNativeAd { ad -> + if (context is Activity) { + if (context.isFinishing || context.isDestroyed) { + ad.destroy() + return@forNativeAd + } + } + cachedAd?.destroy() + cachedAd = ad + onAdLoaded?.invoke() + } + .withAdListener(object : AdListener() { + override fun onAdFailedToLoad(error: LoadAdError) { + cachedAd = null + onAdFailedToLoad?.invoke() + Log.d(AdsInsUtil.Placement.TAG, "$placement 广告加载失败 ${error.message}") + AnalyticsUtils.logAdEvent( + placement, + AnalyticsUtils.AdEvent.LOAD_FAIL, + error.code, + error.message + ) + } + + override fun onAdLoaded() { + Log.d(AdsInsUtil.Placement.TAG, "$placement 广告加载成功") + AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED) + } + }) + .build() + + adLoader.loadAd(AdRequest.Builder().build()) + } + + fun getAd(): NativeAd? = cachedAd + + fun clear() { + cachedAd?.destroy() + cachedAd = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/BaseActivity.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/BaseActivity.kt index 732bcf8..300f18e 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/BaseActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/BaseActivity.kt @@ -24,33 +24,27 @@ abstract class BaseActivity : AppCompatActivity() { protected val appStore by lazy { AppStore(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.d("ocean", "🚀 ${javaClass.simpleName} onCreate") setupBackPressedCallback()//初始化back事件 } override fun onStart() { super.onStart() - Log.d("ocean", "🔄 ${javaClass.simpleName} onStart") } override fun onResume() { super.onResume() - Log.d("ocean", "📱 ${javaClass.simpleName} onResume") } override fun onPause() { super.onPause() - Log.d("ocean", "⏸️ ${javaClass.simpleName} onPause") } override fun onStop() { super.onStop() - Log.d("ocean", "🛑 ${javaClass.simpleName} onStop") } override fun onDestroy() { super.onDestroy() - Log.d("ocean", "💀 ${javaClass.simpleName} onDestroy") } protected fun logDebug(message: String) { diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/MainActivity.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/MainActivity.kt index 2399d19..0b4f277 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/MainActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/MainActivity.kt @@ -3,6 +3,8 @@ package com.all.pdfreader.pdf.reader.ui.act import android.content.Intent import android.os.Build import android.os.Bundle +import android.util.DisplayMetrics +import android.util.Log import android.view.View import android.view.WindowManager import androidx.activity.OnBackPressedCallback @@ -12,10 +14,14 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import com.all.pdfreader.pdf.reader.PRApp import com.all.pdfreader.pdf.reader.R +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil +import com.all.pdfreader.pdf.reader.ad.BannerManager +import com.all.pdfreader.pdf.reader.ad.NativeAdCache import com.all.pdfreader.pdf.reader.databinding.ActivityMainBinding import com.all.pdfreader.pdf.reader.model.FileActionEvent import com.all.pdfreader.pdf.reader.model.FragmentType import com.all.pdfreader.pdf.reader.model.PdfPickerSource +import com.all.pdfreader.pdf.reader.ui.dialog.ExitDialogFragment import com.all.pdfreader.pdf.reader.ui.dialog.PermissionDialogFragment import com.all.pdfreader.pdf.reader.ui.dialog.ProgressDialogFragment import com.all.pdfreader.pdf.reader.ui.dialog.PromptDialogFragment @@ -33,6 +39,16 @@ import com.all.pdfreader.pdf.reader.util.PdfScanner import com.all.pdfreader.pdf.reader.util.StoragePermissionHelper import com.all.pdfreader.pdf.reader.viewmodel.PdfViewModel import com.all.pdfreader.pdf.reader.viewmodel.observeEvent +import com.google.android.gms.ads.AdListener +import com.google.android.gms.ads.AdLoader +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.AdSize +import com.google.android.gms.ads.AdView +import com.google.android.gms.ads.LoadAdError +import com.google.android.gms.ads.nativead.NativeAdOptions +import com.google.android.gms.ads.nativead.NativeAdView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import java.io.File @@ -60,6 +76,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + loadAd() AnalyticsUtils.logEvent(AnalyticsUtils.Event.HOME_SHOW) setupDoubleBackExit() initObserve() @@ -305,9 +322,9 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback } binding.switchScreenOn.setOnCheckedChangeListener(object : OnCheckedChangeListener { override fun onCheckedChanged(view: CustomSwitchButton?, isChecked: Boolean) { - if(isChecked){ + if (isChecked) { AnalyticsUtils.logEvent(AnalyticsUtils.Event.KEEP_SCREEN_OPEN) - }else{ + } else { AnalyticsUtils.logEvent(AnalyticsUtils.Event.KEEP_SCREEN_CLOSE) } view?.setChecked(isChecked) @@ -414,25 +431,6 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback supportFragmentManager.putFragment(outState, fragmentTag, activeFragment) } - override fun onResume() { - super.onResume() - logDebug("main onResume") - if (StoragePermissionHelper.hasBasicStoragePermission(this)) { - // 有授权才初始化文件变化监听器 - PRApp.getInstance().startFileChangeObserving() - scanningStrategy() - binding.pnLayout.visibility = View.GONE - } else { - binding.pnLayout.visibility = View.VISIBLE - val dialog = PermissionDialogFragment() - //如果之前展示过授权对话框,则不再展示 - if (!appStore.isShowPermissionsDialogPrompt) { - dialog.show(supportFragmentManager, TAG) - } - } - updateSelectedNav(activeFragment) - } - private fun scanningStrategy() { if (StoragePermissionHelper.hasBasicStoragePermission(this)) { lifecycleScope.launch { @@ -563,22 +561,54 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback } } - private var lastBackPressedTime: Long = 0 - private val EXIT_INTERVAL = 2000L // 2 秒内双击退出 - private fun setupDoubleBackExit() { onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - val currentTime = System.currentTimeMillis() - if (currentTime - lastBackPressedTime < EXIT_INTERVAL) { - // 双击退出 + ExitDialogFragment(onExitClick = { isEnabled = false // 解除拦截 onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑 - } else { - lastBackPressedTime = currentTime - showToast(getString(R.string.press_again_to_exit)) - } + }).show(supportFragmentManager, TAG) } }) } + + private fun loadAd() { + AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_TOPDF) + AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_PDFTOHOME) + + NativeAdCache.load(this, AdsInsUtil.AdPlacement.NATIVE_AND_EXIT) + + BannerManager.loadBanner(this, binding.bannerAdFl, AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE) + } + + override fun onResume() { + super.onResume() + BannerManager.onResume(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE) + logDebug("main onResume") + if (StoragePermissionHelper.hasBasicStoragePermission(this)) { + // 有授权才初始化文件变化监听器 + PRApp.getInstance().startFileChangeObserving() + scanningStrategy() + binding.pnLayout.visibility = View.GONE + } else { + binding.pnLayout.visibility = View.VISIBLE + val dialog = PermissionDialogFragment() + //如果之前展示过授权对话框,则不再展示 + if (!appStore.isShowPermissionsDialogPrompt) { + dialog.show(supportFragmentManager, TAG) + } + } + updateSelectedNav(activeFragment) + } + + override fun onPause() { + BannerManager.onPause(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE) + super.onPause() + } + + override fun onDestroy() { + BannerManager.onDestroy(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE) + super.onDestroy() + } + } diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/MergePdfActivity.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/MergePdfActivity.kt index 2ba860d..8ce34e0 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/MergePdfActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/MergePdfActivity.kt @@ -11,6 +11,7 @@ import androidx.activity.result.contract.ActivityResultContract import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.all.pdfreader.pdf.reader.R +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil import com.all.pdfreader.pdf.reader.databinding.ActivityPdfMergeBinding import com.all.pdfreader.pdf.reader.model.PdfPickerSource import com.all.pdfreader.pdf.reader.room.entity.PdfDocumentEntity @@ -41,6 +42,7 @@ class MergePdfActivity : BaseActivity() { binding = ActivityPdfMergeBinding.inflate(layoutInflater) setContentView(binding.root) AnalyticsUtils.logEvent(AnalyticsUtils.Event.MERGE_SHOW) + AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_MERGE) setupImmersionBar { statusBarView(binding.view) statusBarDarkFont(true) diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/PdfResultActivity.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/PdfResultActivity.kt index 517f3d6..a31b679 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/PdfResultActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/PdfResultActivity.kt @@ -15,6 +15,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import com.all.pdfreader.pdf.reader.PRApp import com.all.pdfreader.pdf.reader.R +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil import com.all.pdfreader.pdf.reader.databinding.ActivityPdfSplitResultBinding import com.all.pdfreader.pdf.reader.model.PdfPageItem import com.all.pdfreader.pdf.reader.model.PdfPickerSource @@ -160,7 +161,11 @@ class PdfResultActivity : BaseActivity() { } if (source == PdfPickerSource.SPLIT) { val splitPassword = intent.getStringExtra(EXTRA_SPLIT_PASSWORD) ?: "" - runOnUiThread { + withContext(Dispatchers.Main) { + AdsInsUtil.showFinishAd(this@PdfResultActivity, AdsInsUtil.AdPlacement.INT_AND_SPLIT){ + AdsInsUtil.loadAd(this@PdfResultActivity, AdsInsUtil.AdPlacement.INT_AND_SPLIT) + } + binding.progressBar.isIndeterminate = false binding.progressBar.progress = 0 binding.progressBar.max = 100 @@ -210,7 +215,10 @@ class PdfResultActivity : BaseActivity() { } } } else if (source == PdfPickerSource.MERGE) { - runOnUiThread { + withContext(Dispatchers.Main) { + AdsInsUtil.showFinishAd(this@PdfResultActivity, AdsInsUtil.AdPlacement.INT_AND_MERGE){ + AdsInsUtil.loadAd(this@PdfResultActivity, AdsInsUtil.AdPlacement.INT_AND_MERGE) + } binding.progressBar.isIndeterminate = false binding.progressBar.progress = 0 binding.progressBar.max = 100 diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/PdfViewActivity.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/PdfViewActivity.kt index dbfee17..4c16259 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/PdfViewActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/PdfViewActivity.kt @@ -11,13 +11,18 @@ import android.view.inputmethod.EditorInfo import androidx.activity.OnBackPressedCallback import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope +import com.all.pdfreader.pdf.reader.PRApp import com.all.pdfreader.pdf.reader.R +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil +import com.all.pdfreader.pdf.reader.ad.BannerManager +import com.all.pdfreader.pdf.reader.ad.ShowListener import com.all.pdfreader.pdf.reader.databinding.ActivityPdfViewBinding import com.all.pdfreader.pdf.reader.model.FileActionEvent import com.all.pdfreader.pdf.reader.room.entity.PdfDocumentEntity import com.all.pdfreader.pdf.reader.ui.dialog.BookmarksDialogFragment import com.all.pdfreader.pdf.reader.ui.dialog.ListMoreDialogFragment import com.all.pdfreader.pdf.reader.ui.dialog.PdfPasswordProtectionDialogFragment +import com.all.pdfreader.pdf.reader.ui.dialog.PermissionDialogFragment import com.all.pdfreader.pdf.reader.ui.dialog.ViewModelDialogFragment import com.all.pdfreader.pdf.reader.ui.view.CustomScrollHandle import com.all.pdfreader.pdf.reader.util.AnalyticsUtils @@ -27,6 +32,7 @@ import com.all.pdfreader.pdf.reader.util.AppUtils.showKeyboard import com.all.pdfreader.pdf.reader.util.FileUtils import com.all.pdfreader.pdf.reader.util.PDFHighlighter import com.all.pdfreader.pdf.reader.util.PDFSearchManager +import com.all.pdfreader.pdf.reader.util.StoragePermissionHelper import com.all.pdfreader.pdf.reader.viewmodel.PdfViewModel import com.all.pdfreader.pdf.reader.viewmodel.observeEvent import com.github.barteksc.pdfviewer.listener.OnErrorListener @@ -48,6 +54,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList override val TAG: String = "PdfViewActivity" override val rootBottomView: View? get() = binding.rootBottomLayout + companion object { const val FRAG_TAG = "PdfViewActivity" private const val EXTRA_PDF_FILE_PATH = "extra_pdf_file_path" @@ -77,6 +84,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList binding = ActivityPdfViewBinding.inflate(layoutInflater) setContentView(binding.root) AnalyticsUtils.logEvent(AnalyticsUtils.Event.PDF_SHOW) + BannerManager.loadBanner(this, binding.bannerAdFl, AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE) setupImmersionBar { statusBarView(binding.view) statusBarDarkFont(true) @@ -94,6 +102,13 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList //加载书签数据 viewModel.getBookmarks(filePath) setupOnClick() + + AdsInsUtil.showFinishAd( + this@PdfViewActivity, + AdsInsUtil.AdPlacement.INT_AND_TOPDF + ) { + AdsInsUtil.loadAd(this@PdfViewActivity, AdsInsUtil.AdPlacement.INT_AND_TOPDF) + } } private fun initObserve() { @@ -451,6 +466,15 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList setSelection(0) } } else { + AdsInsUtil.showFinishAd( + this@PdfViewActivity, + AdsInsUtil.AdPlacement.INT_AND_PDFTOHOME + ) { + AdsInsUtil.loadAd( + this@PdfViewActivity, + AdsInsUtil.AdPlacement.INT_AND_PDFTOHOME + ) + } isEnabled = false // 解除拦截 onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑 } @@ -558,10 +582,20 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList } override fun onDestroy() { + BannerManager.onDestroy(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE) super.onDestroy() if (::searchManager.isInitialized) { searchManager.closeCachedDocument() } } + override fun onResume() { + super.onResume() + BannerManager.onResume( this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE) + } + + override fun onPause() { + BannerManager.onPause(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE) + super.onPause() + } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/SplashActivity.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/SplashActivity.kt index 0df9e11..03a6d51 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/SplashActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/SplashActivity.kt @@ -5,11 +5,16 @@ import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper +import android.util.Log import androidx.activity.OnBackPressedCallback import androidx.core.view.doOnPreDraw import com.all.pdfreader.pdf.reader.R +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil +import com.all.pdfreader.pdf.reader.ad.LoadListener +import com.all.pdfreader.pdf.reader.ad.ShowListener import com.all.pdfreader.pdf.reader.databinding.ActivitySplashBinding import com.all.pdfreader.pdf.reader.util.AnalyticsUtils +import com.google.android.gms.ads.interstitial.InterstitialAd import com.gyf.immersionbar.BarHide import com.gyf.immersionbar.ImmersionBar @@ -21,7 +26,7 @@ class SplashActivity : BaseActivity() { private lateinit var binding: ActivitySplashBinding companion object { - private const val SPLASH_DELAY = 3000L // 启动页显示时长 + private const val AD_TIMEOUT = 15000L // 广告加载超时时间 15 秒 } override fun onCreate(savedInstanceState: Bundle?) { @@ -35,15 +40,62 @@ class SplashActivity : BaseActivity() { // 设置启动页布局 setContentView(binding.root) AnalyticsUtils.logEvent(AnalyticsUtils.Event.SPLASH_VISIBLE) - // 延迟跳转到权限检查 - Handler(Looper.getMainLooper()).postDelayed({ - navigateToNext() - }, SPLASH_DELAY) + loadSplashAd() + } - + + private val adHandler = Handler(Looper.getMainLooper()) + private val adTimeoutRunnable = Runnable { + navigateToNext() // 超时直接跳转 + } + + private fun loadSplashAd() { + adHandler.postDelayed(adTimeoutRunnable, AD_TIMEOUT) + + AdsInsUtil.loadAd( + act = this, + adPlacement = AdsInsUtil.AdPlacement.SPL_AND_INTO_HOME, + loadListener = object : LoadListener { + override fun loaded(ad: InterstitialAd) { + logDebug("loaded 启动页广告加载成功") + adHandler.removeCallbacks(adTimeoutRunnable) + showSplashAd() + } + + override fun loadFailed(string: String) { + logDebug("loadFailed->$string") + adHandler.removeCallbacks(adTimeoutRunnable) + navigateToNext() // 加载失败直接跳转 + } + }) + } + + private fun showSplashAd() { + AdsInsUtil.showAd( + act = this, + adPlacement = AdsInsUtil.AdPlacement.SPL_AND_INTO_HOME, + listener = object : ShowListener { + override fun onAdShown() { + // 可以记录日志 + } + + override fun onAdShowFailed(string: String) { + navigateToNext() // 展示失败也直接跳转 + } + + override fun onAdClosed() { + navigateToNext() // 广告关闭后跳转 + } + }) + } + + private var hasNavigated = false + + //保证只跳转一次 private fun navigateToNext() { - val intent = Intent(this, MainActivity::class.java) - startActivity(intent) + if (hasNavigated) return + hasNavigated = true + startActivity(Intent(this, MainActivity::class.java)) finish() } @@ -51,4 +103,9 @@ class SplashActivity : BaseActivity() { override fun onInterceptBackPressed() { } + + override fun onDestroy() { + super.onDestroy() + adHandler.removeCallbacks(adTimeoutRunnable) + } } \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/SplitPdfActivity.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/SplitPdfActivity.kt index 539cfc7..9da594e 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/SplitPdfActivity.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/act/SplitPdfActivity.kt @@ -9,6 +9,7 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import com.all.pdfreader.pdf.reader.R +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil import com.all.pdfreader.pdf.reader.databinding.ActivityPdfSplitBinding import com.all.pdfreader.pdf.reader.model.PdfPageItem import com.all.pdfreader.pdf.reader.model.PdfPickerSource @@ -66,6 +67,7 @@ class SplitPdfActivity : BaseActivity() { super.onCreate(savedInstanceState) binding = ActivityPdfSplitBinding.inflate(layoutInflater) setContentView(binding.root) + AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_SPLIT) setupImmersionBar { statusBarView(binding.view) statusBarDarkFont(true) diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ui/dialog/ExitDialogFragment.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/dialog/ExitDialogFragment.kt new file mode 100644 index 0000000..08d05a7 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ui/dialog/ExitDialogFragment.kt @@ -0,0 +1,171 @@ +package com.all.pdfreader.pdf.reader.ui.dialog + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.all.pdfreader.pdf.reader.R +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil +import com.all.pdfreader.pdf.reader.ad.NativeAdCache +import com.all.pdfreader.pdf.reader.databinding.AdAdmobNativeExitLayoutBinding +import com.all.pdfreader.pdf.reader.databinding.DialogExitBinding +import com.google.android.gms.ads.nativead.NativeAd +import com.google.android.material.bottomsheet.BottomSheetDialogFragment + +class ExitDialogFragment( + private val onExitClick: () -> Unit, +) : BottomSheetDialogFragment() { + + private lateinit var binding: DialogExitBinding + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = DialogExitBinding.inflate(layoutInflater) + return binding.root + } + + override fun onStart() { + super.onStart() + dialog?.window?.findViewById(com.google.android.material.R.id.design_bottom_sheet) + ?.setBackgroundResource(R.drawable.dr_rc_top_12_bg_white) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupOnClick() + val cached = NativeAdCache.getAd() + if (cached != null) { + Log.d(AdsInsUtil.Placement.TAG, "使用缓存广告") + showNativeAd(cached) + } else { + Log.d(AdsInsUtil.Placement.TAG, "无缓存,开始加载") + loadNativeAd() + } + } + + private fun setupOnClick() { + binding.exitBtn.setOnClickListener { + onExitClick() + dismiss() + } + } + + private fun showNativeAd(nativeAd: NativeAd) { + if (!isAdded || activity == null) { + nativeAd.destroy() + return + } + + if (requireActivity().isFinishing || requireActivity().isDestroyed) { + nativeAd.destroy() + return + } + + // Inflate 必须在主线程 + val adBinding = AdAdmobNativeExitLayoutBinding.inflate(layoutInflater) + populateNativeAdView(nativeAd, adBinding) + + binding.adFl.removeAllViews() + binding.adFl.addView(adBinding.root) + } + + private fun loadNativeAd() { + + NativeAdCache.load(requireActivity(), AdsInsUtil.AdPlacement.NATIVE_AND_EXIT, onAdLoaded = { + if (!isAdded) return@load + val ad = NativeAdCache.getAd() ?: return@load + showNativeAd(ad) + }) + } + + private fun populateNativeAdView( + nativeAd: NativeAd, + unifiedAdBinding: AdAdmobNativeExitLayoutBinding + ) { + val nativeAdView = unifiedAdBinding.root + + // Set the media view. + nativeAdView.mediaView = unifiedAdBinding.adMedia + + // Set other ad assets. + nativeAdView.headlineView = unifiedAdBinding.adHeadline + nativeAdView.bodyView = unifiedAdBinding.adBody + nativeAdView.callToActionView = unifiedAdBinding.adCallToAction + nativeAdView.iconView = unifiedAdBinding.adAppIcon +// nativeAdView.priceView = unifiedAdBinding.adPrice + nativeAdView.starRatingView = unifiedAdBinding.adStars +// nativeAdView.storeView = unifiedAdBinding.adStore + nativeAdView.advertiserView = unifiedAdBinding.adAdvertiser + + // The headline and media content are guaranteed to be in every UnifiedNativeAd. + unifiedAdBinding.adHeadline.text = nativeAd.headline + nativeAd.mediaContent?.let { unifiedAdBinding.adMedia.setMediaContent(it) } + + // These assets aren't guaranteed to be in every UnifiedNativeAd, so it's important to + // check before trying to display them. + if (nativeAd.body == null) { + unifiedAdBinding.adBody.visibility = View.INVISIBLE + } else { + unifiedAdBinding.adBody.visibility = View.VISIBLE + unifiedAdBinding.adBody.text = nativeAd.body + } + + if (nativeAd.callToAction == null) { + unifiedAdBinding.adCallToAction.visibility = View.INVISIBLE + } else { + unifiedAdBinding.adCallToAction.visibility = View.VISIBLE + unifiedAdBinding.adCallToAction.text = nativeAd.callToAction + } + + if (nativeAd.icon == null) { + unifiedAdBinding.adAppIcon.visibility = View.GONE + } else { + unifiedAdBinding.adAppIcon.setImageDrawable(nativeAd.icon?.drawable) + unifiedAdBinding.adAppIcon.visibility = View.VISIBLE + } + +// if (nativeAd.price == null) { +// unifiedAdBinding.adPrice.visibility = View.INVISIBLE +// } else { +// unifiedAdBinding.adPrice.visibility = View.VISIBLE +// unifiedAdBinding.adPrice.text = nativeAd.price +// } +// +// if (nativeAd.store == null) { +// unifiedAdBinding.adStore.visibility = View.INVISIBLE +// } else { +// unifiedAdBinding.adStore.visibility = View.VISIBLE +// unifiedAdBinding.adStore.text = nativeAd.store +// } + + if (nativeAd.starRating == null) { + unifiedAdBinding.adStars.visibility = View.INVISIBLE + } else { + unifiedAdBinding.adStars.rating = nativeAd.starRating!!.toFloat() + unifiedAdBinding.adStars.visibility = View.VISIBLE + } + + if (nativeAd.advertiser == null) { + unifiedAdBinding.adAdvertiser.visibility = View.INVISIBLE + } else { + unifiedAdBinding.adAdvertiser.text = nativeAd.advertiser + unifiedAdBinding.adAdvertiser.visibility = View.VISIBLE + } + + // This method tells the Google Mobile Ads SDK that you have finished populating your + // native ad view with this native ad. + nativeAdView.setNativeAd(nativeAd) + } + + override fun onDestroyView() { + super.onDestroyView() + // 销毁广告,避免内存泄漏 + NativeAdCache.clear() + } +} diff --git a/app/src/main/res/drawable/dr_rc_top_12_bg_color.xml b/app/src/main/res/drawable/dr_rc_top_12_bg_color.xml new file mode 100644 index 0000000..7cf75ef --- /dev/null +++ b/app/src/main/res/drawable/dr_rc_top_12_bg_color.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gnt_outline_shape.xml b/app/src/main/res/drawable/gnt_outline_shape.xml new file mode 100644 index 0000000..8400e60 --- /dev/null +++ b/app/src/main/res/drawable/gnt_outline_shape.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/gnt_rounded_corners_shape.xml b/app/src/main/res/drawable/gnt_rounded_corners_shape.xml new file mode 100644 index 0000000..ddaf7a8 --- /dev/null +++ b/app/src/main/res/drawable/gnt_rounded_corners_shape.xml @@ -0,0 +1,17 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/gnt_top_round_12_outline_shape.xml b/app/src/main/res/drawable/gnt_top_round_12_outline_shape.xml new file mode 100644 index 0000000..d371081 --- /dev/null +++ b/app/src/main/res/drawable/gnt_top_round_12_outline_shape.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 397bab5..f4617ca 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -8,6 +8,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +