Commit 7525c631 authored by 周文华's avatar 周文华

【新增】新增admob广告相关管理类及其部分逻辑

parent 9587e914
package com.base.locationsharewhite.ads
/**
* 广告限制配置
*
* @property isInBlackList 是否在黑名单
* @property numDisplayLimit 展示次数限制
* @property numRequestLimit 请求次数限制
* @property numClickLimit 点击次数限制
* @property timeInterval 广告间隔时间
*/
class AdsConfigBean(
val isInBlackList: Boolean,
val numDisplayLimit: Int,
val numRequestLimit: Int,
val numClickLimit: Int,
val timeInterval: Int
)
\ No newline at end of file
package com.base.locationsharewhite.ads
import android.app.Activity
import android.content.Context
import android.view.ViewGroup
import com.base.locationsharewhite.ads.admob.AdBannerMgr
import com.base.locationsharewhite.ads.admob.AdInsertMgr
import com.base.locationsharewhite.ads.admob.AdNativeMgr
import com.base.locationsharewhite.ads.admob.AdOpenMgr
import com.google.android.gms.ads.MobileAds
import com.google.android.gms.ads.initialization.AdapterStatus
/**
* 广告管理类
*/
object AdsMgr {
private val adOpenMgr by lazy {
AdOpenMgr()
}
private val adInsertMgr by lazy {
AdInsertMgr()
}
private val adNativeMgr by lazy {
AdNativeMgr()
}
private val adBannerMgr by lazy {
AdBannerMgr()
}
/**
* 是否初始化
*/
var isInit = false
private set
/**
* 广告配置项目
*/
var adsConfigBean: AdsConfigBean? = null
private set
/**
* Init 初始化
*
* @param context 这里最好是appContext,因为是耗时操作,等它初始化完毕马上加载开屏和插屏广告
*/
fun init(context: Context, adsConfigBean: AdsConfigBean) {
this.adsConfigBean = adsConfigBean
if (adsConfigBean.isInBlackList) return
MobileAds.initialize(context) {
val readyAdapter = it.adapterStatusMap.entries.find { entry ->
entry.value.initializationState == AdapterStatus.State.READY
}
isInit = readyAdapter != null
if (isInit) {
//成功初始化就提前预加载开屏广告和插页广告
adOpenMgr.loadAd(context)
adInsertMgr.loadAd(context)
}
}
}
fun showOpen(activity: Activity, showCallBack: AdsShowCallBack? = null) {
if (!isInit || adsConfigBean?.isInBlackList != false) {
showCallBack?.failed()
return
}
adOpenMgr.show(activity, showCallBack)
}
fun showInsert(activity: Activity, showCallBack: AdsShowCallBack? = null) {
if (!isInit || adsConfigBean?.isInBlackList != false) {
showCallBack?.failed()
return
}
adInsertMgr.show(activity, showCallBack)
}
fun showNative(parent: ViewGroup, layout: Int) {
if (!isInit || adsConfigBean?.isInBlackList != false) return
adNativeMgr.show(parent, layout)
}
fun showBanner(parent: ViewGroup) {
if (!isInit || adsConfigBean?.isInBlackList != false) return
adBannerMgr.show(parent)
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads
abstract class AdsShowCallBack {
open fun show() {}
abstract fun close()
abstract fun failed()
}
\ No newline at end of file
package com.base.locationsharewhite.ads
/**
* 广告类型
* 0=开屏广告、1=插屏广告、2=原生广告、3=Banner横幅广告
*/
@JvmInline
value class AdsType private constructor(val value: Int) {
companion object {
val OPEN = AdsType(0)
val INSERT = AdsType(1)
val NATIVE = AdsType(2)
val BANNER = AdsType(3)
fun from(adsType: Int): AdsType {
return when (adsType) {
OPEN.value -> OPEN
INSERT.value -> INSERT
NATIVE.value -> NATIVE
else -> BANNER
}
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads
import android.app.Activity
import android.content.Context
import java.lang.ref.WeakReference
/**
* 特定广告管理基类
* @param T 缓存广告的类
*/
abstract class BaseAdMgr<T> {
/**
* 广告展示回调
*/
protected var showCallBack: AdsShowCallBack? = null
/**
* 当前缓存的广告
*/
protected var currentAd: T? = null
/**
* 是否正在缓存加载广告
*/
protected var loadingAd: Boolean = false
/**
* 是否正在显示广告
*/
protected var showingAd: Boolean = false
/**
* 用于保存引用现有页面,在此页面显示广告(因为要等待广告加载完毕)
*/
protected var activityRef: WeakReference<Activity>? = null
/**
* 上一次的缓存成功时间
*/
protected var lastTime: Long = 0
/**
* 预加载广告
*
* @param context 加载所用的上下文,一般使用appContext
*/
abstract fun loadAd(context: Context)
/**
* 广告显示
*
* @param activity 当前页面
*/
abstract fun show(activity: Activity, showCallBack: AdsShowCallBack? = null)
/**
* 预加载的缓存超时判断
*
* @return true:没有超时 false:超时需要重新加载
*/
abstract fun adAvailable(): Boolean
}
\ No newline at end of file
package com.base.locationsharewhite.ads.admob
import android.os.Bundle
import android.view.ViewGroup
import android.view.ViewTreeObserver
import com.base.locationsharewhite.BuildConfig
import com.base.locationsharewhite.helper.ConfigHelper
import com.google.ads.mediation.admob.AdMobAdapter
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 java.util.UUID
/**
*banner广告加载显示管理类
*/
class AdBannerMgr {
private var adView: AdView? = null
private var listener: ViewTreeObserver.OnGlobalLayoutListener? = null
fun show(parent: ViewGroup) {
if (adView != null) {
adView?.destroy()
}
if (!LimitUtils.isAdShow()) {
adView = null
return
}
adView = AdView(parent.context)
adView?.tag = "CollapsibleBannerAd"
// val list = parent.children
// list.forEach {
// if (it.tag != "zhanweitu") {
// parent.removeView(it)
// }
// }
parent.addView(adView)
listener = ViewTreeObserver.OnGlobalLayoutListener {
val screenPixelDensity = parent.context.resources.displayMetrics.density
val adWidth = (parent.width / screenPixelDensity).toInt()
val adSize =
AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(parent.context, adWidth)
adView?.adUnitId =
if (BuildConfig.DEBUG) ConfigHelper.bannerAdmobIdTest else ConfigHelper.bannerAdmobId
adView?.setAdSize(adSize)
loadAd(parent)
parent.viewTreeObserver.removeOnGlobalLayoutListener(listener)
}
parent.viewTreeObserver.addOnGlobalLayoutListener(listener)
}
fun loadAd(parent: ViewGroup) {
val extras = Bundle()
extras.putString("collapsible", "bottom")
extras.putString("collapsible_request_id", UUID.randomUUID().toString())
val adRequest =
AdRequest.Builder().addNetworkExtrasBundle(AdMobAdapter::class.java, extras).build()
adView?.adListener =
object : AdListener() {
override fun onAdLoaded() {
}
override fun onAdOpened() {
}
override fun onAdClosed() {
super.onAdClosed()
// val removeList = arrayListOf<View>()
// parent.children.forEach {
// if (it.tag != "CollapsibleBannerAd") {
// removeList.add(it)
// }
// }
// removeList.forEach {
// parent.removeView(it)
// }
}
}
adView?.loadAd(adRequest)
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads.admob
import android.app.Activity
import android.content.Context
import com.base.locationsharewhite.BuildConfig
import com.base.locationsharewhite.ads.AdsShowCallBack
import com.base.locationsharewhite.ads.BaseAdMgr
import com.base.locationsharewhite.helper.ConfigHelper
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
import java.lang.ref.WeakReference
/**
*插屏广告加载显示管理类
*/
class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
private val adLoadCallback = object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) {
super.onAdLoaded(ad)
// 开屏广告加载成功
currentAd = ad
loadingAd = false
lastTime = System.currentTimeMillis()
val ac = activityRef?.get()
if (ac != null) {
show(ac)
}
}
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
super.onAdFailedToLoad(loadAdError)
// 广告加载失败
// 官方不建议在此回调中重新加载广告
// 如果确实需要,则必须限制最大重试次数,避免在网络受限的情况下连续多次请求
loadingAd = false
if (activityRef != null && !showingAd && showCallBack != null) {
showCallBack?.failed()
showCallBack = null
}
}
}
override fun loadAd(context: Context) {
if (!loadingAd && LimitUtils.isAdShow()) {
loadingAd = true
//计数
LimitUtils.addRequestNum()
InterstitialAd.load(
context,
if (BuildConfig.DEBUG) ConfigHelper.interAdmobIdTest else ConfigHelper.interAdmobId,
AdRequest.Builder().build(),
adLoadCallback
)
}
}
override fun show(activity: Activity, showCallBack: AdsShowCallBack?) {
if (activity.isFinishing || activity.isDestroyed || !LimitUtils.isAdShow()) {
showCallBack?.failed()
return
}
if (showingAd) {
// 开屏广告正在展示
return
}
if (showCallBack != null && this.showCallBack != showCallBack) this.showCallBack =
showCallBack
if (currentAd == null || !adAvailable()) {
//缓存广告过期
currentAd = null
if (activityRef == null) {
activityRef = WeakReference(activity)
// 开屏广告不可用或者缓存超时过期,重新加载
loadAd(activity.applicationContext)
} else {
activityRef = null
this.showCallBack?.failed()
this.showCallBack = null
}
return
}
currentAd?.run {
fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() {
super.onAdShowedFullScreenContent()
// 广告展示
currentAd = null
activityRef = null
this@AdInsertMgr.showCallBack?.show()
//计数
LimitUtils.addDisplayNum()
}
override fun onAdFailedToShowFullScreenContent(adError: AdError) {
super.onAdFailedToShowFullScreenContent(adError)
// 广告展示失败,清空缓存数据,重新加载
showingAd = false
currentAd = null
activityRef = null
this@AdInsertMgr.showCallBack?.failed()
this@AdInsertMgr.showCallBack = null
}
override fun onAdDismissedFullScreenContent() {
super.onAdDismissedFullScreenContent()
// 广告关闭,清空缓存数据,重新加载
showingAd = false
this@AdInsertMgr.showCallBack?.close()
this@AdInsertMgr.showCallBack = null
loadAd(activity.applicationContext)
}
override fun onAdClicked() {
super.onAdClicked()
//计数
LimitUtils.addClickNum()
}
}
showingAd = true
show(activity)
}
}
override fun adAvailable(): Boolean {
return ((System.currentTimeMillis() - lastTime) / 1000 / 60).toInt() < 30
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads.admob
import android.content.Context
import android.view.ViewGroup
import androidx.core.view.isVisible
import com.base.locationsharewhite.BuildConfig
import com.base.locationsharewhite.helper.ConfigHelper
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
import com.google.android.gms.ads.nativead.NativeAdOptions
import java.util.concurrent.ConcurrentLinkedDeque
/**
*原生广告加载显示管理类
*/
class AdNativeMgr {
/**
* 上一次的缓存成功时间
*/
protected var lastTime: Long = 0
/**
* 原生广告缓存队列
*/
private val cacheItems = ConcurrentLinkedDeque<NativeAd>()
fun loadAd(context: Context, parent: ViewGroup? = null, layout: Int? = null) {
val adLoader = AdLoader.Builder(
context,
if (BuildConfig.DEBUG) ConfigHelper.nativeAdmobIdTest else ConfigHelper.nativeAdmobId
)
.forNativeAd { nativeAd ->
cacheItems.offer(nativeAd)
lastTime = System.currentTimeMillis()
if (parent != null && layout != null) show(parent, layout)
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(error: LoadAdError) {
}
override fun onAdClicked() {
super.onAdClicked()
}
override fun onAdClosed() {
super.onAdClosed()
}
})
.withNativeAdOptions(
NativeAdOptions.Builder()
// .setAdChoicesPlacement(NativeAdOptions.ADCHOICES_TOP_RIGHT)
// .setMediaAspectRatio(NativeAdOptions.NATIVE_MEDIA_ASPECT_RATIO_SQUARE)
.build()
)
.build()
// Load AdMob native advanced ads
adLoader.loadAds(AdRequest.Builder().build(), 1)
}
fun show(parent: ViewGroup, layout: Int) {
if (!LimitUtils.isAdShow()) {
cacheItems.clear()
return
}
val nativeAd = cacheItems.peek()
if (nativeAd == null || !adAvailable()) {
//缓存过期了就清空
cacheItems.clear()
loadAd(parent.context.applicationContext, parent, layout)
return
}
NativeView(parent.context, layout).run {
parent.removeAllViews()
setNativeAd(nativeAd)
parent.addView(this)
parent.isVisible = true
}
loadAd(parent.context.applicationContext)
}
fun adAvailable(): Boolean {
return ((System.currentTimeMillis() - lastTime) / 1000 / 60).toInt() < 30
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads.admob
import android.app.Activity
import android.content.Context
import com.base.locationsharewhite.BuildConfig
import com.base.locationsharewhite.ads.AdsShowCallBack
import com.base.locationsharewhite.ads.BaseAdMgr
import com.base.locationsharewhite.helper.ConfigHelper
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.appopen.AppOpenAd
import java.lang.ref.WeakReference
/**
* 开屏广告加载显示管理类
*/
class AdOpenMgr : BaseAdMgr<AppOpenAd>() {
private val adLoadCallback = object : AppOpenAd.AppOpenAdLoadCallback() {
override fun onAdLoaded(appOpenAd: AppOpenAd) {
super.onAdLoaded(appOpenAd)
// 开屏广告加载成功
currentAd = appOpenAd
loadingAd = false
lastTime = System.currentTimeMillis()
val ac = activityRef?.get()
if (ac != null) {
show(ac)
}
}
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
super.onAdFailedToLoad(loadAdError)
// 开屏广告加载失败
// 官方不建议在此回调中重新加载广告
// 如果确实需要,则必须限制最大重试次数,避免在网络受限的情况下连续多次请求
loadingAd = false
if (activityRef != null && !showingAd && showCallBack != null) {
showCallBack?.failed()
showCallBack = null
}
}
}
override fun loadAd(context: Context) {
if (!loadingAd && LimitUtils.isAdShow()) {
loadingAd = true
//计数
LimitUtils.addRequestNum()
AppOpenAd.load(
context,
if (BuildConfig.DEBUG) ConfigHelper.openAdmobIdTest else ConfigHelper.openAdmobId,
AdRequest.Builder().build(),
adLoadCallback
)
}
}
override fun show(activity: Activity, showCallBack: AdsShowCallBack?) {
if (activity.isFinishing || activity.isDestroyed || !LimitUtils.isAdShow()) {
showCallBack?.failed()
return
}
if (showingAd) {
// 开屏广告正在展示
return
}
if (showCallBack != null && this.showCallBack != showCallBack) this.showCallBack =
showCallBack
if (currentAd == null || !adAvailable()) {
//缓存广告过期
currentAd = null
if (activityRef == null) {
activityRef = WeakReference(activity)
// 开屏广告不可用或者缓存超时过期,重新加载
loadAd(activity.applicationContext)
} else {
activityRef = null
this.showCallBack?.failed()
this.showCallBack = null
}
return
}
currentAd?.run {
fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() {
super.onAdShowedFullScreenContent()
// 广告展示
currentAd = null
activityRef = null
this@AdOpenMgr.showCallBack?.show()
//计数
LimitUtils.addDisplayNum()
}
override fun onAdFailedToShowFullScreenContent(adError: AdError) {
super.onAdFailedToShowFullScreenContent(adError)
// 广告展示失败,清空缓存数据,重新加载
showingAd = false
currentAd = null
activityRef = null
this@AdOpenMgr.showCallBack?.failed()
this@AdOpenMgr.showCallBack = null
}
override fun onAdDismissedFullScreenContent() {
super.onAdDismissedFullScreenContent()
// 广告关闭,清空缓存数据,重新加载
showingAd = false
this@AdOpenMgr.showCallBack?.close()
this@AdOpenMgr.showCallBack = null
loadAd(activity.applicationContext)
}
override fun onAdClicked() {
super.onAdClicked()
//计数
LimitUtils.addClickNum()
}
}
showingAd = true
show(activity)
}
}
override fun adAvailable(): Boolean {
return ((System.currentTimeMillis() - lastTime) / 1000 / 60).toInt() < 30
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads.admob
import com.base.locationsharewhite.ads.AdsMgr
import com.base.locationsharewhite.utils.AppPreferences
import com.base.locationsharewhite.utils.KotlinExt.toFormatTime4
/**
* 控制广告计数与判断显示条件
*
*/
object LimitUtils {
const val NUM_DISPLAY = "numDisplayLimit"
const val NUM_REQUEST = "numRequestLimit"
const val NUM_CLICK = "numClickLimit"
const val SAVE_DATE = "SAVE_DATE"
/**
* 保存的时间,用来判断是否是当天,不是当天要重置计数次数
*/
private var saveDate
get() = AppPreferences.getInstance()
.getString(SAVE_DATE, System.currentTimeMillis().toFormatTime4())
set(value) = AppPreferences.getInstance().put(SAVE_DATE, value)
/**
* 广告请求是否到达限制
*/
private inline val isRequestLimited: Boolean
get() {
return AppPreferences.getInstance()
.getInt(NUM_REQUEST, 0) >= (AdsMgr.adsConfigBean?.numRequestLimit ?: 100)
}
/**
* 广告展示是否到达限制
*/
private inline val isDisplayLimited: Boolean
get() {
return AppPreferences.getInstance()
.getInt(NUM_DISPLAY, 0) >= (AdsMgr.adsConfigBean?.numDisplayLimit ?: 100)
}
/**
* 广告点击是否到达限制
*/
private inline val isClickLimited: Boolean
get() {
return AppPreferences.getInstance()
.getInt(NUM_CLICK, 0) >= (AdsMgr.adsConfigBean?.numClickLimit ?: 100)
}
/**
* 是否显示广告
*
* @return true or false
*/
fun isAdShow(): Boolean {
val currentDate = System.currentTimeMillis().toFormatTime4()
if (saveDate != currentDate) {
//如果已经不是今天了,就重置个数
saveDate = currentDate
AppPreferences.getInstance().put(NUM_DISPLAY, 0)
AppPreferences.getInstance().put(NUM_REQUEST, 0)
AppPreferences.getInstance().put(NUM_CLICK, 0)
}
return !(isDisplayLimited || isClickLimited || isRequestLimited)
}
private fun addNum(key: String) {
val currentDate = System.currentTimeMillis().toFormatTime4()
if (saveDate != currentDate) {
//如果已经不是今天了,就重置个数
saveDate = currentDate
AppPreferences.getInstance().put(key, 1)
return
}
AppPreferences.getInstance()
.put(key, (AppPreferences.getInstance().getInt(key, 0) + 1))
}
fun addDisplayNum() {
addNum(NUM_DISPLAY)
}
fun addRequestNum() {
addNum(NUM_REQUEST)
}
fun addClickNum() {
addNum(NUM_CLICK)
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads.admob
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import com.base.locationsharewhite.R
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdView
@SuppressLint("ViewConstructor")
class NativeView(context: Context, val layout: Int, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
init {
layoutParams =
LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
fun setNativeAd(nativeAd: NativeAd?) {
nativeAd ?: return
val adView = LayoutInflater.from(context)
.inflate(layout, this, false) as NativeAdView
adView.mediaView = adView.findViewById(R.id.ad_media)
adView.headlineView = adView.findViewById(R.id.ad_headline)
adView.bodyView = adView.findViewById(R.id.ad_body)
adView.callToActionView = adView.findViewById(R.id.ad_call_to_action)
adView.iconView = adView.findViewById(R.id.ad_app_icon)
(adView.headlineView as TextView?)?.text = nativeAd.headline
adView.mediaView!!.mediaContent = nativeAd.mediaContent
if (nativeAd.body == null) {
adView.bodyView!!.visibility = View.INVISIBLE
} else {
adView.bodyView!!.visibility = View.VISIBLE
(adView.bodyView as TextView?)?.text = nativeAd.body
}
if (nativeAd.callToAction == null) {
adView.callToActionView!!.visibility = View.INVISIBLE
} else {
adView.callToActionView!!.visibility = View.VISIBLE
(adView.callToActionView as Button?)?.text = nativeAd.callToAction
}
if (nativeAd.icon == null) {
adView.iconView!!.visibility = View.GONE
} else {
(adView.iconView as ImageView?)?.setImageDrawable(
nativeAd.icon!!.drawable
)
adView.iconView!!.visibility = View.VISIBLE
}
adView.setNativeAd(nativeAd)
removeAllViews()
addView(adView)
}
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment