Commit 2080767f authored by wanglei's avatar wanglei

...

parent 2766f4c5
package com.base.locationsharewhite.ads package com.base.locationsharewhite.ads
import com.base.locationsharewhite.utils.AppPreferences
/** /**
* 广告限制配置 * 广告限制配置
* *
...@@ -15,5 +17,20 @@ data class AdsConfigBean( ...@@ -15,5 +17,20 @@ data class AdsConfigBean(
var numRequestLimit: Int = -1, var numRequestLimit: Int = -1,
var numClickLimit: Int = -1, var numClickLimit: Int = -1,
var timeInterval: Int = 1, var timeInterval: Int = 1,
val openAdLoading: Int = 15, var openAdLoading: Int = 15,
) ) {
\ No newline at end of file companion object {
fun getSpConfigBean(): AdsConfigBean {
val adsConfigBean = AdsConfigBean()
adsConfigBean.apply {
isInBlackList = AppPreferences.getInstance().getBoolean("isInBlackList", false)
numDisplayLimit = AppPreferences.getInstance().getInt("numDisplayLimit", -1)
numRequestLimit = AppPreferences.getInstance().getInt("numRequestLimit", -1)
numClickLimit = AppPreferences.getInstance().getInt("numClickLimit", -1)
timeInterval = AppPreferences.getInstance().getInt("timeInterval", 1)
openAdLoading = AppPreferences.getInstance().getInt("openAdLoading", 15)
}
return adsConfigBean
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads package com.base.locationsharewhite.ads
import AdInsertMgr
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.view.ViewGroup import android.view.ViewGroup
import com.base.locationsharewhite.ads.admob.AdBannerMgr 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.AdNativeMgr
import com.base.locationsharewhite.ads.admob.AdOpenMgr import com.base.locationsharewhite.ads.admob.AdOpenMgr
import com.google.android.gms.ads.MobileAds import com.google.android.gms.ads.MobileAds
...@@ -70,10 +70,6 @@ object AdsMgr { ...@@ -70,10 +70,6 @@ object AdsMgr {
* @param showCallBack 展示回调 * @param showCallBack 展示回调
*/ */
fun showOpen(activity: Activity, showCallBack: AdsShowCallBack? = null) { fun showOpen(activity: Activity, showCallBack: AdsShowCallBack? = null) {
// if (!isInit || adsConfigBean?.isInBlackList != false) {
// showCallBack?.failed()
// return
// }
adOpenMgr.show(activity, showCallBack) adOpenMgr.show(activity, showCallBack)
} }
......
...@@ -3,6 +3,7 @@ package com.base.locationsharewhite.ads.admob ...@@ -3,6 +3,7 @@ package com.base.locationsharewhite.ads.admob
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import com.base.locationsharewhite.ads.AdsType
import com.base.locationsharewhite.helper.config.ConstConfig import com.base.locationsharewhite.helper.config.ConstConfig
import com.google.ads.mediation.admob.AdMobAdapter import com.google.ads.mediation.admob.AdMobAdapter
import com.google.android.gms.ads.AdListener import com.google.android.gms.ads.AdListener
...@@ -19,39 +20,37 @@ class AdBannerMgr { ...@@ -19,39 +20,37 @@ class AdBannerMgr {
private var adView: AdView? = null private var adView: AdView? = null
private var listener: ViewTreeObserver.OnGlobalLayoutListener? = null private var listener: ViewTreeObserver.OnGlobalLayoutListener? = null
fun show(parent: ViewGroup) { fun show(parent: ViewGroup, adClose: (() -> Unit)? = null) {
if (adView != null) {
adView?.destroy() if (!LimitUtils.isAdShow(AdsType.BANNER)) {
}
if (!LimitUtils.isAdShow()) {
adView = null adView = null
return return
} }
parent.removeAllViews()
adView?.destroy()
adView = null
adView = AdView(parent.context) adView = AdView(parent.context)
adView?.tag = "CollapsibleBannerAd"
// val list = parent.children
// list.forEach {
// if (it.tag != "zhanweitu") {
// parent.removeView(it)
// }
// }
parent.addView(adView) parent.addView(adView)
adView?.onPaidEventListener = AdmobEvent.EventOnPaidEventListener(adView)
listener = ViewTreeObserver.OnGlobalLayoutListener { listener = ViewTreeObserver.OnGlobalLayoutListener {
val screenPixelDensity = parent.context.resources.displayMetrics.density val screenPixelDensity = parent.context.resources.displayMetrics.density
val adWidth = (parent.width / screenPixelDensity).toInt() val adWidth = (parent.width / screenPixelDensity).toInt()
val adSize = val adSize =
AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(parent.context, adWidth) AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(parent.context, adWidth)
adView?.adUnitId = adView?.adUnitId = ConstConfig.bannerAdId
ConstConfig.bannerAdId
adView?.setAdSize(adSize) adView?.setAdSize(adSize)
loadAd(parent) loadAd(adClose)
parent.viewTreeObserver.removeOnGlobalLayoutListener(listener) parent.viewTreeObserver.removeOnGlobalLayoutListener(listener)
} }
parent.viewTreeObserver.addOnGlobalLayoutListener(listener) parent.viewTreeObserver.addOnGlobalLayoutListener(listener)
} }
fun loadAd(parent: ViewGroup) { fun loadAd(adClose: (() -> Unit)?) {
val extras = Bundle() val extras = Bundle()
extras.putString("collapsible", "bottom") extras.putString("collapsible", "bottom")
extras.putString("collapsible_request_id", UUID.randomUUID().toString()) extras.putString("collapsible_request_id", UUID.randomUUID().toString())
...@@ -69,15 +68,7 @@ class AdBannerMgr { ...@@ -69,15 +68,7 @@ class AdBannerMgr {
override fun onAdClosed() { override fun onAdClosed() {
super.onAdClosed() super.onAdClosed()
// val removeList = arrayListOf<View>() adClose?.invoke()
// parent.children.forEach {
// if (it.tag != "CollapsibleBannerAd") {
// removeList.add(it)
// }
// }
// removeList.forEach {
// parent.removeView(it)
// }
} }
} }
adView?.loadAd(adRequest) adView?.loadAd(adRequest)
......
package com.base.locationsharewhite.ads.admob
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import com.base.locationsharewhite.ads.AdsShowCallBack import com.base.locationsharewhite.ads.AdsShowCallBack
import com.base.locationsharewhite.ads.AdsType
import com.base.locationsharewhite.ads.BaseAdMgr import com.base.locationsharewhite.ads.BaseAdMgr
import com.base.locationsharewhite.ads.admob.LimitUtils import com.base.locationsharewhite.helper.EventUtils
import com.base.locationsharewhite.helper.config.ConstConfig import com.base.locationsharewhite.helper.config.ConstConfig
import com.google.android.gms.ads.AdError import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdRequest import com.google.android.gms.ads.AdRequest
...@@ -10,7 +13,9 @@ import com.google.android.gms.ads.FullScreenContentCallback ...@@ -10,7 +13,9 @@ import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.interstitial.InterstitialAd import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
import org.json.JSONObject
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.UUID
/** /**
*插屏广告加载显示管理类 *插屏广告加载显示管理类
...@@ -19,9 +24,26 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() { ...@@ -19,9 +24,26 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
private var lastOpenDate: Long = 0 private var lastOpenDate: Long = 0
private val adLoadCallback = object : InterstitialAdLoadCallback() { override fun loadAd(context: Context) {
if (!loadingAd && LimitUtils.isAdShow(AdsType.INSERT)) {
loadingAd = true
//计数
LimitUtils.addRequestNum()
val reqId = UUID.randomUUID().toString()
val obj = JSONObject()
obj.put("req_id", reqId)
obj.put("ad_type", "interAd")
obj.put("from", context.javaClass.simpleName)
EventUtils.event("ad_pull_start", ext = obj)
InterstitialAd.load(
context,
ConstConfig.insertAdId,
AdRequest.Builder().build(),
object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) { override fun onAdLoaded(ad: InterstitialAd) {
super.onAdLoaded(ad) super.onAdLoaded(ad)
AdmobEvent.pullAd(ad.responseInfo, "interAd", reqId = reqId)
ad.onPaidEventListener = AdmobEvent.EventOnPaidEventListener(ad)
// 开屏广告加载成功 // 开屏广告加载成功
currentAd = ad currentAd = ad
loadingAd = false loadingAd = false
...@@ -34,6 +56,12 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() { ...@@ -34,6 +56,12 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
override fun onAdFailedToLoad(loadAdError: LoadAdError) { override fun onAdFailedToLoad(loadAdError: LoadAdError) {
super.onAdFailedToLoad(loadAdError) super.onAdFailedToLoad(loadAdError)
AdmobEvent.pullAd(
loadAdError.responseInfo,
"interAd",
loadAdError.message,
reqId = reqId
)
// 广告加载失败 // 广告加载失败
// 官方不建议在此回调中重新加载广告 // 官方不建议在此回调中重新加载广告
// 如果确实需要,则必须限制最大重试次数,避免在网络受限的情况下连续多次请求 // 如果确实需要,则必须限制最大重试次数,避免在网络受限的情况下连续多次请求
...@@ -44,26 +72,20 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() { ...@@ -44,26 +72,20 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
} }
} }
} }
override fun loadAd(context: Context) {
if (!loadingAd && LimitUtils.isAdShow()) {
loadingAd = true
//计数
LimitUtils.addRequestNum()
InterstitialAd.load(
context,
ConstConfig.insertAdId,
AdRequest.Builder().build(),
adLoadCallback
) )
} }
} }
override fun show(activity: Activity, showCallBack: AdsShowCallBack?) { override fun show(activity: Activity, showCallBack: AdsShowCallBack?) {
if (activity.isFinishing || activity.isDestroyed || !LimitUtils.isAdShow() || LimitUtils.isIntervalLimited( if (activity.isFinishing || activity.isDestroyed) {
lastOpenDate showCallBack?.failed()
) return
) { }
if (!LimitUtils.isAdShow(AdsType.INSERT)) {
showCallBack?.failed()
return
}
if (LimitUtils.isIntervalLimited(lastOpenDate)) {
showCallBack?.failed() showCallBack?.failed()
return return
} }
...@@ -73,7 +95,20 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() { ...@@ -73,7 +95,20 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
} }
if (showCallBack != null && this.showCallBack != showCallBack) this.showCallBack = if (showCallBack != null && this.showCallBack != showCallBack) this.showCallBack =
showCallBack showCallBack
if (currentAd == null || !adAvailable()) { if ((currentAd == null).also {
if (it) {
val obj = JSONObject()
obj.put("reason", "no_ad")
obj.put("ad_unit", "interAd")
EventUtils.event("ad_show_error", ext = obj)
}
} || (!adAvailable()).also {
if (it) {
val obj2 = JSONObject()
obj2.put("ad_unit", "interAd")
EventUtils.event("ad_expire", ext = obj2)
}
}) {
//缓存广告过期 //缓存广告过期
currentAd = null currentAd = null
if (activityRef == null) { if (activityRef == null) {
...@@ -84,9 +119,16 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() { ...@@ -84,9 +119,16 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
activityRef = null activityRef = null
this.showCallBack?.failed() this.showCallBack?.failed()
this.showCallBack = null this.showCallBack = null
val obj = JSONObject()
obj.put("reason", "no_ad")
obj.put("ad_unit", "interAd")
EventUtils.event("ad_show_error", ext = obj)
} }
return return
} }
val obj1 = JSONObject()
obj1.put("ad_unit", "interAd")
EventUtils.event("ad_prepare_show", ext = obj1)
currentAd?.run { currentAd?.run {
fullScreenContentCallback = object : FullScreenContentCallback() { fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() { override fun onAdShowedFullScreenContent() {
...@@ -96,7 +138,7 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() { ...@@ -96,7 +138,7 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
currentAd = null currentAd = null
activityRef = null activityRef = null
this@AdInsertMgr.showCallBack?.show() this@AdInsertMgr.showCallBack?.show()
AdmobEvent.showAd(this@run.responseInfo, "interAd", activity)
//计数 //计数
LimitUtils.addDisplayNum() LimitUtils.addDisplayNum()
} }
...@@ -109,6 +151,11 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() { ...@@ -109,6 +151,11 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
activityRef = null activityRef = null
this@AdInsertMgr.showCallBack?.googleFailed() this@AdInsertMgr.showCallBack?.googleFailed()
this@AdInsertMgr.showCallBack = null this@AdInsertMgr.showCallBack = null
val obj = JSONObject()
obj.put("reason", adError.message)
obj.put("code", adError.code)
obj.put("ad_unit", "interAd")
EventUtils.event("ad_show_error", ext = obj)
} }
override fun onAdDismissedFullScreenContent() { override fun onAdDismissedFullScreenContent() {
...@@ -123,6 +170,7 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() { ...@@ -123,6 +170,7 @@ class AdInsertMgr : BaseAdMgr<InterstitialAd>() {
override fun onAdClicked() { override fun onAdClicked() {
super.onAdClicked() super.onAdClicked()
AdmobEvent.clickAd(this@run.responseInfo, "interAd")
//计数 //计数
LimitUtils.addClickNum() LimitUtils.addClickNum()
} }
......
...@@ -3,14 +3,17 @@ package com.base.locationsharewhite.ads.admob ...@@ -3,14 +3,17 @@ package com.base.locationsharewhite.ads.admob
import android.content.Context import android.content.Context
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.base.locationsharewhite.ads.AdsType
import com.base.locationsharewhite.helper.EventUtils
import com.base.locationsharewhite.helper.config.ConstConfig import com.base.locationsharewhite.helper.config.ConstConfig
import com.example.mydemo.strategy.ads.admob.NativeView
import com.google.android.gms.ads.AdListener import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdLoader import com.google.android.gms.ads.AdLoader
import com.google.android.gms.ads.AdRequest import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.nativead.NativeAd import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.nativead.NativeAdOptions import com.google.android.gms.ads.nativead.NativeAdOptions
import org.json.JSONObject
import java.util.UUID
import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.ConcurrentLinkedDeque
/** /**
...@@ -29,6 +32,12 @@ class AdNativeMgr { ...@@ -29,6 +32,12 @@ class AdNativeMgr {
private val cacheItems = ConcurrentLinkedDeque<NativeAd>() private val cacheItems = ConcurrentLinkedDeque<NativeAd>()
fun loadAd(context: Context, parent: ViewGroup? = null, layout: Int? = null) { fun loadAd(context: Context, parent: ViewGroup? = null, layout: Int? = null) {
if (LimitUtils.isAdShow(AdsType.NATIVE)) return
val reqId = UUID.randomUUID().toString()
val obj = JSONObject()
obj.put("req_id", reqId)
obj.put("ad_type", "nativeAd")
val adLoader = AdLoader.Builder( val adLoader = AdLoader.Builder(
context, context,
ConstConfig.nativeAdId ConstConfig.nativeAdId
...@@ -36,15 +45,19 @@ class AdNativeMgr { ...@@ -36,15 +45,19 @@ class AdNativeMgr {
.forNativeAd { nativeAd -> .forNativeAd { nativeAd ->
cacheItems.offer(nativeAd) cacheItems.offer(nativeAd)
lastTime = System.currentTimeMillis() lastTime = System.currentTimeMillis()
nativeAd.setOnPaidEventListener(AdmobEvent.EventOnPaidEventListener(nativeAd))
AdmobEvent.pullAd(nativeAd.responseInfo, "nativeAd", reqId = reqId)
if (parent != null && layout != null) show(parent, layout) if (parent != null && layout != null) show(parent, layout)
} }
.withAdListener(object : AdListener() { .withAdListener(object : AdListener() {
override fun onAdFailedToLoad(error: LoadAdError) { override fun onAdFailedToLoad(error: LoadAdError) {
AdmobEvent.pullAd(error.responseInfo, "nativeAd", error.message, reqId = reqId)
} }
override fun onAdClicked() { override fun onAdClicked() {
super.onAdClicked() super.onAdClicked()
AdmobEvent.clickAd(cacheItems.lastOrNull()?.responseInfo, "nativeAd")
} }
override fun onAdClosed() { override fun onAdClosed() {
...@@ -63,22 +76,39 @@ class AdNativeMgr { ...@@ -63,22 +76,39 @@ class AdNativeMgr {
} }
fun show(parent: ViewGroup, layout: Int) { fun show(parent: ViewGroup, layout: Int) {
if (!LimitUtils.isAdShow()) { if (!LimitUtils.isAdShow(AdsType.NATIVE)) {
cacheItems.clear() cacheItems.clear()
return return
} }
val nativeAd = cacheItems.peek() val nativeAd = cacheItems.peek()
if (nativeAd == null || !adAvailable()) { if ((nativeAd == null).also {
if (it) {
val obj2 = JSONObject()
obj2.put("reason", "no_ad")
obj2.put("ad_unit", "nativeAd")
EventUtils.event("ad_show_error", ext = obj2)
}
} || (!adAvailable()).also {
if (it) {
val obj2 = JSONObject()
obj2.put("ad_unit", "nativeAd")
EventUtils.event("ad_expire", ext = obj2)
}
}) {
//缓存过期了就清空 //缓存过期了就清空
cacheItems.clear() cacheItems.clear()
loadAd(parent.context.applicationContext, parent, layout) loadAd(parent.context.applicationContext, parent, layout)
return return
} }
val obj = JSONObject()
obj.put("ad_unit", "nativeAd")
EventUtils.event("ad_prepare_show_native", ext = obj)
NativeView(parent.context, layout).run { NativeView(parent.context, layout).run {
parent.removeAllViews() parent.removeAllViews()
setNativeAd(nativeAd) setNativeAd(nativeAd)
parent.addView(this) parent.addView(this)
parent.isVisible = true parent.isVisible = true
AdmobEvent.showAd(nativeAd?.responseInfo, "nativeAd")
} }
loadAd(parent.context.applicationContext) loadAd(parent.context.applicationContext)
} }
......
...@@ -3,26 +3,41 @@ package com.base.locationsharewhite.ads.admob ...@@ -3,26 +3,41 @@ package com.base.locationsharewhite.ads.admob
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import com.base.locationsharewhite.ads.AdsShowCallBack import com.base.locationsharewhite.ads.AdsShowCallBack
import com.base.locationsharewhite.ads.AdsType
import com.base.locationsharewhite.ads.BaseAdMgr import com.base.locationsharewhite.ads.BaseAdMgr
import com.base.locationsharewhite.helper.EventUtils
import com.base.locationsharewhite.helper.config.ConstConfig import com.base.locationsharewhite.helper.config.ConstConfig
import com.base.locationsharewhite.utils.LogEx
import com.google.android.gms.ads.AdError import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdRequest import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.FullScreenContentCallback import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.appopen.AppOpenAd import com.google.android.gms.ads.appopen.AppOpenAd
import org.json.JSONObject
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.UUID
/** /**
* 开屏广告加载显示管理类 * 开屏广告加载显示管理类
*/ */
class AdOpenMgr : BaseAdMgr<AppOpenAd>() { class AdOpenMgr : BaseAdMgr<AppOpenAd>() {
private val TAG = "AdOpenMgr"
private var lastOpenDate: Long = 0 private var lastOpenDate: Long = 0
private val adLoadCallback = object : AppOpenAd.AppOpenAdLoadCallback() { override fun loadAd(context: Context) {
if (!loadingAd && LimitUtils.isAdShow(AdsType.OPEN)) {
loadingAd = true
//计数
LimitUtils.addRequestNum()
val reqId = UUID.randomUUID().toString()
val obj = JSONObject()
obj.put("req_id", reqId)
obj.put("ad_type", "openAd")
EventUtils.event("ad_pull_start", ext = obj)
AppOpenAd.load(
context,
ConstConfig.openAdId,
AdRequest.Builder().build(),
object : AppOpenAd.AppOpenAdLoadCallback() {
override fun onAdLoaded(appOpenAd: AppOpenAd) { override fun onAdLoaded(appOpenAd: AppOpenAd) {
super.onAdLoaded(appOpenAd) super.onAdLoaded(appOpenAd)
// 开屏广告加载成功 // 开屏广告加载成功
...@@ -33,6 +48,9 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() { ...@@ -33,6 +48,9 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() {
if (ac != null) { if (ac != null) {
show(ac) show(ac)
} }
AdmobEvent.pullAd(appOpenAd.responseInfo, "openAd", reqId = reqId)
appOpenAd.onPaidEventListener =
AdmobEvent.EventOnPaidEventListener(appOpenAd)
} }
override fun onAdFailedToLoad(loadAdError: LoadAdError) { override fun onAdFailedToLoad(loadAdError: LoadAdError) {
...@@ -45,39 +63,53 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() { ...@@ -45,39 +63,53 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() {
showCallBack?.googleFailed() showCallBack?.googleFailed()
showCallBack = null showCallBack = null
} }
AdmobEvent.pullAd(
loadAdError.responseInfo,
"openAd",
loadAdError.message,
reqId = reqId
)
} }
} }
override fun loadAd(context: Context) {
if (!loadingAd && LimitUtils.isAdShow()) {
loadingAd = true
//计数
LimitUtils.addRequestNum()
AppOpenAd.load(
context,
ConstConfig.openAdId,
AdRequest.Builder().build(),
adLoadCallback
) )
} }
} }
override fun show(activity: Activity, showCallBack: AdsShowCallBack?) { override fun show(activity: Activity, showCallBack: AdsShowCallBack?) {
if (activity.isFinishing || activity.isDestroyed || !LimitUtils.isAdShow() || LimitUtils.isIntervalLimited( if (activity.isFinishing || activity.isDestroyed) {
lastOpenDate showCallBack?.failed()
) return
) { }
LogEx.logDebug(TAG, "failed 1")
if (!LimitUtils.isAdShow(AdsType.OPEN)) {
showCallBack?.failed() showCallBack?.failed()
return return
} }
if (LimitUtils.isIntervalLimited(lastOpenDate)) {
showCallBack?.failed()
return
}
if (showingAd) { if (showingAd) {
// 开屏广告正在展示 // 开屏广告正在展示
return return
} }
if (showCallBack != null && this.showCallBack != showCallBack) this.showCallBack = if (showCallBack != null && this.showCallBack != showCallBack) this.showCallBack =
showCallBack showCallBack
if (currentAd == null || !adAvailable()) { if ((currentAd == null).also {
if (it) {
val obj = JSONObject()
obj.put("reason", "no_ad")
obj.put("ad_unit", "openAd")
EventUtils.event("ad_show_error", ext = obj)
}
} || (!adAvailable()).also {
if (it) {
val obj2 = JSONObject()
obj2.put("ad_unit", "openAd")
EventUtils.event("ad_expire", ext = obj2)
}
}) {
//缓存广告过期 //缓存广告过期
currentAd = null currentAd = null
if (activityRef == null) { if (activityRef == null) {
...@@ -86,12 +118,18 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() { ...@@ -86,12 +118,18 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() {
loadAd(activity.applicationContext) loadAd(activity.applicationContext)
} else { } else {
activityRef = null activityRef = null
LogEx.logDebug(TAG, "failed 2")
this.showCallBack?.failed() this.showCallBack?.failed()
this.showCallBack = null this.showCallBack = null
val obj = JSONObject()
obj.put("reason", "no_ad")
obj.put("ad_unit", "openAd")
EventUtils.event("ad_show_error", ext = obj)
} }
return return
} }
val obj1 = JSONObject()
obj1.put("ad_unit", "openAd")
EventUtils.event("ad_prepare_show", ext = obj1)
currentAd?.run { currentAd?.run {
fullScreenContentCallback = object : FullScreenContentCallback() { fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() { override fun onAdShowedFullScreenContent() {
...@@ -101,9 +139,9 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() { ...@@ -101,9 +139,9 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() {
currentAd = null currentAd = null
activityRef = null activityRef = null
this@AdOpenMgr.showCallBack?.show() this@AdOpenMgr.showCallBack?.show()
//计数 //计数
LimitUtils.addDisplayNum() LimitUtils.addDisplayNum()
AdmobEvent.showAd(this@run.responseInfo, "openAd", activity)
} }
override fun onAdFailedToShowFullScreenContent(adError: AdError) { override fun onAdFailedToShowFullScreenContent(adError: AdError) {
...@@ -114,6 +152,11 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() { ...@@ -114,6 +152,11 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() {
activityRef = null activityRef = null
this@AdOpenMgr.showCallBack?.googleFailed() this@AdOpenMgr.showCallBack?.googleFailed()
this@AdOpenMgr.showCallBack = null this@AdOpenMgr.showCallBack = null
val obj = JSONObject()
obj.put("reason", adError.message)
obj.put("code", adError.code)
obj.put("ad_unit", "openAd")
EventUtils.event("ad_show_error", ext = obj)
} }
override fun onAdDismissedFullScreenContent() { override fun onAdDismissedFullScreenContent() {
...@@ -127,6 +170,7 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() { ...@@ -127,6 +170,7 @@ class AdOpenMgr : BaseAdMgr<AppOpenAd>() {
override fun onAdClicked() { override fun onAdClicked() {
super.onAdClicked() super.onAdClicked()
AdmobEvent.clickAd(this@run.responseInfo, "openAd")
//计数 //计数
LimitUtils.addClickNum() LimitUtils.addClickNum()
} }
......
package com.base.locationsharewhite.ads.admob
import android.app.Activity
import android.os.Bundle
import com.base.locationsharewhite.helper.EventUtils
import com.base.locationsharewhite.helper.MyApplication
import com.base.locationsharewhite.utils.LogEx
import com.facebook.appevents.AppEventsConstants
import com.facebook.appevents.AppEventsLogger
import com.google.android.gms.ads.AdValue
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.OnPaidEventListener
import com.google.android.gms.ads.ResponseInfo
import com.google.android.gms.ads.appopen.AppOpenAd
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.nativead.NativeAd
import com.google.android.gms.ads.rewarded.RewardedAd
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import org.json.JSONObject
object AdmobEvent {
private val TAG = "AdmobEvent"
fun pullAd(
responseInfo: ResponseInfo?,
adUnit: String,
error: String? = null,
reqId: String? = null
) {
val obj = JSONObject()
if (responseInfo != null) {
val response = responseInfo.adapterResponses.getOrNull(0)
if (response != null) {
obj.put("source", response.adSourceName)
val credentials = mapOf(
"placementid" to response.credentials.get("placementid"),
"appid" to response.credentials.get("appid"),
"pubid" to response.credentials.get("pubid")
)
obj.put("credentials", credentials.toString())
}
obj.put("session_id", responseInfo.responseId)
}
obj.put("networkname", responseInfo?.mediationAdapterClassName)
obj.put("ad_unit", adUnit)
obj.put("req_id", reqId)
if (error == null) {
obj.put("status", "1")
} else {
obj.put("errMsg", error)
obj.put("status", "2")
}
LogEx.logDebug(TAG, "obj=$obj")
EventUtils.event("ad_pull", ext = obj)
}
private val taichiPref by lazy {
MyApplication.appContext.getSharedPreferences("TaichiTroasCache", 0)
}
private val taichiSharedPreferencesEditor by lazy {
taichiPref.edit()
}
class EventOnPaidEventListener(private val ad: Any?) : OnPaidEventListener {
override fun onPaidEvent(adValue: AdValue) {
val valueMicros = adValue.valueMicros
val currencyCode = adValue.currencyCode
val precision = adValue.precisionType
val obj = JSONObject()
obj.put("valueMicros", valueMicros)
obj.put("currencyCode", currencyCode)
obj.put("precision", precision)
Firebase.analytics.logEvent("ad_price", Bundle().apply {
putDouble("valueMicros", valueMicros / 1000000.0)
})
val params = Bundle()
val currentImpressionRevenue = adValue.valueMicros.toDouble() / 1000000.0
params.putDouble(FirebaseAnalytics.Param.VALUE, currentImpressionRevenue)
params.putString(FirebaseAnalytics.Param.CURRENCY, "USD")
LogEx.logDebug("EventOnPaidEventListener", "precisionType=${adValue.precisionType}")
val precisionType = when (adValue.precisionType) {
0 -> "UNKNOWN"
1 -> "ESTIMATED"
2 -> "PUBLISHER_PROVIDED"
3 -> "PRECISE"
else -> "Invalid"
}
params.putString("precisionType", precisionType)
Firebase.analytics.logEvent("Ad_Impression_Revenue", params)
val previousTaichiTroasCache = taichiPref.getFloat("TaichiTroasCache", 0f)
val currentTaichiTroasCache = (previousTaichiTroasCache +
currentImpressionRevenue).toFloat()
if (currentTaichiTroasCache >= 0.01) {//如果超过0.01就触发一次tROAS taichi事件
val roasbundle = Bundle()
roasbundle.putDouble(
FirebaseAnalytics.Param.VALUE,
currentTaichiTroasCache.toDouble()
)
roasbundle.putString(FirebaseAnalytics.Param.CURRENCY, "USD")
Firebase.analytics.logEvent("Total_Ads_Revenue_001", roasbundle)
taichiSharedPreferencesEditor.putFloat("TaichiTroasCache", 0f)//重新清零,开始计算
val logger = AppEventsLogger.newLogger(MyApplication.appContext)
val parameters = Bundle()
parameters.putString(AppEventsConstants.EVENT_PARAM_CURRENCY, "USD")
logger.logEvent("ad_value", currentTaichiTroasCache.toDouble(), parameters)
} else {
taichiSharedPreferencesEditor.putFloat("TaichiTroasCache", currentTaichiTroasCache)
}
taichiSharedPreferencesEditor.commit()
var key = "ad_price"
when (ad) {
is AppOpenAd -> {
val adUnitId = ad.adUnitId
val loadedAdapterResponseInfo = ad.responseInfo.loadedAdapterResponseInfo
val adSourceName = loadedAdapterResponseInfo?.adSourceName
val adSourceId = loadedAdapterResponseInfo?.adSourceId
val adSourceInstanceName = loadedAdapterResponseInfo?.adSourceInstanceName
val adSourceInstanceId = loadedAdapterResponseInfo?.adSourceInstanceId
val sessionId = ad.responseInfo.responseId
val extras = ad.responseInfo.responseExtras
val mediationGroupName = extras.getString("mediation_group_name")
val mediationABTestName = extras.getString("mediation_ab_test_name")
val mediationABTestVariant = extras.getString("mediation_ab_test_variant")
obj.put("ad_unit", "openAd")
obj.put("adUnitId", adUnitId)
obj.put("adSourceName", adSourceName)
obj.put("adSourceId", adSourceId)
obj.put("adSourceInstanceName", adSourceInstanceName)
obj.put("adSourceInstanceId", adSourceInstanceId)
obj.put("mediationGroupName", mediationGroupName)
obj.put("mediationABTestName", mediationABTestName)
obj.put("mediationABTestVariant", mediationABTestVariant)
obj.put("session_id", sessionId)
}
is InterstitialAd -> {
val adUnitId = ad.adUnitId
val loadedAdapterResponseInfo = ad.responseInfo.loadedAdapterResponseInfo
val adSourceName = loadedAdapterResponseInfo?.adSourceName
val adSourceId = loadedAdapterResponseInfo?.adSourceId
val adSourceInstanceName = loadedAdapterResponseInfo?.adSourceInstanceName
val adSourceInstanceId = loadedAdapterResponseInfo?.adSourceInstanceId
val sessionId = ad.responseInfo.responseId
val extras = ad.responseInfo.responseExtras
val mediationGroupName = extras.getString("mediation_group_name")
val mediationABTestName = extras.getString("mediation_ab_test_name")
val mediationABTestVariant = extras.getString("mediation_ab_test_variant")
obj.put("ad_unit", "interAd")
obj.put("adUnitId", adUnitId)
obj.put("adSourceName", adSourceName)
obj.put("adSourceId", adSourceId)
obj.put("adSourceInstanceName", adSourceInstanceName)
obj.put("adSourceInstanceId", adSourceInstanceId)
obj.put("mediationGroupName", mediationGroupName)
obj.put("mediationABTestName", mediationABTestName)
obj.put("mediationABTestVariant", mediationABTestVariant)
obj.put("session_id", sessionId)
}
is RewardedAd -> {
val loadedAdapterResponseInfo = ad.responseInfo.loadedAdapterResponseInfo
val adSourceName = loadedAdapterResponseInfo?.adSourceName
val adSourceId = loadedAdapterResponseInfo?.adSourceId
val adSourceInstanceName = loadedAdapterResponseInfo?.adSourceInstanceName
val adSourceInstanceId = loadedAdapterResponseInfo?.adSourceInstanceId
val sessionId = ad.responseInfo.responseId
val extras = ad.responseInfo.responseExtras
val mediationGroupName = extras.getString("mediation_group_name")
val mediationABTestName = extras.getString("mediation_ab_test_name")
val mediationABTestVariant = extras.getString("mediation_ab_test_variant")
obj.put("adSourceName", adSourceName)
obj.put("adSourceId", adSourceId)
obj.put("adSourceInstanceName", adSourceInstanceName)
obj.put("adSourceInstanceId", adSourceInstanceId)
obj.put("mediationGroupName", mediationGroupName)
obj.put("mediationABTestName", mediationABTestName)
obj.put("mediationABTestVariant", mediationABTestVariant)
obj.put("session_id", sessionId)
}
is NativeAd -> {
key = "ad_price"
val loadedAdapterResponseInfo = ad.responseInfo?.loadedAdapterResponseInfo
val adSourceName = loadedAdapterResponseInfo?.adSourceName
val adSourceId = loadedAdapterResponseInfo?.adSourceId
val adSourceInstanceName = loadedAdapterResponseInfo?.adSourceInstanceName
val adSourceInstanceId = loadedAdapterResponseInfo?.adSourceInstanceId
val sessionId = ad.responseInfo?.responseId
val extras = ad.responseInfo?.responseExtras
val mediationGroupName = extras?.getString("mediation_group_name")
val mediationABTestName = extras?.getString("mediation_ab_test_name")
val mediationABTestVariant = extras?.getString("mediation_ab_test_variant")
obj.put("adUnitId", "nativeAd")
obj.put("adSourceName", adSourceName)
obj.put("adSourceId", adSourceId)
obj.put("adSourceInstanceName", adSourceInstanceName)
obj.put("adSourceInstanceId", adSourceInstanceId)
obj.put("mediationGroupName", mediationGroupName)
obj.put("mediationABTestName", mediationABTestName)
obj.put("mediationABTestVariant", mediationABTestVariant)
obj.put("session_id", sessionId)
}
else -> {
runCatching {
val adView = ad as AdView
val adUnitId = adView.adUnitId
val loadedAdapterResponseInfo = adView.responseInfo?.loadedAdapterResponseInfo
val adSourceName = loadedAdapterResponseInfo?.adSourceName
val adSourceId = loadedAdapterResponseInfo?.adSourceId
val adSourceInstanceName = loadedAdapterResponseInfo?.adSourceInstanceName
val adSourceInstanceId = loadedAdapterResponseInfo?.adSourceInstanceId
val sessionId = adView.responseInfo?.responseId
val extras = adView.responseInfo?.responseExtras
val mediationGroupName = extras?.getString("mediation_group_name")
val mediationABTestName = extras?.getString("mediation_ab_test_name")
val mediationABTestVariant = extras?.getString("mediation_ab_test_variant")
obj.put("ad_unit", "banner")
obj.put("adUnitId", adUnitId)
obj.put("adSourceName", adSourceName)
obj.put("adSourceId", adSourceId)
obj.put("adSourceInstanceName", adSourceInstanceName)
obj.put("adSourceInstanceId", adSourceInstanceId)
obj.put("mediationGroupName", mediationGroupName)
obj.put("mediationABTestName", mediationABTestName)
obj.put("mediationABTestVariant", mediationABTestVariant)
obj.put("session_id", sessionId)
}
}
}
EventUtils.event(key, ext = obj)
}
}
fun clickAd(responseInfo: ResponseInfo?, adUnit: String) {
val response = responseInfo?.adapterResponses?.getOrNull(0)
val obj = JSONObject()
obj.put("source", response?.adSourceName)
obj.put("ad_unit", adUnit)
val credentials = mapOf(
"placementid" to response?.credentials?.get("placementid"),
"appid" to response?.credentials?.get("appid"),
"pubid" to response?.credentials?.get("pubid")
)
obj.put("credentials", credentials.toString())
obj.put("session_id", responseInfo?.responseId)
obj.put("networkname", responseInfo?.mediationAdapterClassName)
if (adUnit != "nativeAd") {
EventUtils.event("ad_click", ext = obj)
} else {
EventUtils.event("bigimage_ad_click", ext = obj)
}
}
fun showAd(responseInfo: ResponseInfo?, adUnit: String, activity: Activity? = null) {
val response = responseInfo?.adapterResponses?.getOrNull(0)
val obj = JSONObject()
obj.put("source", response?.adSourceName)
obj.put("ad_unit", adUnit)
obj.put("networkname", responseInfo?.mediationAdapterClassName)
val credentials = mapOf(
"placementid" to response?.credentials?.get("placementid"),
"appid" to response?.credentials?.get("appid"),
"pubid" to response?.credentials?.get("pubid")
)
obj.put("credentials", credentials.toString())
obj.put("session_id", responseInfo?.responseId)
obj.put("from", activity?.javaClass?.simpleName)
if (adUnit != "nativeAd") {
EventUtils.event("ad_show", ext = obj)
} else {
EventUtils.event("ad_show", ext = obj)
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.ads.admob package com.base.locationsharewhite.ads.admob
import com.base.locationsharewhite.ads.AdsMgr import com.base.locationsharewhite.ads.AdsMgr
import com.base.locationsharewhite.ads.AdsType
import com.base.locationsharewhite.helper.EventUtils
import com.base.locationsharewhite.utils.AppPreferences import com.base.locationsharewhite.utils.AppPreferences
import com.base.locationsharewhite.utils.KotlinExt.toFormatTime4 import com.base.locationsharewhite.utils.KotlinExt.toFormatTime4
/** /**
* 控制广告计数与判断显示条件 * 控制广告计数与判断显示条件
* *
...@@ -56,12 +59,22 @@ object LimitUtils { ...@@ -56,12 +59,22 @@ object LimitUtils {
.getInt(NUM_CLICK, 0) >= maxCount .getInt(NUM_CLICK, 0) >= maxCount
} }
private fun getAdEventMsg(adsType: AdsType): String {
return when (adsType) {
AdsType.OPEN -> "Open"
AdsType.INSERT -> "Inter"
AdsType.NATIVE -> "Native"
else -> "Banner"
}
}
/** /**
* 是否显示广告 * 是否显示广告
* *
* @return true or false * @return true or false
*/ */
fun isAdShow(): Boolean { fun isAdShow(adsType: AdsType): Boolean {
val currentDate = System.currentTimeMillis().toFormatTime4() val currentDate = System.currentTimeMillis().toFormatTime4()
if (saveDate != currentDate) { if (saveDate != currentDate) {
//如果已经不是今天了,就重置个数 //如果已经不是今天了,就重置个数
...@@ -70,7 +83,31 @@ object LimitUtils { ...@@ -70,7 +83,31 @@ object LimitUtils {
AppPreferences.getInstance().put(NUM_REQUEST, 0) AppPreferences.getInstance().put(NUM_REQUEST, 0)
AppPreferences.getInstance().put(NUM_CLICK, 0) AppPreferences.getInstance().put(NUM_CLICK, 0)
} }
return !(isDisplayLimited || isClickLimited || isRequestLimited) return !(isDisplayLimited.also {
if (it) EventUtils.event(
"ad_limit",
"current${getAdEventMsg(adsType)}Show=${
AppPreferences.getInstance()
.getInt(NUM_DISPLAY, 0)
} ${getAdEventMsg(adsType).lowercase()}_max_show=${AdsMgr.adsConfigBean?.numDisplayLimit}"
)
} || isClickLimited.also {
if (it) EventUtils.event(
"ad_limit",
"current${getAdEventMsg(adsType)}Click=${
AppPreferences.getInstance()
.getInt(NUM_CLICK, 0)
} ${getAdEventMsg(adsType).lowercase()}_max_click=${AdsMgr.adsConfigBean?.numClickLimit}"
)
} || isRequestLimited.also {
if (it) EventUtils.event(
"ad_limit",
"current${getAdEventMsg(adsType)}Request=${
AppPreferences.getInstance()
.getInt(NUM_REQUEST, 0)
} ${getAdEventMsg(adsType).lowercase()}_max_request=${AdsMgr.adsConfigBean?.numRequestLimit}"
)
})
} }
......
package com.example.mydemo.strategy.ads.admob package com.base.locationsharewhite.ads.admob
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
......
package com.base.locationsharewhite.fcm
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import com.base.locationsharewhite.utils.AppPreferences
import com.base.locationsharewhite.utils.KotlinExt.toFormatTime4
/**
*电量监听
*/
class BatteryStatusReceiver(private val configBean: MsgConfigBean) : BroadcastReceiver() {
private var count
get() = AppPreferences.getInstance().getInt("battery_count", 0)
set(value) = AppPreferences.getInstance().putInt("battery_count", value)
private var date
get() = AppPreferences.getInstance().getLong("battery_date", 0)
set(value) = AppPreferences.getInstance().putLong("battery_date", value)
companion object {
fun registerBatteryReceiver(context: Context, configBean: MsgConfigBean) {
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_BATTERY_CHANGED)
}
val applicationContext = context.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
applicationContext.registerReceiver(
BatteryStatusReceiver(configBean),
intentFilter,
Context.RECEIVER_EXPORTED
)
} else {
applicationContext.registerReceiver(BatteryStatusReceiver(configBean), intentFilter)
}
}
}
override fun onReceive(context: Context, intent: Intent?) {
val action = intent?.action
if (action == Intent.ACTION_BATTERY_CHANGED) {
val batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
val batteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
val batteryPercentage = (batteryLevel / batteryScale.toFloat()) * 100
if (batteryPercentage < 21) {
//当电量小于21%就会有推送
val currentMillis = System.currentTimeMillis()
val currentDate = currentMillis.toFormatTime4()
if (currentDate != MsgMgr.saveDate) {
//不是当天就重置
MsgMgr.saveDate = currentDate
count = 0
}
if ((configBean.value2 < 0 || count < configBean.value2) && ((currentMillis - date) / 1000 / 60).toInt() > configBean.value1) {
//推送次数没有达到限制并且展示的最小时间间隔大于配置时间(分钟)
count += 1
date = System.currentTimeMillis()
MsgMgr.sendNotification(context)
}
}
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.fcm;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.messaging.FirebaseMessaging;
public class FCMManager {
public static void initFirebase(Context context) {
FirebaseApp.initializeApp(context);
}
public static void subscribeToTopic(String topic) {
FirebaseMessaging.getInstance().subscribeToTopic(topic)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
Log.d("FCMUtil", "suc:" + topic);
// EventUtils.INSTANCE.event("FCM_Topic_" + topic, null, null, false);
} else {
Log.d("FCMUtil", "fail");
}
}
});
}
public static void unsubscribeFromTopic(String topic) {
FirebaseMessaging.getInstance().unsubscribeFromTopic(topic)
.addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
if (task.isSuccessful()) {
} else {
}
}
});
}
}
package com.base.locationsharewhite.fcm;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import com.base.locationsharewhite.utils.LogEx;
public class FcmReceiver extends BroadcastReceiver {
private String TAG = "FcmReceiver";
@Override
public void onReceive(Context context, Intent intent) {
LogEx.INSTANCE.logDebug(TAG, "onReceive", false);
}
}
package com.base.locationsharewhite.fcm;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
@SuppressLint("MissingFirebaseInstanceTokenRefresh")
public class MessagingService extends FirebaseMessagingService {
private static final String TAG = "MessagingService";
@Override
public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
}
}
\ No newline at end of file
package com.base.locationsharewhite.fcm
import android.util.SparseArray
import androidx.core.util.isNotEmpty
import com.base.locationsharewhite.notification.NotificationBean
/**
* 消息触发配置与消息通知栏配置项目
*
* @property timer 定时器场景触发配置
* @property battery 电量场景触发配置
* @property unlock 解锁场景触发配置
* @property packageChange app安装与卸载触发场景
* @property hover 通知栏悬停配置
* @property remoteViewContainer 自定义通知栏注册容器
*/
data class MsgConfig(
var timer: MsgConfigBean, var battery: MsgConfigBean,
var unlock: MsgConfigBean, var packageChange: MsgConfigBean,
var hover: MsgConfigBean, val remoteViewContainer: SparseArray<NotificationBean>?
) {
class Builder {
private var timer: MsgConfigBean? = null
private var battery: MsgConfigBean? = null
private var unlock: MsgConfigBean? = null
private var packageChange: MsgConfigBean? = null
private var hover: MsgConfigBean? = null
private val remoteViewContainer = SparseArray<NotificationBean>()
fun setConfig(configBean: MsgConfigBean, msgType: MsgType): Builder {
when (msgType) {
MsgType.TIMER -> timer = configBean
MsgType.BATTERY -> battery = configBean
MsgType.UNLOCK -> unlock = configBean
MsgType.PACKAGE -> packageChange = configBean
}
return this
}
/**
* 设置悬停通知配置
*
* @param configBean
* @return
*/
fun setHover(configBean: MsgConfigBean): Builder {
hover = configBean
return this
}
fun addRemoteBean(actionId: Int, bean: NotificationBean): Builder {
remoteViewContainer.put(actionId, bean)
return this
}
fun build(): MsgConfig {
val config = MsgConfig(
//默认情况下,1.timer默认首次3分钟,之后7分钟。
timer ?: MsgConfigBean(3, 7),
//2.解锁场景,默认间隔1分钟,默认无次数限制。
battery ?: MsgConfigBean(60),
//3.电量场景,默认间隔1小时,默认无次数限制。
unlock ?: MsgConfigBean(1),
//4.安装与卸载场景,默认间隔5分钟,默认无次数限制。
packageChange ?: MsgConfigBean(5),
//设置悬停通知配置,默认开启,0代表关闭,1代表开启,持续时间默认为5s
hover ?: MsgConfigBean(1, 5),
if (remoteViewContainer.isNotEmpty()) remoteViewContainer else null
)
return config
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.fcm
/**
* 消息推送显示策略
*
* @param value1 第一个控制参数项目(一般是代表时间间隔限制,如果是悬停通知配置,这个0代表关闭,1代表开启)
* @param value2 第二个控制参数项目(一般是配置的次数限制,-1代表无限制,0代表彻底关闭,如果悬停配置,这个代表悬停时间)
*/
data class MsgConfigBean(val value1: Int, val value2: Int = -1)
\ No newline at end of file
package com.base.locationsharewhite.fcm
import android.app.NotificationManager
import android.content.Context
import android.util.SparseArray
import androidx.core.util.isEmpty
import androidx.core.util.size
import com.base.locationsharewhite.notification.NotificationBean
import com.base.locationsharewhite.notification.NotificationHoverUtils
import com.base.locationsharewhite.notification.NotificationMgr
import com.base.locationsharewhite.service.StayNotificationService.Companion.startStayNotification
import com.base.locationsharewhite.utils.AppPreferences
import com.base.locationsharewhite.utils.KotlinExt.toFormatMinute
import com.base.locationsharewhite.utils.KotlinExt.toFormatTime4
/**
* 推送消息通知栏相关管理类
*/
object MsgMgr {
const val SAVE_DATE = "SAVE_DATE"
/**
* 保存的时间,用来判断是否是当天,不是当天要重置计数次数,这里的和admob的广告当天记录时间用的一样的哦
*/
var saveDate
get() = AppPreferences.getInstance()
.getString(SAVE_DATE, System.currentTimeMillis().toFormatTime4())
set(value) = AppPreferences.getInstance().put(SAVE_DATE, value)
/**
* FCM主题订阅 Topic 包名+首次启动的当前的分钟
*/
private var topic
get() = AppPreferences.getInstance().getString("topic", "")
set(value) = AppPreferences.getInstance().put("topic", value)
var hoverActionId = -1
private set
private var actionKeyIndex = 0
private lateinit var hoverConfig: MsgConfigBean
var remoteViewContainer: SparseArray<NotificationBean>? = null
fun init(context: Context, packageName: String, msgConfig: MsgConfig) {
hoverConfig = msgConfig.hover
remoteViewContainer = msgConfig.remoteViewContainer
//初始化SDK
FCMManager.initFirebase(context)
//注册fcm主题订阅
var topic = topic
if (topic.isEmpty()) {
val topicNumber = System.currentTimeMillis().toFormatMinute()
topic = packageName + "_$topicNumber"
MsgMgr.topic = topic
}
FCMManager.subscribeToTopic(topic)
//判断是否配置自定义通知栏事件
if (remoteViewContainer?.isEmpty() != false) return
//注册几个状态广播监听
ScreenStatusReceiver.registerScreenReceiver(context, msgConfig.unlock)
PackageStatusReceiver.registerBatteryReceiver(context, msgConfig.packageChange)
BatteryStatusReceiver.registerBatteryReceiver(context, msgConfig.battery)
//根据配置设置定时推送
if (!TimerManager.isTaskTimerActive) {
TimerManager
.scheduleTask(
(msgConfig.timer.value1 * 60 * 1000).toLong(),
(msgConfig.timer.value2 * 60 * 1000).toLong()
)
}
}
/**
* 发送通知
*
* @param context
* @param actionId 通知栏的事件id,这个是自己定的,每个应用可能不一样
* @param hoverOpen 悬停通知是否关闭,0=关闭,1=开启
*/
fun sendNotification(
context: Context,
actionId: Int = getNextActionId(),
hoverOpen: Int = if (this::hoverConfig.isInitialized) hoverConfig.value1 else 0
) {
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (!notificationManager.areNotificationsEnabled()) return
//先判断是否有自定义通知栏装入
if (remoteViewContainer?.isEmpty() != false) return
val bean = remoteViewContainer?.get(actionId) ?: return
NotificationMgr.sendNotification(
context,
bean.intent,
bean.bigRemoteViews,
bean.smallRemoteViews
)
if (hoverOpen == 1) {
if (hoverActionId != -1) {
//如果当前悬浮有事件,就重置整个悬浮逻辑
stopHoverNotification()
}
//记录当前悬停的通知事件id
hoverActionId = actionId
//悬停通知开启了
//计算次数,因为系统默认是5s,这里计算多久再次发送通知
val count = hoverConfig.value2 / 5
//如果悬浮通知配置小于了系统的默认值那就没必要在发送延迟了
if (count <= 1) return
//发送延迟通知
NotificationHoverUtils.sendHoverNotification(context, count - 1, 5 - 1)
}
}
/**
* 开启常驻通知栏
*/
fun startPermanentNotification(context: Context) {
context.startStayNotification()
}
/**
* 停止悬停通知的延迟队列
*/
fun stopHoverNotification() {
hoverActionId = -1
NotificationHoverUtils.stopNotificationHandler()
}
/**
*循环输出通知栏不同的事件
* @return 当前循环输出的actionId
*/
private fun getNextActionId(): Int {
val size = remoteViewContainer?.size ?: 0
val actionId = remoteViewContainer?.keyAt(actionKeyIndex) ?: 0
if (size > actionKeyIndex + 1) {
actionKeyIndex++
} else actionKeyIndex = 0
return actionId
}
}
\ No newline at end of file
package com.base.locationsharewhite.fcm
/**
* 推送类型
* 0=定时、1=fcm、2=解锁、3=电量、4=安装与卸载
*/
@JvmInline
value class MsgType private constructor(val value: Int) {
companion object {
val TIMER = MsgType(0)
val FCM = MsgType(1)
val UNLOCK = MsgType(2)
val BATTERY = MsgType(3)
val PACKAGE = MsgType(4)
fun from(adsType: Int): MsgType {
return when (adsType) {
TIMER.value -> TIMER
FCM.value -> FCM
UNLOCK.value -> UNLOCK
BATTERY.value -> BATTERY
else -> PACKAGE
}
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.fcm
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import com.base.locationsharewhite.utils.AppPreferences
import com.base.locationsharewhite.utils.KotlinExt.toFormatTime4
class PackageStatusReceiver(private val configBean: MsgConfigBean) : BroadcastReceiver() {
private var count
get() = AppPreferences.getInstance().getInt("package_count", 0)
set(value) = AppPreferences.getInstance().putInt("package_count", value)
private var date
get() = AppPreferences.getInstance().getLong("package_date", 0)
set(value) = AppPreferences.getInstance().putLong("package_date", value)
companion object {
fun registerBatteryReceiver(context: Context, configBean: MsgConfigBean) {
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED)
addDataScheme("package")
}
val applicationContext = context.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
applicationContext.registerReceiver(
PackageStatusReceiver(configBean),
intentFilter,
Context.RECEIVER_EXPORTED
)
} else {
applicationContext.registerReceiver(PackageStatusReceiver(configBean), intentFilter)
}
}
}
override fun onReceive(context: Context, intent: Intent?) {
val action = intent?.action
if (action == Intent.ACTION_PACKAGE_ADDED || action == Intent.ACTION_PACKAGE_REMOVED) {
val currentMillis = System.currentTimeMillis()
val currentDate = currentMillis.toFormatTime4()
if (currentDate != MsgMgr.saveDate) {
//不是当天就重置
MsgMgr.saveDate = currentDate
count = 0
}
if ((configBean.value2 < 0 || count < configBean.value2) && ((currentMillis - date) / 1000 / 60).toInt() > configBean.value1) {
//推送次数没有达到限制并且展示的最小时间间隔大于配置时间(分钟)
count += 1
date = System.currentTimeMillis()
MsgMgr.sendNotification(context)
}
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.fcm
/**
* 云控返回的json数据格式
*
* @property battery
* @property hover
* @property pkChange
* @property timer
* @property unlock
*/
data class RemoteConfigBean(
val battery: MsgConfigBean?,
val hover: MsgConfigBean?,
val pkChange: MsgConfigBean?,
val timer: MsgConfigBean?,
val unlock: MsgConfigBean?
)
\ No newline at end of file
package com.base.locationsharewhite.fcm
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import com.base.locationsharewhite.utils.AppPreferences
import com.base.locationsharewhite.utils.KotlinExt.toFormatTime4
class ScreenStatusReceiver(private val configBean: MsgConfigBean) : BroadcastReceiver() {
private var count
get() = AppPreferences.getInstance().getInt("screen_count", 0)
set(value) = AppPreferences.getInstance().putInt("screen_count", value)
private var date
get() = AppPreferences.getInstance().getLong("screen_date", 0)
set(value) = AppPreferences.getInstance().putLong("screen_date", value)
companion object {
var isDeviceInteractive = true
private set
var isSecureLockActive = false
private set
fun registerScreenReceiver(context: Context, configBean: MsgConfigBean) {
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_SCREEN_OFF)
addAction(Intent.ACTION_SCREEN_ON)
addAction(Intent.ACTION_USER_PRESENT)
}
val applicationContext = context.applicationContext
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
applicationContext.registerReceiver(
ScreenStatusReceiver(configBean),
intentFilter,
Context.RECEIVER_EXPORTED
)
} else {
applicationContext.registerReceiver(ScreenStatusReceiver(configBean), intentFilter)
}
}
}
override fun onReceive(context: Context, intent: Intent?) {
val action = intent?.action
when (action) {
Intent.ACTION_SCREEN_ON -> {
isDeviceInteractive = true
}
Intent.ACTION_SCREEN_OFF -> {
isDeviceInteractive = false
isSecureLockActive = true
}
Intent.ACTION_USER_PRESENT -> {
isSecureLockActive = false
if (isDeviceInteractive) {
val currentMillis = System.currentTimeMillis()
val currentDate = currentMillis.toFormatTime4()
if (currentDate != MsgMgr.saveDate) {
//不是当天就重置
MsgMgr.saveDate = currentDate
count = 0
}
if ((configBean.value2 < 0 || count < configBean.value2) && ((currentMillis - date) / 1000 / 60).toInt() > configBean.value1) {
//推送次数没有达到限制并且展示的最小时间间隔大于配置时间(分钟)
count += 1
date = System.currentTimeMillis()
MsgMgr.sendNotification(context)
}
}
}
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.fcm
import com.base.locationsharewhite.helper.GoogleSdkMgr
import java.util.Timer
import java.util.TimerTask
object TimerManager {
private var taskTimer: Timer? = null
var isTaskTimerActive: Boolean = false
private set
fun scheduleTask(delay: Long, period: Long) {
synchronized(TimerManager::class.java) {
ensureTimerIsStopped() // 确保定时器未运行
taskTimer = Timer() // 创建新的 Timer 实例
val task: TimerTask = object : TimerTask() {
override fun run() {
//Log.d("glc", "Scheduled task is running")
if (!ScreenStatusReceiver.isSecureLockActive && ScreenStatusReceiver.isDeviceInteractive) {
// 确保设备处于交互状态,未锁定,且应用未暂停
MsgMgr.sendNotification(GoogleSdkMgr.context)
}
}
}
taskTimer?.schedule(task, delay, period) // 调度任务
isTaskTimerActive = true // 设置定时器状态为活跃
}
}
private fun ensureTimerIsStopped() {
if (isTaskTimerActive) {
if (taskTimer != null) {
taskTimer?.cancel()
taskTimer?.purge() // 清除所有取消的任务
}
isTaskTimerActive = false // 重置定时器状态
}
}
fun stopTaskTimer() {
synchronized(TimerManager::class.java) {
ensureTimerIsStopped() // 停止定时器
}
}
}
\ No newline at end of file
package com.base.locationsharewhite.helper
import android.annotation.SuppressLint
import android.content.Context
import com.base.locationsharewhite.ads.AdsConfigBean
import com.base.locationsharewhite.ads.AdsMgr
import com.base.locationsharewhite.fcm.MsgConfig
import com.base.locationsharewhite.fcm.MsgConfigBean
import com.base.locationsharewhite.fcm.MsgMgr
import com.base.locationsharewhite.fcm.MsgType
import com.base.locationsharewhite.fcm.RemoteConfigBean
import com.base.locationsharewhite.helper.config.ConstConfig
import com.base.locationsharewhite.notification.NotificationMgr
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import com.google.gson.Gson
/**
* admob广告与fireBase与FCM的初始化等管理类
*/
@SuppressLint("StaticFieldLeak")
object GoogleSdkMgr {
lateinit var context: Context
fun init(
context: Context, packageName: String,
msgConfig: MsgConfig = MsgConfig.Builder()
//这里只是展示默认值并没有配置自定义通知栏视图即addRemoteBean方法,默认情况下,1.timer默认首次3分钟,之后7分钟。
.setConfig(MsgConfigBean(3, 7), MsgType.TIMER)
//2.解锁场景,默认间隔1分钟,默认无次数限制。
.setConfig(MsgConfigBean(1), MsgType.UNLOCK)
//3.电量场景,默认间隔1小时,默认无次数限制。
.setConfig(MsgConfigBean(60), MsgType.BATTERY)
//4.安装与卸载场景,默认间隔5分钟,默认无次数限制。
.setConfig(MsgConfigBean(5), MsgType.PACKAGE)
//设置悬停通知配置,默认开启,0代表关闭,1代表开启,持续时间默认为5s
.setHover(MsgConfigBean(1, 5))
.build(),
//默认情况下,广告展示,点击和请求都没有次数限制,间隔时间为1分钟。
adsConfigBean: AdsConfigBean = AdsConfigBean()
) {
GoogleSdkMgr.context = context.applicationContext
//初始化注册通知栏通知渠道等
NotificationMgr.init(context)
//先注册自定义通知栏,这个不需要等云控数据回调
MsgMgr.remoteViewContainer = msgConfig.remoteViewContainer
val remoteConfig = FirebaseRemoteConfig.getInstance()
remoteConfig.fetchAndActivate().addOnCompleteListener { task ->
if (task.isSuccessful) {
//解析云控消息配置
val config = remoteConfig.getString(ConstConfig.REMOTE_MSG)
//Log.d(this::class.java.simpleName, config)
val bean: RemoteConfigBean? = Gson().fromJson(config, RemoteConfigBean::class.java)
// Log.d(this::class.java.simpleName, bean.toString())
bean?.apply {
hover?.let {
msgConfig.hover = it
}
unlock?.let {
msgConfig.unlock = it
}
battery?.let {
msgConfig.battery = it
}
pkChange?.let {
msgConfig.packageChange = it
}
timer?.let {
msgConfig.timer = it
}
}
//解析云控广告配置
val adConfig = remoteConfig.getString(ConstConfig.REMOTE_AD)
//Log.d(this::class.java.simpleName, adConfig)
val adBean: AdsConfigBean? = Gson().fromJson(adConfig, AdsConfigBean::class.java)
//Log.d(this::class.java.simpleName, adBean.toString())
adBean?.apply {
adsConfigBean.isInBlackList = isInBlackList
adsConfigBean.numDisplayLimit = numDisplayLimit
adsConfigBean.numRequestLimit = numRequestLimit
adsConfigBean.numClickLimit = numClickLimit
adsConfigBean.timeInterval = timeInterval
}
}
//Log.d(this::class.java.simpleName, msgConfig.toString())
//Log.d(this::class.java.simpleName, adsConfigBean.toString())
//初始化通知栏推送相关业务
MsgMgr.init(context, packageName, msgConfig)
//初始化广告相关业务
AdsMgr.init(context, adsConfigBean)
}
}
}
\ No newline at end of file
...@@ -16,7 +16,7 @@ object InstallHelps { ...@@ -16,7 +16,7 @@ object InstallHelps {
fun init() { fun init() {
val referrerClient = InstallReferrerClient.newBuilder(MyApplication.context).build() val referrerClient = InstallReferrerClient.newBuilder(MyApplication.appContext).build()
referrerClient.startConnection(object : InstallReferrerStateListener { referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) { override fun onInstallReferrerSetupFinished(responseCode: Int) {
try { try {
......
...@@ -5,13 +5,10 @@ import android.app.Application ...@@ -5,13 +5,10 @@ import android.app.Application
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.widget.RemoteViews import com.base.locationsharewhite.ads.AdsConfigBean
import com.base.locationsharewhite.R import com.base.locationsharewhite.ads.AdsMgr
import com.base.locationsharewhite.bean.ConstObject.topic_number import com.base.locationsharewhite.bean.ConstObject.topic_number
import com.base.locationsharewhite.fcm.MsgConfig
import com.base.locationsharewhite.helper.config.AppConfig import com.base.locationsharewhite.helper.config.AppConfig
import com.base.locationsharewhite.helper.config.ConstConfig
import com.base.locationsharewhite.notification.NotificationBean
import com.base.locationsharewhite.ui.splash.SplashActivity import com.base.locationsharewhite.ui.splash.SplashActivity
import com.base.locationsharewhite.utils.AppPreferences import com.base.locationsharewhite.utils.AppPreferences
import com.base.locationsharewhite.utils.KotlinExt.toFormatMinute import com.base.locationsharewhite.utils.KotlinExt.toFormatMinute
...@@ -25,8 +22,8 @@ class MyApplication : Application() { ...@@ -25,8 +22,8 @@ class MyApplication : Application() {
var uuid = "" var uuid = ""
companion object { companion object {
lateinit var context: MyApplication
lateinit var appContext: MyApplication
var splashLanguage: String = Locale.getDefault().language + "_" + Locale.getDefault().country var splashLanguage: String = Locale.getDefault().language + "_" + Locale.getDefault().country
var mainLanguage: String = Locale.getDefault().language + "_" + Locale.getDefault().country var mainLanguage: String = Locale.getDefault().language + "_" + Locale.getDefault().country
...@@ -45,7 +42,7 @@ class MyApplication : Application() { ...@@ -45,7 +42,7 @@ class MyApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
context = this appContext = this
initUUid() initUUid()
initApp() initApp()
} }
...@@ -148,50 +145,10 @@ class MyApplication : Application() { ...@@ -148,50 +145,10 @@ class MyApplication : Application() {
}) })
} }
fun initAdSdk() { private fun initAdSdk() {
GoogleSdkMgr.init( val adsConfigBean = AdsConfigBean.getSpConfigBean()
this, this.packageName, MsgConfig.Builder() //初始化广告相关业务
//设置自己的自定义通知栏 AdsMgr.init(appContext, adsConfigBean)
.addRemoteBean(
//自定义的通知事件id,可以自己添加
ConstConfig.ACTION_SHARE_LOCATION, NotificationBean(
Intent(this, SplashActivity::class.java).apply {
//悬停点击标记,用来进入当相关页面进行悬停关闭
putExtra("hover", true)
putExtra("actionId", ConstConfig.ACTION_SHARE_LOCATION)
},
RemoteViews(packageName, R.layout.notification_share_location).apply {
},
RemoteViews(packageName, R.layout.notification_share_location).apply {
}
)
)
.addRemoteBean(
ConstConfig.ACTION_ENABLE_LOCATION, NotificationBean(
Intent(this, SplashActivity::class.java).apply {
putExtra("hover", true)
putExtra("actionId", ConstConfig.ACTION_ENABLE_LOCATION)
},
RemoteViews(packageName, R.layout.notification_enable_location).apply {
},
RemoteViews(packageName, R.layout.notification_enable_location).apply {
}
)
)
.addRemoteBean(
ConstConfig.ACTION_COPY_CODE, NotificationBean(
Intent(this, SplashActivity::class.java).apply {
putExtra("hover", true)
putExtra("actionId", ConstConfig.ACTION_COPY_CODE)
},
RemoteViews(packageName, R.layout.notification_copy_code).apply {
},
RemoteViews(packageName, R.layout.notification_copy_code).apply {
}
)
)
.build()
)
} }
} }
\ No newline at end of file
...@@ -18,7 +18,6 @@ import androidx.core.graphics.drawable.IconCompat ...@@ -18,7 +18,6 @@ import androidx.core.graphics.drawable.IconCompat
import com.base.locationsharewhite.R import com.base.locationsharewhite.R
import com.base.locationsharewhite.bean.ConstObject import com.base.locationsharewhite.bean.ConstObject
import com.base.locationsharewhite.helper.config.ConstConfig import com.base.locationsharewhite.helper.config.ConstConfig
import com.base.locationsharewhite.helper.GoogleSdkMgr
import com.base.locationsharewhite.ui.main.MainActivity import com.base.locationsharewhite.ui.main.MainActivity
import com.base.locationsharewhite.ui.splash.SplashActivity import com.base.locationsharewhite.ui.splash.SplashActivity
import kotlin.random.Random import kotlin.random.Random
......
...@@ -22,7 +22,7 @@ public class AppPreferences { ...@@ -22,7 +22,7 @@ public class AppPreferences {
public static synchronized AppPreferences getInstance() { public static synchronized AppPreferences getInstance() {
if (sInstance == null) { if (sInstance == null) {
sInstance = new AppPreferences(MyApplication.context.getApplicationContext()); sInstance = new AppPreferences(MyApplication.appContext.getApplicationContext());
} }
return sInstance; return sInstance;
} }
......
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