1.添加admob广告

2.添加广告打点
This commit is contained in:
ocean 2025-12-02 11:49:50 +08:00
parent e714a1ef63
commit 905c97e415
29 changed files with 923 additions and 83 deletions

6
.idea/studiobot.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="StudioBotProjectSettings">
<option name="shareContext" value="OptedIn" />
</component>
</project>

View File

@ -26,6 +26,7 @@
<uses-permission <uses-permission
android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<application <application
android:name=".PRApp" android:name=".PRApp"
@ -40,9 +41,11 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.PDFReaderPro"> android:theme="@style/Theme.PDFReaderPro">
<!--正式ID ca-app-pub-5717753826607607~1850604454 -->
<!--google测试ID ca-app-pub-3940256099942544~3347511713 -->
<meta-data <meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID" android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" /> android:value="ca-app-pub-5717753826607607~1850604454" />
<meta-data <meta-data
android:name="android.max_aspect" android:name="android.max_aspect"
android:value="2.4" /> android:value="2.4" />

View File

@ -46,10 +46,11 @@ class PRApp : Application() {
UpLoadManager.init(context = this, tag = "PRApp_upload_task") { _, _ -> } UpLoadManager.init(context = this, tag = "PRApp_upload_task") { _, _ -> }
// 广告初始化 // 广告初始化
MobileAds.initialize(this) MobileAds.initialize(this)
initMobileAds()
} }
private fun initMobileAds() { private fun initMobileAds() {
val testDeviceIds = listOf("TEST_DEVICE_ID") val testDeviceIds = listOf("9A96E667D69B45F744FD7D724DF8B093")
val configuration = RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build() val configuration = RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build()
MobileAds.setRequestConfiguration(configuration) MobileAds.setRequestConfiguration(configuration)
} }

View File

@ -19,48 +19,55 @@ class AdInstLoad(
} }
private fun loadAd() { private fun loadAd() {
//多处调用load也不会重复、不影响缓存广告、展示安全 Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 开始加载")
// 是否存在缓存广告、展示安全
val cachedAd = InstAdCacheManager.instance.getAdCache(placement) val cachedAd = InstAdCacheManager.instance.getAdCache(placement)
if (cachedAd != null) { if (cachedAd != null) {
Log.d("ocean","广告存在缓存,跳过加载,返回成功") Log.d(AdsInsUtil.Placement.TAG,"广告存在缓存,跳过加载,返回成功")
//缓存广告有效,跳过加载,返回成功 //缓存广告有效,跳过加载,返回成功
adLoadListener?.loaded(cachedAd) adLoadListener?.loaded(cachedAd)
return 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 adUnitId = AdsInsUtil.adUnitIdMap[placement] ?: run {
val errorMsg = "No AdUnitId for $placement" val errorMsg = "No AdUnitId for $placement"
Log.d("ocean","没找到对应的广告ID->$placement") Log.d(AdsInsUtil.Placement.TAG,"没找到对应的广告ID->$placement")
adLoadListener?.loadFailed(errorMsg) InstAdCacheManager.instance.notifyFail(placement, errorMsg)
AnalyticsUtils.logAdEvent(
placement,
AnalyticsUtils.AdEvent.LOAD_FAIL,
null,
errorMsg
)
return return
} }
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ) AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ)
Log.d(AdsInsUtil.Placement.TAG,"adUnitId->$adUnitId")
InterstitialAd.load( InterstitialAd.load(
activity, activity,
adUnitId, adUnitId,
AdRequest.Builder().build(), AdRequest.Builder().build(),
object : InterstitialAdLoadCallback() { object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) { override fun onAdLoaded(ad: InterstitialAd) {
Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 加载成功")
InstAdCacheManager.instance.setAdCache(placement, ad) InstAdCacheManager.instance.setAdCache(placement, ad)
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED) AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED)
adLoadListener?.loaded(ad) InstAdCacheManager.instance.notifySuccess(placement, ad)
} }
override fun onAdFailedToLoad(adError: LoadAdError) { override fun onAdFailedToLoad(adError: LoadAdError) {
Log.d(AdsInsUtil.Placement.TAG,"广告 $placement 加载失败")
AnalyticsUtils.logAdEvent( AnalyticsUtils.logAdEvent(
placement, placement,
AnalyticsUtils.AdEvent.LOAD_FAIL, AnalyticsUtils.AdEvent.LOAD_FAIL,
adError.code, adError.code,
adError.message adError.message
) )
adLoadListener?.loadFailed(adError.toString()) InstAdCacheManager.instance.notifyFail(placement, adError.toString())
} }
}, },
) )

View File

@ -1,38 +1,52 @@
package com.all.pdfreader.pdf.reader.ad package com.all.pdfreader.pdf.reader.ad
import android.app.Activity 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.all.pdfreader.pdf.reader.util.AnalyticsUtils
import com.google.android.gms.ads.AdError import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.FullScreenContentCallback import com.google.android.gms.ads.FullScreenContentCallback
class AdInstShower( class AdInstShower(
private val activity: Activity, private val activity: Activity,
private val placement: AdPlacement, private val placement: AdsInsUtil.AdPlacement,
private val showListener: ShowListener? private val showListener: ShowListener?
) { ) {
companion object {
private const val SHOW_INTERVAL = 60_000L // 60秒间隔
private var lastShowTimestamp = 0L
}
init { init {
showAd() showAd()
} }
private fun 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) val interstitialAd = InstAdCacheManager.instance.getAdCache(placement)
?: run { ?: run {
val errorMsg = "InterstitialAd cache is null for place = $placement" val errorMsg = "插页式广告缓存为空 = $placement"
AnalyticsUtils.logAdEvent(
placement,
AnalyticsUtils.AdEvent.SHOW_FAIL,
null,
errorMsg
)
showListener?.onAdShowFailed(errorMsg) showListener?.onAdShowFailed(errorMsg)
Log.d(AdsInsUtil.Placement.TAG, "showAd->$errorMsg")
return return
} }
interstitialAd.fullScreenContentCallback = object : FullScreenContentCallback() { interstitialAd.fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() { override fun onAdShowedFullScreenContent() {
// ======= 新增:展示成功 记录时间 =======
lastShowTimestamp = System.currentTimeMillis()
Log.d(AdsInsUtil.Placement.TAG, "广告 $placement 展示成功")
AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.SHOW_SUC) AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.SHOW_SUC)
showListener?.onAdShown() showListener?.onAdShown()
} }
@ -52,6 +66,7 @@ class AdInstShower(
) )
InstAdCacheManager.instance.remove(placement) InstAdCacheManager.instance.remove(placement)
showListener?.onAdShowFailed(adError.toString()) showListener?.onAdShowFailed(adError.toString())
Log.d(AdsInsUtil.Placement.TAG, "广告 $placement 展示失败->${adError}")
} }
override fun onAdClicked() { override fun onAdClicked() {

View File

@ -5,16 +5,17 @@ import android.app.Activity
object AdsInsUtil { object AdsInsUtil {
/** 广告位定义(可扩展) */ /** 广告位定义(可扩展) */
enum class AdPlacement(val tag: String) { 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( SPL_AND_INTO_HOME(Placement.SPL_AND_INTO_HOME),
Placement.INT_AND_PDFTOHOME 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( INT_AND_MERGE(Placement.INT_AND_MERGE),
Placement.NATIVE_AND_EXIT INT_AND_SPLIT(Placement.INT_AND_SPLIT),
), NATIVE_AND_EXIT(Placement.NATIVE_AND_EXIT),
BAN_AND_HOMEPAGE(Placement.BAN_AND_HOMEPAGE) BAN_AND_HOMEPAGE(Placement.BAN_AND_HOMEPAGE)
} }
object Placement { object Placement {
const val TAG = "ocean-ad"
/** /**
* 启动页插页 * 启动页插页
*/ */
@ -53,7 +54,7 @@ object AdsInsUtil {
// 广告位对应的广告ID // 广告位对应的广告ID
val adUnitIdMap: Map<AdPlacement, String> = mapOf( val adUnitIdMap: Map<AdPlacement, String> = 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_TOPDF to "ca-app-pub-5717753826607607/5308904672",
AdPlacement.INT_AND_PDFTOHOME to "ca-app-pub-5717753826607607/7085128570", AdPlacement.INT_AND_PDFTOHOME to "ca-app-pub-5717753826607607/7085128570",
AdPlacement.INT_AND_MERGE to "ca-app-pub-5717753826607607/8928693282", AdPlacement.INT_AND_MERGE to "ca-app-pub-5717753826607607/8928693282",
@ -74,9 +75,33 @@ object AdsInsUtil {
return AdInstLoad(act, adPlacement, loadListener) return AdInstLoad(act, adPlacement, loadListener)
} }
fun showAd(
act: Activity, adPlacement: AdPlacement
): AdInstShower {
return AdInstShower(act, adPlacement, null)
}
fun showAd( fun showAd(
act: Activity, adPlacement: AdPlacement, listener: ShowListener? act: Activity, adPlacement: AdPlacement, listener: ShowListener?
): AdInstShower { ): AdInstShower {
return AdInstShower(act, adPlacement, listener) 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()
}
}
)
}
} }

View File

@ -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<String, BannerInfo>()
//因为有多个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)
}
}

View File

@ -3,14 +3,56 @@ package com.all.pdfreader.pdf.reader.ad
import com.google.android.gms.ads.interstitial.InterstitialAd import com.google.android.gms.ads.interstitial.InterstitialAd
class InstAdCacheManager { class InstAdCacheManager {
private val mAdCacheDict: MutableMap<AdsInsUtil.AdPlacement, CachedAd> = mutableMapOf() private val mAdCacheDict: MutableMap<AdsInsUtil.AdPlacement, CachedAd> = mutableMapOf()
// 是否正在加载
private val loadingSet = mutableSetOf<AdsInsUtil.AdPlacement>()
// 等待回调的 listener多个 load 时会加入这里)
private val pendingListeners =
mutableMapOf<AdsInsUtil.AdPlacement, MutableList<LoadListener?>>()
companion object { companion object {
val instance: InstAdCacheManager by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { val instance: InstAdCacheManager by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
InstAdCacheManager() 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) { fun setAdCache(place: AdsInsUtil.AdPlacement, adCache: InterstitialAd) {
mAdCacheDict[place] = CachedAd(adCache) mAdCacheDict[place] = CachedAd(adCache)
} }
@ -19,7 +61,7 @@ class InstAdCacheManager {
val cached = mAdCacheDict[place] val cached = mAdCacheDict[place]
return if (cached != null && cached.isValid()) cached.ad return if (cached != null && cached.isValid()) cached.ad
else { else {
mAdCacheDict.remove(place) // 过期广告清理 mAdCacheDict.remove(place) // 清理过期
null null
} }
} }
@ -28,12 +70,14 @@ class InstAdCacheManager {
mAdCacheDict.remove(place) mAdCacheDict.remove(place)
} }
/**
* 包装广告 + 时间
*/
data class CachedAd( data class CachedAd(
val ad: InterstitialAd, val ad: InterstitialAd,
val loadedAt: Long = System.currentTimeMillis() val loadedAt: Long = System.currentTimeMillis()
) { ) {
// 广告有效期 1 小时 // 广告有效期 1 小时
fun isValid(): Boolean = System.currentTimeMillis() - loadedAt < 3600_000 fun isValid(): Boolean = System.currentTimeMillis() - loadedAt < 3600_000
} }
} }

View File

@ -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
}
}

View File

@ -24,33 +24,27 @@ abstract class BaseActivity : AppCompatActivity() {
protected val appStore by lazy { AppStore(this) } protected val appStore by lazy { AppStore(this) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d("ocean", "🚀 ${javaClass.simpleName} onCreate")
setupBackPressedCallback()//初始化back事件 setupBackPressedCallback()//初始化back事件
} }
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
Log.d("ocean", "🔄 ${javaClass.simpleName} onStart")
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Log.d("ocean", "📱 ${javaClass.simpleName} onResume")
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
Log.d("ocean", "⏸️ ${javaClass.simpleName} onPause")
} }
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
Log.d("ocean", "🛑 ${javaClass.simpleName} onStop")
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
Log.d("ocean", "💀 ${javaClass.simpleName} onDestroy")
} }
protected fun logDebug(message: String) { protected fun logDebug(message: String) {

View File

@ -3,6 +3,8 @@ package com.all.pdfreader.pdf.reader.ui.act
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
@ -12,10 +14,14 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pdf.reader.PRApp import com.all.pdfreader.pdf.reader.PRApp
import com.all.pdfreader.pdf.reader.R 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.databinding.ActivityMainBinding
import com.all.pdfreader.pdf.reader.model.FileActionEvent import com.all.pdfreader.pdf.reader.model.FileActionEvent
import com.all.pdfreader.pdf.reader.model.FragmentType import com.all.pdfreader.pdf.reader.model.FragmentType
import com.all.pdfreader.pdf.reader.model.PdfPickerSource 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.PermissionDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.ProgressDialogFragment import com.all.pdfreader.pdf.reader.ui.dialog.ProgressDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.PromptDialogFragment 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.util.StoragePermissionHelper
import com.all.pdfreader.pdf.reader.viewmodel.PdfViewModel import com.all.pdfreader.pdf.reader.viewmodel.PdfViewModel
import com.all.pdfreader.pdf.reader.viewmodel.observeEvent 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 kotlinx.coroutines.launch
import java.io.File import java.io.File
@ -60,6 +76,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
loadAd()
AnalyticsUtils.logEvent(AnalyticsUtils.Event.HOME_SHOW) AnalyticsUtils.logEvent(AnalyticsUtils.Event.HOME_SHOW)
setupDoubleBackExit() setupDoubleBackExit()
initObserve() initObserve()
@ -414,25 +431,6 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
supportFragmentManager.putFragment(outState, fragmentTag, activeFragment) 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() { private fun scanningStrategy() {
if (StoragePermissionHelper.hasBasicStoragePermission(this)) { if (StoragePermissionHelper.hasBasicStoragePermission(this)) {
lifecycleScope.launch { 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() { private fun setupDoubleBackExit() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
val currentTime = System.currentTimeMillis() ExitDialogFragment(onExitClick = {
if (currentTime - lastBackPressedTime < EXIT_INTERVAL) {
// 双击退出
isEnabled = false // 解除拦截 isEnabled = false // 解除拦截
onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑 onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑
} else { }).show(supportFragmentManager, TAG)
lastBackPressedTime = currentTime
showToast(getString(R.string.press_again_to_exit))
}
} }
}) })
} }
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()
}
} }

View File

@ -11,6 +11,7 @@ import androidx.activity.result.contract.ActivityResultContract
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pdf.reader.R 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.databinding.ActivityPdfMergeBinding
import com.all.pdfreader.pdf.reader.model.PdfPickerSource import com.all.pdfreader.pdf.reader.model.PdfPickerSource
import com.all.pdfreader.pdf.reader.room.entity.PdfDocumentEntity import com.all.pdfreader.pdf.reader.room.entity.PdfDocumentEntity
@ -41,6 +42,7 @@ class MergePdfActivity : BaseActivity() {
binding = ActivityPdfMergeBinding.inflate(layoutInflater) binding = ActivityPdfMergeBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
AnalyticsUtils.logEvent(AnalyticsUtils.Event.MERGE_SHOW) AnalyticsUtils.logEvent(AnalyticsUtils.Event.MERGE_SHOW)
AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_MERGE)
setupImmersionBar { setupImmersionBar {
statusBarView(binding.view) statusBarView(binding.view)
statusBarDarkFont(true) statusBarDarkFont(true)

View File

@ -15,6 +15,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pdf.reader.PRApp import com.all.pdfreader.pdf.reader.PRApp
import com.all.pdfreader.pdf.reader.R 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.databinding.ActivityPdfSplitResultBinding
import com.all.pdfreader.pdf.reader.model.PdfPageItem import com.all.pdfreader.pdf.reader.model.PdfPageItem
import com.all.pdfreader.pdf.reader.model.PdfPickerSource import com.all.pdfreader.pdf.reader.model.PdfPickerSource
@ -160,7 +161,11 @@ class PdfResultActivity : BaseActivity() {
} }
if (source == PdfPickerSource.SPLIT) { if (source == PdfPickerSource.SPLIT) {
val splitPassword = intent.getStringExtra(EXTRA_SPLIT_PASSWORD) ?: "" 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.isIndeterminate = false
binding.progressBar.progress = 0 binding.progressBar.progress = 0
binding.progressBar.max = 100 binding.progressBar.max = 100
@ -210,7 +215,10 @@ class PdfResultActivity : BaseActivity() {
} }
} }
} else if (source == PdfPickerSource.MERGE) { } 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.isIndeterminate = false
binding.progressBar.progress = 0 binding.progressBar.progress = 0
binding.progressBar.max = 100 binding.progressBar.max = 100

View File

@ -11,13 +11,18 @@ import android.view.inputmethod.EditorInfo
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.all.pdfreader.pdf.reader.PRApp
import com.all.pdfreader.pdf.reader.R 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.databinding.ActivityPdfViewBinding
import com.all.pdfreader.pdf.reader.model.FileActionEvent import com.all.pdfreader.pdf.reader.model.FileActionEvent
import com.all.pdfreader.pdf.reader.room.entity.PdfDocumentEntity 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.BookmarksDialogFragment
import com.all.pdfreader.pdf.reader.ui.dialog.ListMoreDialogFragment 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.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.dialog.ViewModelDialogFragment
import com.all.pdfreader.pdf.reader.ui.view.CustomScrollHandle import com.all.pdfreader.pdf.reader.ui.view.CustomScrollHandle
import com.all.pdfreader.pdf.reader.util.AnalyticsUtils 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.FileUtils
import com.all.pdfreader.pdf.reader.util.PDFHighlighter import com.all.pdfreader.pdf.reader.util.PDFHighlighter
import com.all.pdfreader.pdf.reader.util.PDFSearchManager 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.PdfViewModel
import com.all.pdfreader.pdf.reader.viewmodel.observeEvent import com.all.pdfreader.pdf.reader.viewmodel.observeEvent
import com.github.barteksc.pdfviewer.listener.OnErrorListener import com.github.barteksc.pdfviewer.listener.OnErrorListener
@ -48,6 +54,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
override val TAG: String = "PdfViewActivity" override val TAG: String = "PdfViewActivity"
override val rootBottomView: View? override val rootBottomView: View?
get() = binding.rootBottomLayout get() = binding.rootBottomLayout
companion object { companion object {
const val FRAG_TAG = "PdfViewActivity" const val FRAG_TAG = "PdfViewActivity"
private const val EXTRA_PDF_FILE_PATH = "extra_pdf_file_path" private const val EXTRA_PDF_FILE_PATH = "extra_pdf_file_path"
@ -77,6 +84,7 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
binding = ActivityPdfViewBinding.inflate(layoutInflater) binding = ActivityPdfViewBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
AnalyticsUtils.logEvent(AnalyticsUtils.Event.PDF_SHOW) AnalyticsUtils.logEvent(AnalyticsUtils.Event.PDF_SHOW)
BannerManager.loadBanner(this, binding.bannerAdFl, AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
setupImmersionBar { setupImmersionBar {
statusBarView(binding.view) statusBarView(binding.view)
statusBarDarkFont(true) statusBarDarkFont(true)
@ -94,6 +102,13 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
//加载书签数据 //加载书签数据
viewModel.getBookmarks(filePath) viewModel.getBookmarks(filePath)
setupOnClick() setupOnClick()
AdsInsUtil.showFinishAd(
this@PdfViewActivity,
AdsInsUtil.AdPlacement.INT_AND_TOPDF
) {
AdsInsUtil.loadAd(this@PdfViewActivity, AdsInsUtil.AdPlacement.INT_AND_TOPDF)
}
} }
private fun initObserve() { private fun initObserve() {
@ -451,6 +466,15 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
setSelection(0) setSelection(0)
} }
} else { } else {
AdsInsUtil.showFinishAd(
this@PdfViewActivity,
AdsInsUtil.AdPlacement.INT_AND_PDFTOHOME
) {
AdsInsUtil.loadAd(
this@PdfViewActivity,
AdsInsUtil.AdPlacement.INT_AND_PDFTOHOME
)
}
isEnabled = false // 解除拦截 isEnabled = false // 解除拦截
onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑 onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑
} }
@ -558,10 +582,20 @@ class PdfViewActivity : BaseActivity(), OnLoadCompleteListener, OnPageChangeList
} }
override fun onDestroy() { override fun onDestroy() {
BannerManager.onDestroy(this,AdsInsUtil.AdPlacement.BAN_AND_HOMEPAGE)
super.onDestroy() super.onDestroy()
if (::searchManager.isInitialized) { if (::searchManager.isInitialized) {
searchManager.closeCachedDocument() 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()
}
} }

View File

@ -5,11 +5,16 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.core.view.doOnPreDraw import androidx.core.view.doOnPreDraw
import com.all.pdfreader.pdf.reader.R 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.databinding.ActivitySplashBinding
import com.all.pdfreader.pdf.reader.util.AnalyticsUtils 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.BarHide
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
@ -21,7 +26,7 @@ class SplashActivity : BaseActivity() {
private lateinit var binding: ActivitySplashBinding private lateinit var binding: ActivitySplashBinding
companion object { companion object {
private const val SPLASH_DELAY = 3000L // 启动页显示时长 private const val AD_TIMEOUT = 15000L // 广告加载超时时间 15 秒
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -35,15 +40,62 @@ class SplashActivity : BaseActivity() {
// 设置启动页布局 // 设置启动页布局
setContentView(binding.root) setContentView(binding.root)
AnalyticsUtils.logEvent(AnalyticsUtils.Event.SPLASH_VISIBLE) AnalyticsUtils.logEvent(AnalyticsUtils.Event.SPLASH_VISIBLE)
// 延迟跳转到权限检查 loadSplashAd()
Handler(Looper.getMainLooper()).postDelayed({
navigateToNext()
}, SPLASH_DELAY)
} }
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() { private fun navigateToNext() {
val intent = Intent(this, MainActivity::class.java) if (hasNavigated) return
startActivity(intent) hasNavigated = true
startActivity(Intent(this, MainActivity::class.java))
finish() finish()
} }
@ -51,4 +103,9 @@ class SplashActivity : BaseActivity() {
override fun onInterceptBackPressed() { override fun onInterceptBackPressed() {
} }
override fun onDestroy() {
super.onDestroy()
adHandler.removeCallbacks(adTimeoutRunnable)
}
} }

View File

@ -9,6 +9,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pdf.reader.R 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.databinding.ActivityPdfSplitBinding
import com.all.pdfreader.pdf.reader.model.PdfPageItem import com.all.pdfreader.pdf.reader.model.PdfPageItem
import com.all.pdfreader.pdf.reader.model.PdfPickerSource import com.all.pdfreader.pdf.reader.model.PdfPickerSource
@ -66,6 +67,7 @@ class SplitPdfActivity : BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityPdfSplitBinding.inflate(layoutInflater) binding = ActivityPdfSplitBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
AdsInsUtil.loadAd(this, AdsInsUtil.AdPlacement.INT_AND_SPLIT)
setupImmersionBar { setupImmersionBar {
statusBarView(binding.view) statusBarView(binding.view)
statusBarDarkFont(true) statusBarDarkFont(true)

View File

@ -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<View>(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()
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:topLeftRadius="12dp"
android:topRightRadius="12dp" />
<solid android:color="@color/bg_color" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="@color/gnt_white" />
<stroke android:width="2dp" android:color="@color/gnt_outline"/>
</shape>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffffffff"/>
<stroke android:width="2dp"
android:color="@color/gnt_ad_green"/>
<padding android:left="1dp"
android:top="1dp"
android:right="1dp"
android:bottom="1dp" />
<corners android:bottomRightRadius="5dp"
android:bottomLeftRadius="5dp"
android:topLeftRadius="5dp"
android:topRightRadius="5dp" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/gnt_white" />
<corners
android:topLeftRadius="12dp"
android:topRightRadius="12dp" />
<stroke
android:width="2dp"
android:color="@color/gnt_outline" />
</shape>

View File

@ -8,6 +8,7 @@
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -455,6 +456,11 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<FrameLayout
android:id="@+id/bannerAdFl"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout <LinearLayout
android:id="@+id/rootBottomLayout" android:id="@+id/rootBottomLayout"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -110,8 +110,8 @@
android:id="@+id/selectNextBtn" android:id="@+id/selectNextBtn"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="32dp" android:layout_height="32dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:gravity="center"> android:gravity="center">
<ImageView <ImageView
@ -332,7 +332,13 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<FrameLayout
android:id="@+id/bannerAdFl"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/rootBottomLayout" android:id="@+id/rootBottomLayout"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -0,0 +1,113 @@
<com.google.android.gms.ads.nativead.NativeAdView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:minHeight="50dp"
android:orientation="vertical">
<TextView style="@style/AdAttribution" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="20dp"
android:paddingTop="3dp"
android:paddingRight="20dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/ad_app_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:adjustViewBounds="true"
android:paddingEnd="5dp"
android:paddingRight="5dp"
android:paddingBottom="5dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/ad_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#0000FF"
android:textSize="16sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/ad_advertiser"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="bottom"
android:textSize="14sp"
android:textStyle="bold" />
<RatingBar
android:id="@+id/ad_stars"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:isIndicator="true"
android:numStars="5"
android:stepSize="0.5" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/ad_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_marginRight="20dp"
android:textSize="12sp" />
<com.google.android.gms.ads.nativead.MediaView
android:id="@+id/ad_media"
android:layout_width="match_parent"
android:layout_height="175dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="5dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<Button
android:id="@+id/ad_call_to_action"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/bg_color"
android:gravity="center"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.gms.ads.nativead.NativeAdView>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:orientation="vertical">
<FrameLayout
android:id="@+id/adFl"
android:layout_marginTop="12dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white" />
<TextView
android:id="@+id/exitBtn"
style="@style/TextViewFont_PopRegular"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginBottom="12dp"
android:gravity="center"
android:text="@string/tap_again_to_exit"
android:textColor="@color/black"
android:textSize="14sp" />
</LinearLayout>

View File

@ -39,4 +39,14 @@
<color name="ok_btn_bg_lv2_color">#FD4E1D</color> <color name="ok_btn_bg_lv2_color">#FD4E1D</color>
<color name="no_click_btn_bg_lv1_color">#7FE43521</color> <color name="no_click_btn_bg_lv1_color">#7FE43521</color>
<color name="no_click_btn_bg_lv2_color">#80FD4E1D</color> <color name="no_click_btn_bg_lv2_color">#80FD4E1D</color>
<color name="gnt_test_background_color">#00F4F4F4</color>
<color name="gnt_test_background_color_2">#00A3A3A3</color>
<color name="gnt_black">#000000</color>
<color name="gnt_white">#FFFFFF</color>
<color name="gnt_red">#FF0000</color>
<color name="gnt_green">#00FF00</color>
<color name="gnt_blue">#4285f4</color>
<color name="gnt_ad_green">#3A6728</color>
<color name="gnt_gray">#808080</color>
<color name="gnt_outline">#E0E0E0</color>
</resources> </resources>

View File

@ -1,4 +1,23 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="dialog_margin">16dp</dimen> <dimen name="dialog_margin">16dp</dimen>
<item name="gnt_medium_template_bottom_weight" format="float" type="dimen">0.4</item>
<item name="gnt_text_row_weight" format="float" type="dimen">0.25</item>
<item name="gnt_media_view_weight" format="float" type="dimen">1.0</item>
<item name="gnt_medium_template_top_weight" format="float" type="dimen">0.2</item>
<dimen name="gnt_default_margin">10dp</dimen>
<dimen name="gnt_small_margin">5dp</dimen>
<dimen name="gnt_text_size_small">12sp</dimen>
<dimen name="gnt_text_size_large">15sp</dimen>
<dimen name="gnt_ad_indicator_width">25dp</dimen>
<dimen name="gnt_ad_indicator_height">20dp</dimen>
<dimen name="gnt_ad_indicator_bar_height">30dp</dimen>
<dimen name="gnt_ad_indicator_top_margin">0dp</dimen>
<dimen name="gnt_ad_indicator_bottom_margin">10dp</dimen>
<dimen name="gnt_ad_indicator_text_size">10sp</dimen>
<dimen name="gnt_small_cta_button_height">30dp</dimen>
<dimen name="gnt_medium_cta_button_height">50dp</dimen>
<dimen name="gnt_no_margin">0dp</dimen>
<dimen name="gnt_no_size">0dp</dimen>
</resources> </resources>

View File

@ -195,4 +195,6 @@
<string name="number_image_converted">%1$d imagesF converted</string> <string name="number_image_converted">%1$d imagesF converted</string>
<string name="file_not_pdf_or_corrupted">File not in PDF format or corrupted</string> <string name="file_not_pdf_or_corrupted">File not in PDF format or corrupted</string>
<string name="processing_failed">Processing failed</string> <string name="processing_failed">Processing failed</string>
<string name="tap_again_to_exit">Tap again to exit</string>
<string name="ad_attribution" translatable="false">Ad</string>
</resources> </resources>

View File

@ -45,4 +45,16 @@
<item name="android:includeFontPadding">false</item> <item name="android:includeFontPadding">false</item>
<item name="android:fontFamily">@font/poppins_semibold</item> <item name="android:fontFamily">@font/poppins_semibold</item>
</style> </style>
<style name="AdAttribution">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_gravity">left</item>
<item name="android:textColor">#FFFFFF</item>
<item name="android:textSize">12sp</item>
<item name="android:text">@string/ad_attribution</item>
<item name="android:background">#FFCC66</item>
<item name="android:width">15dp</item>
<item name="android:height">15dp</item>
</style>
</resources> </resources>