diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9efde7e..85dfe1d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -84,6 +84,7 @@ android { } dependencies { + implementation(files("libs/UpLoadLibrary_11_24_18_30-release.aar")) implementation(libs.androidx.fragment.ktx) implementation(libs.androidx.appcompat) implementation(libs.androidx.core.ktx) @@ -112,4 +113,11 @@ dependencies { implementation(platform("com.google.firebase:firebase-bom:34.6.0")) implementation("com.google.firebase:firebase-crashlytics-ndk") implementation("com.google.firebase:firebase-analytics") + // google ads + implementation("com.google.android.gms:play-services-ads:24.7.0") + implementation ("com.google.android.gms:play-services-ads-identifier:18.0.1") + // okhttp + implementation ("com.squareup.okhttp3:okhttp:4.12.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") + } \ No newline at end of file diff --git a/app/libs/UpLoadLibrary_11_24_18_30-release.aar b/app/libs/UpLoadLibrary_11_24_18_30-release.aar new file mode 100644 index 0000000..01829d2 Binary files /dev/null and b/app/libs/UpLoadLibrary_11_24_18_30-release.aar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 49d074d..c7d19bc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -35,10 +35,14 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:largeHeap="true" + android:networkSecurityConfig="@xml/net" android:requestLegacyExternalStorage="true" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.PDFReaderPro"> + diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/PRApp.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/PRApp.kt index 182ba34..36c7d0e 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/PRApp.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/PRApp.kt @@ -6,7 +6,10 @@ import androidx.annotation.StringRes import com.all.pdfreader.pdf.reader.room.repository.PdfRepository import com.all.pdfreader.pdf.reader.util.AnalyticsUtils import com.all.pdfreader.pdf.reader.util.FileChangeObserver +import com.google.android.gms.ads.MobileAds +import com.google.android.gms.ads.RequestConfiguration import com.tom_roush.pdfbox.android.PDFBoxResourceLoader +import com.up.uploadlibrary.UpLoadManager class PRApp : Application() { @@ -39,10 +42,20 @@ class PRApp : Application() { PdfRepository.initialize(this) // 初始化pdfbox PDFBoxResourceLoader.init(this) + // 上传 + UpLoadManager.init(context = this, tag = "PRApp_upload_task") { _, _ -> } + // 广告初始化 + MobileAds.initialize(this) + } + + private fun initMobileAds(){ + val testDeviceIds = listOf("TEST_DEVICE_ID") + val configuration = RequestConfiguration.Builder().setTestDeviceIds(testDeviceIds).build() + MobileAds.setRequestConfiguration(configuration) } // 在权限授权后调用 fun startFileChangeObserving() { - fileChangeObserver.startObserving() +// fileChangeObserver.startObserving() } } diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstLoad.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstLoad.kt new file mode 100644 index 0000000..e06bb73 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstLoad.kt @@ -0,0 +1,68 @@ +package com.all.pdfreader.pdf.reader.ad + +import android.app.Activity +import android.util.Log +import com.all.pdfreader.pdf.reader.util.AnalyticsUtils +import com.google.android.gms.ads.AdRequest +import com.google.android.gms.ads.LoadAdError +import com.google.android.gms.ads.interstitial.InterstitialAd +import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback + +class AdInstLoad( + private val activity: Activity, + private val placement: AdsInsUtil.AdPlacement, + private val adLoadListener: LoadListener? +) { + + init { + loadAd() + } + + private fun loadAd() { + //多处调用load,也不会重复、不影响缓存广告、展示安全 + val cachedAd = InstAdCacheManager.instance.getAdCache(placement) + if (cachedAd != null) { + Log.d("ocean","广告存在缓存,跳过加载,返回成功") + //缓存广告有效,跳过加载,返回成功 + adLoadListener?.loaded(cachedAd) + return + } + + val adUnitId = AdsInsUtil.adUnitIdMap[placement] ?: run { + val errorMsg = "No AdUnitId for $placement" + Log.d("ocean","没找到对应的广告ID->$placement") + adLoadListener?.loadFailed(errorMsg) + AnalyticsUtils.logAdEvent( + placement, + AnalyticsUtils.AdEvent.LOAD_FAIL, + null, + errorMsg + ) + return + } + AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.REQ) + + InterstitialAd.load( + activity, + adUnitId, + AdRequest.Builder().build(), + object : InterstitialAdLoadCallback() { + override fun onAdLoaded(ad: InterstitialAd) { + InstAdCacheManager.instance.setAdCache(placement, ad) + AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.LOADED) + adLoadListener?.loaded(ad) + } + + override fun onAdFailedToLoad(adError: LoadAdError) { + AnalyticsUtils.logAdEvent( + placement, + AnalyticsUtils.AdEvent.LOAD_FAIL, + adError.code, + adError.message + ) + adLoadListener?.loadFailed(adError.toString()) + } + }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstShower.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstShower.kt new file mode 100644 index 0000000..7262911 --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdInstShower.kt @@ -0,0 +1,68 @@ +package com.all.pdfreader.pdf.reader.ad + +import android.app.Activity +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil.AdPlacement +import com.all.pdfreader.pdf.reader.util.AnalyticsUtils +import com.google.android.gms.ads.AdError +import com.google.android.gms.ads.FullScreenContentCallback + +class AdInstShower( + private val activity: Activity, + private val placement: AdPlacement, + private val showListener: ShowListener? +) { + + init { + showAd() + } + + private fun showAd() { + val interstitialAd = InstAdCacheManager.instance.getAdCache(placement) + ?: run { + val errorMsg = "InterstitialAd cache is null for place = $placement" + AnalyticsUtils.logAdEvent( + placement, + AnalyticsUtils.AdEvent.SHOW_FAIL, + null, + errorMsg + ) + showListener?.onAdShowFailed(errorMsg) + return + } + + interstitialAd.fullScreenContentCallback = object : FullScreenContentCallback() { + + override fun onAdShowedFullScreenContent() { + AnalyticsUtils.logAdEvent(placement, AnalyticsUtils.AdEvent.SHOW_SUC) + showListener?.onAdShown() + } + + override fun onAdDismissedFullScreenContent() { + // 用户关闭广告 + InstAdCacheManager.instance.remove(placement) + showListener?.onAdClosed() + } + + override fun onAdFailedToShowFullScreenContent(adError: AdError) { + AnalyticsUtils.logAdEvent( + placement, + AnalyticsUtils.AdEvent.SHOW_FAIL, + adError.code, + adError.message + ) + InstAdCacheManager.instance.remove(placement) + showListener?.onAdShowFailed(adError.toString()) + } + + override fun onAdClicked() { + showListener?.onAdClicked() + } + + override fun onAdImpression() { + // 曝光回调 + } + } + + interstitialAd.show(activity) + } +} diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdsInsUtil.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdsInsUtil.kt new file mode 100644 index 0000000..774ad9c --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/AdsInsUtil.kt @@ -0,0 +1,82 @@ +package com.all.pdfreader.pdf.reader.ad + +import android.app.Activity + +object AdsInsUtil { + /** 广告位定义(可扩展) */ + enum class AdPlacement(val tag: String) { + SPL_AND_INTO_HOME(Placement.SPL_AND_INTO_HOME), INT_AND_TOPDF(Placement.INT_AND_TOPDF), INT_AND_PDFTOHOME( + Placement.INT_AND_PDFTOHOME + ), + INT_AND_MERGE(Placement.INT_AND_MERGE), INT_AND_SPLIT(Placement.INT_AND_SPLIT), NATIVE_AND_EXIT( + Placement.NATIVE_AND_EXIT + ), + BAN_AND_HOMEPAGE(Placement.BAN_AND_HOMEPAGE) + } + + object Placement { + /** + * 启动页插页 + */ + const val SPL_AND_INTO_HOME = "spl_and_into_home" + + /** + * 在首页/最近页/喜欢页-点击文件进入PDF内容页过程中,弹出缓存的插屏广告 + */ + const val INT_AND_TOPDF = "int_and_topdf" + + /** + * PDF内容页点击返回到首页/最近页/喜欢页过程中,弹出缓存的插屏广告 + */ + const val INT_AND_PDFTOHOME = "int_and_pdftohome" + + /** + * 首页/最近/喜欢-PDF内容页-更多-合并文件(在点击ok后,出现插屏广告,插屏广告结束后到合并成功页面) + */ + const val INT_AND_MERGE = "int_and_merge" + + /** + * 首页/最近/喜欢-PDF内容页-更多-拆分文件(点击ok后,出现插屏广告,插屏广告结束后到拆分成功页面) + */ + const val INT_AND_SPLIT = "int_and_split" + + /** + * 退出提示对话框原生 + */ + const val NATIVE_AND_EXIT = "native_and_exit" + + /** + * 首页横幅 + */ + const val BAN_AND_HOMEPAGE = "ban_and_homepage" + } + + // 广告位对应的广告ID + val adUnitIdMap: Map = mapOf( + AdPlacement.SPL_AND_INTO_HOME to "ca-app-pub-5717753826607607/5211991318", + AdPlacement.INT_AND_TOPDF to "ca-app-pub-5717753826607607/5308904672", + AdPlacement.INT_AND_PDFTOHOME to "ca-app-pub-5717753826607607/7085128570", + AdPlacement.INT_AND_MERGE to "ca-app-pub-5717753826607607/8928693282", + AdPlacement.INT_AND_SPLIT to "ca-app-pub-5717753826607607/2338415962", + AdPlacement.NATIVE_AND_EXIT to "ca-app-pub-5717753826607607/7276700267", + AdPlacement.BAN_AND_HOMEPAGE to "ca-app-pub-5717753826607607/5939567861" + ) + + fun loadAd( + act: Activity, adPlacement: AdPlacement + ): AdInstLoad { + return AdInstLoad(act, adPlacement, null) + } + + fun loadAd( + act: Activity, adPlacement: AdPlacement, loadListener: LoadListener? + ): AdInstLoad { + return AdInstLoad(act, adPlacement, loadListener) + } + + fun showAd( + act: Activity, adPlacement: AdPlacement, listener: ShowListener? + ): AdInstShower { + return AdInstShower(act, adPlacement, listener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/InstAdCacheManager.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/InstAdCacheManager.kt new file mode 100644 index 0000000..915dcbb --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/InstAdCacheManager.kt @@ -0,0 +1,39 @@ +package com.all.pdfreader.pdf.reader.ad + +import com.google.android.gms.ads.interstitial.InterstitialAd + +class InstAdCacheManager { + private val mAdCacheDict: MutableMap = mutableMapOf() + + companion object { + val instance: InstAdCacheManager by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + InstAdCacheManager() + } + } + + fun setAdCache(place: AdsInsUtil.AdPlacement, adCache: InterstitialAd) { + mAdCacheDict[place] = CachedAd(adCache) + } + + fun getAdCache(place: AdsInsUtil.AdPlacement): InterstitialAd? { + val cached = mAdCacheDict[place] + return if (cached != null && cached.isValid()) cached.ad + else { + mAdCacheDict.remove(place) // 过期广告清理 + null + } + } + + fun remove(place: AdsInsUtil.AdPlacement) { + mAdCacheDict.remove(place) + } + + + data class CachedAd( + val ad: InterstitialAd, + val loadedAt: Long = System.currentTimeMillis() + ) { + // 广告有效期, 1 小时 + fun isValid(): Boolean = System.currentTimeMillis() - loadedAt < 3600_000 + } +} diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/LoadListener.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/LoadListener.kt new file mode 100644 index 0000000..d9dfbea --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/LoadListener.kt @@ -0,0 +1,8 @@ +package com.all.pdfreader.pdf.reader.ad + +import com.google.android.gms.ads.interstitial.InterstitialAd + +interface LoadListener { + fun loadFailed(string: String) {} + fun loaded(ad: InterstitialAd) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/ad/ShowListener.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/ShowListener.kt new file mode 100644 index 0000000..c92084d --- /dev/null +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/ad/ShowListener.kt @@ -0,0 +1,8 @@ +package com.all.pdfreader.pdf.reader.ad + +interface ShowListener { + fun onAdShown() {} + fun onAdShowFailed(string: String) {} + fun onAdClosed() {} + fun onAdClicked() {} +} diff --git a/app/src/main/java/com/all/pdfreader/pdf/reader/util/AnalyticsUtils.kt b/app/src/main/java/com/all/pdfreader/pdf/reader/util/AnalyticsUtils.kt index cafd4fa..3d38039 100644 --- a/app/src/main/java/com/all/pdfreader/pdf/reader/util/AnalyticsUtils.kt +++ b/app/src/main/java/com/all/pdfreader/pdf/reader/util/AnalyticsUtils.kt @@ -2,6 +2,7 @@ package com.all.pdfreader.pdf.reader.util import android.os.Bundle import com.all.pdfreader.pdf.reader.BuildConfig +import com.all.pdfreader.pdf.reader.ad.AdsInsUtil import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.analytics @@ -97,8 +98,35 @@ object AnalyticsUtils { const val KEEP_SCREEN_CLOSE = "keep_screen_close" // 点击关闭(keep_screen_close) } - /** param 常量 */ + /** param 常量(可扩展) */ object Param { - + const val PLACE = "place" + const val ERROR_CODE = "error_code" + const val ERROR_MSG = "error_msg" } + + /** 广告事件类型 */ + enum class AdEvent(val suffix: String) { + REQ("req_header"), + LOADED("loaded_header"), + LOAD_FAIL("load_fail_header"), + SHOW_SUC("show_suc_header"), + SHOW_FAIL("show_fail_header"), + } + + /** 统一广告打点 */ + fun logAdEvent( + placement: AdsInsUtil.AdPlacement, + event: AdEvent, + errorCode: Int? = null, + errorMsg: String? = null + ) { + val eventName = "${placement.tag}_${event.suffix}" + logEvent(eventName) { + put(Param.PLACE, placement.tag) + errorCode?.let { put(Param.ERROR_CODE, it) } + errorMsg?.let { put(Param.ERROR_MSG, it) } + } + } + } diff --git a/app/src/main/res/xml/net.xml b/app/src/main/res/xml/net.xml new file mode 100644 index 0000000..69cc842 --- /dev/null +++ b/app/src/main/res/xml/net.xml @@ -0,0 +1,6 @@ + + + + mobile-server.lux-ad.com + + diff --git a/settings.gradle.kts b/settings.gradle.kts index aa906bb..20120ae 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,5 @@ +import org.gradle.kotlin.dsl.flatDir + pluginManagement { repositories { google { @@ -7,6 +9,7 @@ pluginManagement { includeGroupByRegex("androidx.*") } } + google() mavenCentral() gradlePluginPortal() } @@ -17,6 +20,9 @@ dependencyResolutionManagement { google() mavenCentral() maven("https://jitpack.io") + flatDir { + dirs("libs") + } } }