Browse Source

广告Core库开发,增加Admob广告

liuxiaolong 2 years ago
parent
commit
89c358b952
100 changed files with 4412 additions and 19 deletions
  1. 8 3
      app/build.gradle
  2. 35 6
      app/src/main/AndroidManifest.xml
  3. 51 0
      app/src/main/java/com/convenient/android/lib/LibApplication.kt
  4. 3 1
      app/src/main/java/com/convenient/android/lib/ui/sample/MainActivity.kt
  5. 39 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/AdMainActivity.kt
  6. 64 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdAppOpenActivity.kt
  7. 80 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdBannerActivity.kt
  8. 79 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdFullScreenAdActivity.kt
  9. 100 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdNativeActivity.kt
  10. 33 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdNativeListActivity.kt
  11. 118 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AppWelcomeActivity.kt
  12. 72 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/adapter/NativeAdSampleListAdapter.kt
  13. 82 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/model/Datas.kt
  14. 18 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/model/NativeListBean.kt
  15. 15 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdAppOpenPage.kt
  16. 16 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdBannerPage.kt
  17. 116 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdHomePage.kt
  18. 115 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdInterstitialPage.kt
  19. 89 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdMainPage.kt
  20. 15 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdNativePage.kt
  21. 15 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdRewardVideoPage.kt
  22. 25 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/util/CoilImageLoader.kt
  23. 39 0
      app/src/main/java/com/convenient/android/lib/ui/sample/ad/viewmodel/AdConfigViewModel.kt
  24. 0 5
      app/src/main/java/com/convenient/android/lib/ui/sample/media/MediaFilesPage.kt
  25. 0 2
      app/src/main/java/com/convenient/android/lib/ui/theme/Theme.kt
  26. 30 0
      app/src/main/res/layout/activity_ad_app_open.xml
  27. 56 0
      app/src/main/res/layout/activity_ad_banner.xml
  28. 46 0
      app/src/main/res/layout/activity_ad_interstitial.xml
  29. 139 0
      app/src/main/res/layout/activity_ad_native.xml
  30. 18 0
      app/src/main/res/layout/activity_ad_native_list.xml
  31. 0 1
      app/src/main/res/layout/activity_date.xml
  32. 56 0
      app/src/main/res/layout/view_ad_native.xml
  33. 82 0
      app/src/main/res/layout/view_rv_ad_native_item.xml
  34. 4 0
      app/src/main/res/values/strings.xml
  35. 1 1
      build.gradle
  36. 1 0
      lib_ad_admob/.gitignore
  37. 43 0
      lib_ad_admob/build.gradle
  38. 0 0
      lib_ad_admob/consumer-rules.pro
  39. 21 0
      lib_ad_admob/proguard-rules.pro
  40. 24 0
      lib_ad_admob/src/androidTest/java/com/composition/android/ad/admob/ExampleInstrumentedTest.kt
  41. 6 0
      lib_ad_admob/src/main/AndroidManifest.xml
  42. 25 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/bean/AdmobAdUnitBean.kt
  43. 21 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/impl/AdmobInitialize.kt
  44. 220 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/AdmobAdLoader.kt
  45. 53 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdMobNativeRequestImpl.kt
  46. 57 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobAppOpenRequestImpl.kt
  47. 72 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobBannerRequestImpl.kt
  48. 59 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobInterstitialRequestImpl.kt
  49. 59 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobRewardInterstitialRequestImpl.kt
  50. 49 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobRewardRequestImpl.kt
  51. 34 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/view/AdmobBannerView.kt
  52. 120 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/view/AdmobNativeAdView.kt
  53. 63 0
      lib_ad_admob/src/main/java/com/composition/android/ad/admob/util/AdmobAdExpan.kt
  54. 8 0
      lib_ad_admob/src/main/res/layout/layout_admob_native_ad_view_root.xml
  55. 6 0
      lib_ad_admob/src/main/res/layout/layout_admob_native_media_view.xml
  56. 17 0
      lib_ad_admob/src/test/java/com/composition/android/ad/admob/ExampleUnitTest.kt
  57. 1 0
      lib_ad_core/.gitignore
  58. 51 0
      lib_ad_core/build.gradle
  59. 0 0
      lib_ad_core/consumer-rules.pro
  60. 21 0
      lib_ad_core/proguard-rules.pro
  61. 24 0
      lib_ad_core/src/androidTest/java/com/composition/android/lib/ad/ExampleInstrumentedTest.kt
  62. 5 0
      lib_ad_core/src/main/AndroidManifest.xml
  63. 149 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/AdLoad.kt
  64. 78 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/AdManager.kt
  65. 46 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/AdUnitConfigManager.kt
  66. 39 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/AdLoadCode.kt
  67. 59 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/AdResult.kt
  68. 24 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/AdType.kt
  69. 41 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/Advertisers.kt
  70. 87 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/BasicAdView.kt
  71. 42 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/NativeAdViewHolder.kt
  72. 13 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/NormalImageLoader.kt
  73. 17 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/strategy/IStrategy.kt
  74. 66 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/bean/AdUnitBean.kt
  75. 19 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/bean/AdUnitLoadConfig.kt
  76. 8 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/bean/Alias.kt
  77. 7 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/exception/AdLoadFailException.kt
  78. 27 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/factory/AdLoaderFactory.kt
  79. 14 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/factory/AdRequestFactory.kt
  80. 131 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/impl/Api.kt
  81. 108 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/impl/NormalAdListener.kt
  82. 24 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/impl/NormalStrategy.kt
  83. 64 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/impl/UnknownAdLoader.kt
  84. 61 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/AdListener.kt
  85. 82 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/AdLoader.kt
  86. 17 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/IAdFormatRequest.kt
  87. 16 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/ImageLoader.kt
  88. 15 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/Initialize.kt
  89. 22 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/util/AdLog.kt
  90. 84 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/widget/BannerAdView.kt
  91. 188 0
      lib_ad_core/src/main/java/com/composition/android/lib/ad/widget/NativeAdView.kt
  92. 32 0
      lib_ad_core/src/main/res/values/attr.xml
  93. 22 0
      lib_ad_core/src/test/java/com/composition/android/lib/ad/ExampleUnitTest.kt
  94. 1 0
      lib_ad_csj/.gitignore
  95. 40 0
      lib_ad_csj/build.gradle
  96. 0 0
      lib_ad_csj/consumer-rules.pro
  97. 21 0
      lib_ad_csj/proguard-rules.pro
  98. 24 0
      lib_ad_csj/src/androidTest/java/com/composition/android/ad/csj/ExampleInstrumentedTest.kt
  99. 5 0
      lib_ad_csj/src/main/AndroidManifest.xml
  100. 0 0
      lib_ad_csj/src/test/java/com/composition/android/ad/csj/ExampleUnitTest.kt

+ 8 - 3
app/build.gradle

@@ -4,12 +4,13 @@ plugins {
 }
 
 android {
-    compileSdk 33
+    compileSdk 32
+
 
     defaultConfig {
         applicationId "com.common.android.lib"
         minSdk 21
-        targetSdk 33
+        targetSdk 32
         versionCode 1
         versionName "1.0"
 
@@ -49,10 +50,12 @@ android {
 dependencies {
 
     implementation project(':lib_common')
+    implementation project(':lib_ad_core')
+    implementation project(':lib_ad_admob')
 
     //compose依赖
     implementation "androidx.compose.ui:ui:$compose_version"
-    implementation 'androidx.compose.material3:material3:1.0.0-beta01'
+    implementation 'androidx.compose.material3:material3:1.0.0-alpha01'
     implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
     implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
     implementation 'androidx.activity:activity-compose:1.5.1'
@@ -69,6 +72,8 @@ dependencies {
     implementation "androidx.navigation:navigation-compose:2.5.1"
     implementation "androidx.compose.runtime:runtime-livedata:1.2.0"
     implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
+    implementation 'androidx.appcompat:appcompat:1.5.0'
+    implementation 'com.google.android.material:material:1.6.1'
 
     testImplementation 'junit:junit:4.13.2'
     androidTestImplementation 'androidx.test.ext:junit:1.1.3'

+ 35 - 6
app/src/main/AndroidManifest.xml

@@ -9,29 +9,58 @@
 
     <application
         android:allowBackup="true"
+        android:name=".LibApplication"
         android:dataExtractionRules="@xml/data_extraction_rules"
         android:fullBackupContent="@xml/backup_rules"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
+        android:requestLegacyExternalStorage="true"
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
-        android:requestLegacyExternalStorage="true"
         android:theme="@style/Theme.Lib"
         tools:targetApi="31">
-        <activity
-            android:name=".ui.sample.media.MediaSampleActivity"
-            android:exported="false" />
 
         <activity
-            android:name="com.convenient.android.lib.ui.sample.MainActivity"
-            android:exported="true">
+            android:name=".ui.sample.ad.activity.AppWelcomeActivity"
+            android:exported="true" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
+
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
+        <activity
+            android:name=".ui.sample.ad.activity.AdAppOpenActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.sample.ad.activity.AdFullScreenAdActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.sample.ad.activity.AdNativeListActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.sample.ad.activity.AdNativeActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.sample.ad.activity.AdBannerActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.sample.ad.AdMainActivity"
+            android:exported="false" />
+        <activity
+            android:name=".ui.sample.media.MediaSampleActivity"
+            android:exported="false" />
+        <activity
+            android:name="com.convenient.android.lib.ui.sample.MainActivity"
+            android:exported="false">
+
+        </activity>
         <activity android:name=".DateUtilActivity" />
+
+        <meta-data
+            android:name="com.google.android.gms.ads.APPLICATION_ID"
+            android:value="ca-app-pub-3940256099942544~3347511713"/>
     </application>
 
 </manifest>

+ 51 - 0
app/src/main/java/com/convenient/android/lib/LibApplication.kt

@@ -0,0 +1,51 @@
+package com.convenient.android.lib
+
+import android.app.Application
+import android.content.Intent
+import androidx.lifecycle.*
+import com.composition.android.lib.ad.AdLoad
+import com.convenient.android.common.extension.readyGo
+import com.convenient.android.lib.ui.sample.ad.activity.AppWelcomeActivity
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:
+ */
+class LibApplication : Application(), LifecycleEventObserver {
+
+    override fun onCreate() {
+        super<Application>.onCreate()
+        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
+    }
+
+    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+        when(event){
+            Lifecycle.Event.ON_START->{
+                ProcessLifecycleOwner.get().lifecycleScope.launch {
+                    if (AdLoad.hasCacheAd(Datas.APP_OPEN) && Datas.bgShowAppOpen) {
+                        readyGo(AppWelcomeActivity::class.java){
+                            it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+                            it.putExtra(AppWelcomeActivity.ONLY_SHOW, true)
+                        }
+                    }
+                }
+            }
+            Lifecycle.Event.ON_PAUSE->{
+                if (Datas.bgShowAppOpen){
+                    ProcessLifecycleOwner.get().lifecycleScope.launch {
+                        AdLoad.preloadAd(this@LibApplication, Datas.APP_OPEN)
+                    }
+                }
+            }
+            else->{
+
+            }
+
+        }
+    }
+
+}

+ 3 - 1
app/src/main/java/com/convenient/android/lib/ui/sample/MainActivity.kt

@@ -21,6 +21,7 @@ import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import com.convenient.android.common.extension.readyGo
 import com.convenient.android.lib.DateUtilActivity
+import com.convenient.android.lib.ui.sample.ad.AdMainActivity
 import com.convenient.android.lib.ui.sample.media.MediaSampleActivity
 import com.convenient.android.lib.ui.theme.SampleTheme
 
@@ -86,7 +87,8 @@ private fun FuncListView() {
     val context = LocalContext.current
     val funcList = arrayListOf(
         FuncBean("DateUtils", DateUtilActivity::class.java),
-        FuncBean("MediaUtils", MediaSampleActivity::class.java)
+        FuncBean("MediaUtils", MediaSampleActivity::class.java),
+        FuncBean("广告测试",AdMainActivity::class.java)
     )
     Column {
         LazyColumn() {

+ 39 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/AdMainActivity.kt

@@ -0,0 +1,39 @@
+package com.convenient.android.lib.ui.sample.ad
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.compose.setContent
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.AdUnitConfigManager
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import com.convenient.android.lib.ui.sample.ad.page.AdMainPage
+import com.convenient.android.lib.ui.theme.SampleTheme
+import com.composition.android.ad.admob.impl.AdmobInitialize
+
+class AdMainActivity : AppCompatActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            SampleTheme {
+                AdMainPage()
+            }
+        }
+        initAd()
+
+    }
+
+
+    private fun initAd(){
+
+        AdManager.instance.init(applicationContext, true)
+        AdManager.instance.initAdvertisersSDK(applicationContext, AdmobInitialize())
+        AdUnitConfigManager.instance.setAdUnits(Datas.AdmobAdUnitList)
+        AdManager.instance.addGlobalAdShowListener {
+
+            Log.e("广告全局展示监听", "广告位名称:${it.adSlotName}\n广告商:${it.advertisersName}\n广告id:${it.adUnitId}\n广告类型:${it.adType}")
+
+        }
+
+    }
+}

+ 64 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdAppOpenActivity.kt

@@ -0,0 +1,64 @@
+package com.convenient.android.lib.ui.sample.ad.activity
+
+import android.os.Bundle
+import androidx.lifecycle.lifecycleScope
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.basic.AdResult
+import com.convenient.android.common.base.viewbinding.BaseBindingActivity
+import com.convenient.android.common.extension.readyGo
+import com.convenient.android.common.extension.setViewsClick
+import com.convenient.android.common.utils.ToastUtil
+import com.convenient.android.lib.R
+import com.convenient.android.lib.databinding.ActivityAdAppOpenBinding
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/9
+ * description:
+ */
+class AdAppOpenActivity : BaseBindingActivity<ActivityAdAppOpenBinding>(ActivityAdAppOpenBinding::inflate) {
+
+    private val status = MutableStateFlow(Datas.bgShowAppOpen)
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        lifecycleScope.launch {
+            status.collect{
+                binding.btnOpenBgAppOpen.text = getString(R.string.app_open_1, if (it) getString(R.string.open) else getString(R.string.close))
+            }
+        }
+        setViewsClick({
+            when (it.id) {
+                R.id.btn_load_app_open -> {
+                    lifecycleScope.launch {
+                        AdLoad.loadAd(this@AdAppOpenActivity, Datas.APP_OPEN)
+                            .collect{
+                                if (it is AdResult.Success){
+                                    AdLoad.addToCache(it)
+                                    ToastUtil.showLongToast(this@AdAppOpenActivity, "开屏广告加载成功")
+                                }
+                            }
+                    }
+                }
+                R.id.btn_show_app_open -> {
+                    readyGo(AppWelcomeActivity::class.java){
+                        it.putExtra(AppWelcomeActivity.ONLY_SHOW, true)
+                    }
+                }
+                R.id.btn_open_bg_app_open->{
+                    Datas.bgShowAppOpen = Datas.bgShowAppOpen.not()
+                    status.value = Datas.bgShowAppOpen
+                }
+                else -> {}
+            }
+        }, binding.btnLoadAppOpen, binding.btnShowAppOpen, binding.btnOpenBgAppOpen)
+    }
+
+
+}

+ 80 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdBannerActivity.kt

@@ -0,0 +1,80 @@
+package com.convenient.android.lib.ui.sample.ad.activity
+
+import android.os.Bundle
+import androidx.lifecycle.lifecycleScope
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.util.adLogE
+import com.convenient.android.common.base.viewbinding.BaseBindingActivity
+import com.convenient.android.common.extension.setViewsClick
+import com.convenient.android.common.utils.ToastUtil
+import com.convenient.android.lib.R
+import com.convenient.android.lib.databinding.ActivityAdBannerBinding
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description: 横幅广告示例
+ */
+class AdBannerActivity : BaseBindingActivity<ActivityAdBannerBinding>(ActivityAdBannerBinding::inflate) {
+
+    private var bannerAdResult: AdResult.Success? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setViewsClick({
+            when (it.id) {
+                R.id.btn_load_banner -> {
+                    loadBanner()
+                }
+                R.id.btn_show_banner -> {
+                    lifecycleScope.launch {
+                        delay(1000)
+                        showBanner()
+                    }
+                }
+                R.id.btn_load_and_show -> {
+                    lifecycleScope.launch {
+                        binding.llAdContent.removeAllViews()
+                        delay(1000)
+                        loadBanner(true)
+                    }
+                }
+            }
+
+        }, binding.btnLoadBanner, binding.btnShowBanner, binding.btnLoadAndShow)
+    }
+
+    private fun loadBanner(show: Boolean = false) {
+        lifecycleScope.launch {
+            AdLoad.loadAd(this@AdBannerActivity, Datas.BANNER)
+                .collect {
+                    if (it is AdResult.Success) {
+                        bannerAdResult = it
+                        ToastUtil.showLongToast(this@AdBannerActivity, "加载Banner广告成功")
+                        if (show) {
+                            showBanner()
+                        }
+                    } else {
+                        adLogE(AdManager.TAG, it.msg)
+                    }
+                }
+        }
+    }
+
+    private fun showBanner() {
+        bannerAdResult?.let {
+            AdLoad.getBannerAdView(this, it)?.let { adView ->
+                binding.llAdContent.removeAllViews()
+                binding.llAdContent.addView(adView)
+            }
+        } ?: ToastUtil.showLongToast(this, "请先加载广告")
+    }
+
+
+}

+ 79 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdFullScreenAdActivity.kt

@@ -0,0 +1,79 @@
+package com.convenient.android.lib.ui.sample.ad.activity
+
+import android.os.Bundle
+import androidx.lifecycle.lifecycleScope
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.AdType
+import com.composition.android.lib.ad.impl.NormalAdListener
+import com.convenient.android.common.base.viewbinding.BaseBindingActivity
+import com.convenient.android.common.extension.setViewsClick
+import com.convenient.android.common.utils.ToastUtil
+import com.convenient.android.lib.R
+import com.convenient.android.lib.databinding.ActivityAdInterstitialBinding
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/8
+ * description:
+ */
+class AdFullScreenAdActivity : BaseBindingActivity<ActivityAdInterstitialBinding>(ActivityAdInterstitialBinding::inflate) {
+
+    private var adResult : AdResult.Success? = null
+
+    private var adSlotName : String = Datas.INTERSTITIAL
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        adSlotName = intent.getStringExtra("ad_slot_name")?:Datas.INTERSTITIAL
+        binding.tvAdSlotName.text = "当前加载广告位为:${adSlotName}"
+
+        setViewsClick({
+            when (it.id) {
+                R.id.btn_load_full_screen_ad -> {
+                    loadFullScreenAd {
+                        adResult = it
+                    }
+                }
+                R.id.btn_show_full_screen_ad->{
+                    showRewardVideo()
+                }
+                R.id.btn_load_and_show->{
+                    loadFullScreenAd {
+                        adResult = it
+                        showRewardVideo()
+                    }
+                }
+                else -> {}
+            }
+        }, binding.btnLoadFullScreenAd, binding.btnShowFullScreenAd, binding.btnLoadAndShow)
+
+    }
+
+
+    private fun loadFullScreenAd(result : (AdResult.Success) -> Unit){
+        lifecycleScope.launch {
+            AdLoad.loadAd(this@AdFullScreenAdActivity, adSlotName)
+                .collect{
+                    if (it is AdResult.Success){
+                        result.invoke(it)
+                        ToastUtil.showLongToast(this@AdFullScreenAdActivity, "${adSlotName}广告加载成功")
+                    }
+                }
+        }
+    }
+
+    private fun showRewardVideo(){
+        adResult?.let {
+            AdLoad.showFullScreenAd(this, it){
+                onAdClose {
+                    adResult?.adObject = null
+                    adResult = null
+                }
+            }
+        }
+    }
+}

+ 100 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdNativeActivity.kt

@@ -0,0 +1,100 @@
+package com.convenient.android.lib.ui.sample.ad.activity
+
+import android.os.Bundle
+import androidx.lifecycle.lifecycleScope
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.buildAdNativeViewHolder
+import com.composition.android.lib.ad.util.adLogE
+import com.convenient.android.common.base.viewbinding.BaseBindingActivity
+import com.convenient.android.common.extension.readyGo
+import com.convenient.android.common.extension.setViewsClick
+import com.convenient.android.common.utils.ToastUtil
+import com.convenient.android.lib.R
+import com.convenient.android.lib.databinding.ActivityAdNativeBinding
+import com.convenient.android.lib.databinding.ViewAdNativeBinding
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description: 横幅广告示例
+ */
+class AdNativeActivity : BaseBindingActivity<ActivityAdNativeBinding>(ActivityAdNativeBinding::inflate) {
+
+    private var adResult: AdResult.Success? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setViewsClick({
+            when (it.id) {
+                R.id.btn_load_native_in_recyclerview->{
+                    readyGo(AdNativeListActivity::class.java)
+                }
+                R.id.btn_load_native -> {
+                    loadNativeAd { success ->
+                        adResult = success
+                    }
+                }
+                R.id.btn_show_native -> {
+                    lifecycleScope.launch {
+                        delay(1000)
+                        showNativeAd()
+                    }
+                }
+                R.id.btn_load_and_show -> {
+                    lifecycleScope.launch {
+                        binding.llAdContent.removeAllViews()
+                        delay(1000)
+                        loadNativeAd {
+                            adResult = it
+                            showNativeAd()
+                        }
+                    }
+                }
+            }
+
+        }, binding.btnLoadNative, binding.btnShowNative, binding.btnLoadAndShow,binding.btnLoadNativeInRecyclerview)
+    }
+
+    private fun loadNativeAd(success: (AdResult.Success) -> Unit) {
+        lifecycleScope.launch {
+            AdLoad.loadAd(this@AdNativeActivity, Datas.NATIVE)
+                .collect {
+                    if (it is AdResult.Success) {
+                        ToastUtil.showLongToast(this@AdNativeActivity, "加载原生广告成功")
+                        success.invoke(it)
+                    } else {
+                        adLogE(AdManager.TAG, it.msg)
+                    }
+                }
+        }
+    }
+
+    private fun showNativeAd() {
+        adResult?.let {
+
+            val nativeViewBinding = ViewAdNativeBinding.inflate(layoutInflater)
+            val viewHolder = buildAdNativeViewHolder {
+                this.rootView = nativeViewBinding.root
+                this.titleView = nativeViewBinding.tvAdTitle
+                this.titleDescView = nativeViewBinding.tvAdTitleDesc
+                this.iconView = nativeViewBinding.ivAdIcon
+                this.contentMediaGroup = nativeViewBinding.flAdMediaContent
+                this.callActionView = nativeViewBinding.btnCallToAction
+            }
+
+            AdLoad.getNativeAdView(this, it, viewHolder)?.let {
+                binding.llAdContent.removeAllViews()
+                binding.llAdContent.addView(it)
+            }
+
+        } ?: ToastUtil.showLongToast(this, "请先加载广告")
+    }
+
+
+}

+ 33 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AdNativeListActivity.kt

@@ -0,0 +1,33 @@
+package com.convenient.android.lib.ui.sample.ad.activity
+
+import android.os.Bundle
+import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.convenient.android.common.base.viewbinding.BaseBindingActivity
+import com.convenient.android.lib.databinding.ActivityAdNativeListBinding
+import com.convenient.android.lib.ui.sample.ad.adapter.NativeAdSampleListAdapter
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/8
+ * description:原生广告在RecyclerView中的使用示例
+ */
+class AdNativeListActivity : BaseBindingActivity<ActivityAdNativeListBinding>(ActivityAdNativeListBinding::inflate) {
+
+    private var adapter : NativeAdSampleListAdapter = NativeAdSampleListAdapter()
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        binding.recyclerView.layoutManager = LinearLayoutManager(this)
+        binding.recyclerView.adapter = adapter
+
+        lifecycleScope.launch {
+            val list = Datas.getNativeADSampleLists(this@AdNativeListActivity)
+            adapter.setData(list.toMutableList())
+        }
+    }
+}

+ 118 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/activity/AppWelcomeActivity.kt

@@ -0,0 +1,118 @@
+package com.convenient.android.lib.ui.sample.ad.activity
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.lifecycleScope
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.basic.AdResult
+import com.convenient.android.common.base.view.BaseActivity
+import com.convenient.android.common.extension.readyGo
+import com.convenient.android.lib.R
+import com.convenient.android.lib.ui.sample.MainActivity
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import com.convenient.android.lib.ui.theme.SampleTheme
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/9
+ * description:
+ */
+class AppWelcomeActivity : BaseActivity() {
+
+    companion object {
+        const val ONLY_SHOW = "only_show"
+    }
+
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContent {
+            WelcomePage()
+        }
+
+        lifecycleScope.launch {
+
+            val onlyShow = intent.getBooleanExtra(ONLY_SHOW, false)
+            if (onlyShow) {
+                showAd {
+                    lifecycleScope.launch {
+                        delay(300)
+                        finish()
+                    }
+                }
+            }else{
+                showAd {
+                    toGoMainActivity()
+                }
+            }
+        }
+    }
+
+    private suspend fun showAd(result : () -> Unit){
+
+        AdLoad.loadAd(this, Datas.APP_OPEN)
+            .collect{
+                if (it is AdResult.Success){
+                    delay(200)
+                    AdLoad.showSplash(this, null, it){
+                        onAdClose {
+                            result.invoke()
+                        }
+                        onAdLoadedFail {
+                            result.invoke()
+                        }
+                        onAdSkip {
+                            result.invoke()
+                        }
+                    }
+                }else{
+                    result.invoke()
+                }
+            }
+    }
+
+
+    private fun toGoMainActivity(delay : Long = 3000){
+
+        lifecycleScope.launch {
+            delay(delay)
+            this@AppWelcomeActivity.readyGo(MainActivity::class.java)
+            finish()
+        }
+
+    }
+}
+
+
+
+@Composable
+fun WelcomePage() {
+
+    Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
+
+        Image(painter = painterResource(id = R.mipmap.ic_launcher), contentDescription = null, modifier = Modifier.size(60.dp))
+        Spacer(modifier = Modifier.padding(vertical = 16.dp))
+        Text(text = "组件Demo")
+    }
+
+}
+
+@Preview(showSystemUi = true)
+@Composable
+fun WelcomePagePreview() {
+    SampleTheme {
+        WelcomePage()
+    }
+}

+ 72 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/adapter/NativeAdSampleListAdapter.kt

@@ -0,0 +1,72 @@
+package com.convenient.android.lib.ui.sample.ad.adapter
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.composition.android.lib.ad.AdLoad
+import com.convenient.android.common.base.viewbinding.BindingViewHolder
+import com.convenient.android.common.utils.date.DateUtils
+import com.convenient.android.common.utils.date.toDate
+import com.convenient.android.lib.R
+import com.convenient.android.lib.databinding.ViewRvAdNativeItemBinding
+import com.convenient.android.lib.databinding.ViewRvMediaSampleItemBinding
+import com.convenient.android.lib.ui.sample.ad.model.NativeListBean
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/8
+ * description:
+ */
+class NativeAdSampleListAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
+
+    companion object{
+        const val NORMAL_ITEM = 0
+
+        const val AD_ITEM = 1
+    }
+
+    private var lists : MutableList<NativeListBean> = mutableListOf()
+
+    fun setData(list : MutableList<NativeListBean>){
+        this.lists = list
+        notifyDataSetChanged()
+    }
+
+    override fun getItemViewType(position: Int): Int {
+        return lists[position].viewType
+    }
+
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+        return if (viewType == NORMAL_ITEM){
+            BindingViewHolder(parent, ViewRvMediaSampleItemBinding::inflate)
+        }else{
+            BindingViewHolder(parent, ViewRvAdNativeItemBinding::inflate)
+        }
+    }
+
+    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+        if(lists[position].viewType == NORMAL_ITEM){
+            val normalHolder = holder as BindingViewHolder<ViewRvMediaSampleItemBinding>
+            val item = lists[position]
+            item.media?.let {
+                normalHolder.binding.ivMediaItemIcon.setImageResource(if (item.media.isFile) R.drawable.ic_icons8_file else R.drawable.ic_icons8_folder)
+                normalHolder.binding.let {
+                    it.tvMediaItemTitle.text = item.media.name
+                    it.tvMediaItemDate.text = item.media.lastModified.toDate(DateUtils.DateFormat.DEFAULT_PATTERN.dateFormat)
+                }
+            }
+        }else{
+            (holder as BindingViewHolder<ViewRvAdNativeItemBinding>)
+                .binding.nativeAdView.populateNativeAd(lists[position].success!!)
+        }
+    }
+
+    override fun getItemCount(): Int {
+        return lists.size
+    }
+
+
+
+}

+ 82 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/model/Datas.kt

@@ -0,0 +1,82 @@
+package com.convenient.android.lib.ui.sample.ad.model
+
+import android.content.Context
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.AdType
+import com.composition.android.lib.ad.basic.Advertisers
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.bean.AdUnitLoadConfig
+import com.convenient.android.common.media.MediaBean
+import com.convenient.android.lib.ui.sample.ad.adapter.NativeAdSampleListAdapter
+import kotlinx.coroutines.flow.firstOrNull
+import kotlin.random.Random
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:
+ */
+object Datas {
+
+    fun getAdvertisers(): List<Advertisers> {
+        return listOf(
+            Advertisers.Admob,
+            Advertisers.AppLovin,
+            Advertisers.CSJ,
+            Advertisers.CUSTOM,
+        )
+    }
+
+    var bgShowAppOpen = false
+
+    val BANNER = "banner"
+    val NATIVE = "native"
+    val INTERSTITIAL = "interstitial"
+    val REWARDED_VIDEO = "rewarded_video"
+    val REWARDED_INTERSTITIAL = "rewarded_interstitial"
+    val APP_OPEN = "app_open"
+
+
+    val AdmobAdUnitList: List<AdUnitBean> =
+        listOf(
+            AdUnitBean(BANNER, Advertisers.Admob.name, AdType.BANNER.name, "ca-app-pub-3940256099942544/6300978111"),
+            AdUnitBean(NATIVE, Advertisers.Admob.name, AdType.NATIVE.name, "ca-app-pub-3940256099942544/2247696110"),
+            AdUnitBean(INTERSTITIAL, Advertisers.Admob.name, AdType.INTERSTITIAL.name, "ca-app-pub-3940256099942544/1033173712"),
+            AdUnitBean(REWARDED_VIDEO, Advertisers.Admob.name, AdType.REWARDED_VIDEO.name, "ca-app-pub-3940256099942544/5224354917"),
+            AdUnitBean(REWARDED_INTERSTITIAL, Advertisers.Admob.name, AdType.REWARDED_INTERSTITIAL.name, "ca-app-pub-3940256099942544/5354046379"),
+            AdUnitBean(APP_OPEN, Advertisers.Admob.name, AdType.APP_OPEN.name, "ca-app-pub-3940256099942544/3419835294", requestAdConfig = AdUnitLoadConfig(adLoadRetryCount = 1, adLoadTimeOut = 5000))
+        )
+
+
+    suspend fun getNativeADSampleLists(context: Context): List<NativeListBean> {
+
+        val list = mutableListOf<NativeListBean>()
+
+        var files = (0..100).map {
+            MediaBean(name = "测试:${it}", lastModified = System.currentTimeMillis(), isFile = true, extension = "png", parentPath = "", mediaPath = "")
+        }.map {
+            NativeListBean(viewType = NativeAdSampleListAdapter.NORMAL_ITEM, media = it)
+        }
+        list.addAll(files)
+
+
+        val adList = mutableListOf<AdResult.Success>()
+        for (i in 0..4) {
+            val adResult = AdLoad.loadAd(context, NATIVE).firstOrNull()
+            if (adResult is AdResult.Success) {
+                adList.add(adResult)
+            }
+        }
+
+        for (success in adList) {
+            val rand = Random(System.nanoTime())
+            val randomNum = (0..list.size).random(rand)
+            list.add(randomNum, NativeListBean(viewType = NativeAdSampleListAdapter.AD_ITEM, success = success))
+        }
+
+        return list
+    }
+
+}

+ 18 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/model/NativeListBean.kt

@@ -0,0 +1,18 @@
+package com.convenient.android.lib.ui.sample.ad.model
+
+import com.composition.android.lib.ad.basic.AdResult
+import com.convenient.android.common.media.MediaBean
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/8
+ * description:
+ */
+data class NativeListBean(
+    val viewType : Int = 0,
+    val media : MediaBean? = null,
+    val success: AdResult.Success? = null
+) {
+
+}

+ 15 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdAppOpenPage.kt

@@ -0,0 +1,15 @@
+package com.convenient.android.lib.ui.sample.ad.page
+
+import androidx.compose.runtime.Composable
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:
+ */
+
+@Composable
+fun AdAppOpenPage(){
+    
+}

+ 16 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdBannerPage.kt

@@ -0,0 +1,16 @@
+package com.convenient.android.lib.ui.sample.ad.page
+
+import androidx.compose.runtime.Composable
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:
+ */
+
+@Composable
+fun AdBannerPage(){
+
+
+}

+ 116 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdHomePage.kt

@@ -0,0 +1,116 @@
+package com.convenient.android.lib.ui.sample.ad.page
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.RadioButton
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import com.composition.android.lib.ad.basic.Advertisers
+import com.convenient.android.common.extension.readyGo
+import com.convenient.android.lib.ui.sample.ad.activity.AdAppOpenActivity
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import com.convenient.android.lib.ui.sample.ad.activity.AdBannerActivity
+import com.convenient.android.lib.ui.sample.ad.activity.AdFullScreenAdActivity
+import com.convenient.android.lib.ui.sample.ad.activity.AdNativeActivity
+import com.convenient.android.lib.ui.sample.ad.viewmodel.AdConfigViewModel
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:
+ */
+
+
+@Composable
+fun AdHomePage(viewModel: AdConfigViewModel, navHostController: NavHostController) {
+
+    val list = Datas.getAdvertisers()
+    val registerAdvertisers = remember {
+        viewModel.advertisers
+    }
+    val expanded = remember {
+        mutableStateOf(false)
+    }
+
+    val context = LocalContext.current
+
+    Column {
+
+        Box {
+            Button(
+                shape = RectangleShape,
+                onClick = {
+                    expanded.value = true
+                }, modifier = Modifier.padding(bottom = 4.dp)
+            ) {
+                Text(text = "启用的广告商${if (registerAdvertisers.value == Advertisers.UNKNOWN) "" else ":${registerAdvertisers.value.name}"}")
+            }
+
+            DropdownMenu(expanded = expanded.value, offset = DpOffset(0.dp, 0.dp), onDismissRequest = { expanded.value = false }) {
+                for (advertisers in list) {
+                    DropdownMenuItem(onClick = {
+                        viewModel.changeRegisterAdvertisers(advertisers)
+                        expanded.value = false
+                    }) {
+                        Row(verticalAlignment = Alignment.CenterVertically) {
+                            RadioButton(selected = registerAdvertisers.value == advertisers, onClick = null)
+                            Text(text = advertisers.name, modifier = Modifier.padding(start = 8.dp))
+                        }
+                    }
+                }
+            }
+        }
+
+        if (registerAdvertisers.value != Advertisers.UNKNOWN) {
+
+            val modifier = Modifier.fillMaxWidth()
+            Item(modifier = modifier, title = "横幅广告") {
+                context.readyGo(AdBannerActivity::class.java)
+            }
+            Item(modifier = modifier, title = "原生广告") {
+                context.readyGo(AdNativeActivity::class.java)
+            }
+            Item(modifier = modifier, title = "插屏广告") {
+                context.readyGo(AdFullScreenAdActivity::class.java){
+                    it.putExtra("ad_slot_name", Datas.INTERSTITIAL)
+                }
+            }
+            Item(modifier = modifier, title = "激励视频广告") {
+                context.readyGo(AdFullScreenAdActivity::class.java){
+                    it.putExtra("ad_slot_name", Datas.REWARDED_VIDEO)
+                }
+            }
+            Item(modifier = modifier, title = "激励插屏广告") {
+                context.readyGo(AdFullScreenAdActivity::class.java){
+                    it.putExtra("ad_slot_name", Datas.REWARDED_INTERSTITIAL)
+                }
+            }
+            Item(modifier = modifier, title = "开屏广告") {
+                context.readyGo(AdAppOpenActivity::class.java){
+                }
+            }
+        }
+    }
+}
+
+@Composable
+private fun Item(modifier: Modifier, title: String, onClick: () -> Unit) {
+    FilledTonalButton(
+        modifier = modifier,
+        onClick = onClick
+    ) {
+        Text(text = title)
+    }
+}
+

+ 115 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdInterstitialPage.kt

@@ -0,0 +1,115 @@
+package com.convenient.android.lib.ui.sample.ad.page
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.ContextWrapper
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.FilledTonalButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.basic.AdResult
+import com.convenient.android.common.utils.ToastUtil
+import com.convenient.android.lib.ui.sample.ad.AdMainActivity
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:
+ */
+
+@Composable
+fun AdInterstitialPage() {
+
+    val composableScope = rememberCoroutineScope()
+    val context = LocalContext.current
+    val activity = LocalContext.current.getActivity<AdMainActivity>()
+
+    var adResult: AdResult.Success? = null
+
+    Column {
+        val modifier = Modifier.fillMaxWidth()
+        FilledTonalButton(modifier = modifier, onClick = {
+            loadInterstitial(context, composableScope) {
+                adResult = it
+            }
+        }) {
+            Text(text = "加载插屏广告")
+        }
+
+        FilledTonalButton(modifier = modifier, onClick = {
+            adResult?.let {
+                if (activity != null) {
+                    showInterstitial(activity, it)
+                }
+
+            }
+        }) {
+            Text(text = "展示插屏广告")
+        }
+
+
+        FilledTonalButton(modifier = modifier, onClick = {
+            loadInterstitial(context = context, composableScope){
+                adResult = it
+                adResult?.let {
+                    if (activity != null) {
+                        showInterstitial(activity, it)
+                    }
+                }
+            }
+        }) {
+            Text(text = "加载并展示插屏广告")
+        }
+    }
+
+}
+
+
+
+@SuppressLint("CoroutineCreationDuringComposition")
+private fun loadInterstitial(context: Context, composable: CoroutineScope, result: (AdResult.Success) -> Unit) {
+    composable.launch {
+        AdLoad.loadAd(context, Datas.INTERSTITIAL)
+            .collect {
+                if (it is AdResult.Success) {
+                    result.invoke(it)
+                    ToastUtil.showLongToast(context, "插屏广告加载成功")
+                }
+            }
+    }
+}
+
+
+private fun showInterstitial(activity: ComponentActivity, adResult: AdResult.Success) {
+    AdLoad.showInterstitialAd(activity, success = adResult) {
+
+    }
+
+}
+
+
+inline fun <reified Activity : ComponentActivity> Context.getActivity(): Activity? {
+    return when (this) {
+        is Activity -> this
+        else -> {
+            var context = this
+            while (context is ContextWrapper) {
+                context = context.baseContext
+                if (context is Activity) return context
+            }
+            null
+        }
+    }
+}

+ 89 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdMainPage.kt

@@ -0,0 +1,89 @@
+package com.convenient.android.lib.ui.sample.ad.page
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavController
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.rememberNavController
+import com.composition.android.lib.ad.basic.Advertisers
+import com.convenient.android.lib.ui.sample.ad.model.Datas
+import com.convenient.android.lib.ui.sample.ad.viewmodel.AdConfigViewModel
+import com.convenient.android.lib.ui.theme.SampleTheme
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:
+ */
+
+object AdRouteConfig {
+
+    const val AD_ROUTE_HOME = "ad_home"
+
+    const val AD_ROUTE_BANNER = "ad_banner"
+
+    const val AD_ROUTE_NATIVE = "ad_native"
+
+    const val AD_ROUTE_INTERSTITIAL = "ad_interstitial"
+
+    const val AD_ROUTE_REWARD_VIDEO = "ad_reward_video"
+
+    const val AD_APP_OPEN = "ad_app_open"
+}
+
+
+@Composable
+fun AdMainPage() {
+    val viewModel: AdConfigViewModel = viewModel()
+    val navHostController = rememberNavController()
+    NavHost(navHostController = navHostController, viewModel = viewModel)
+}
+
+@Composable
+fun NavHost(navHostController: NavHostController, viewModel: AdConfigViewModel) {
+    androidx.navigation.compose.NavHost(
+        modifier = Modifier.padding(all = 8.dp),
+        navController = navHostController, startDestination = AdRouteConfig.AD_ROUTE_HOME
+    ) {
+        composable(AdRouteConfig.AD_ROUTE_HOME) {
+            AdHomePage(viewModel = viewModel,navHostController)
+        }
+
+        composable(AdRouteConfig.AD_ROUTE_BANNER){
+            AdBannerPage()
+        }
+        composable(AdRouteConfig.AD_ROUTE_NATIVE){
+            AdNativePage()
+        }
+        composable(AdRouteConfig.AD_ROUTE_REWARD_VIDEO){
+            AdRewardVideoPage()
+        }
+        composable(AdRouteConfig.AD_ROUTE_INTERSTITIAL){
+            AdInterstitialPage()
+        }
+        composable(AdRouteConfig.AD_APP_OPEN){
+            AdAppOpenPage()
+        }
+    }
+}
+
+
+@Preview
+@Composable
+fun AdMainPagePreview() {
+    SampleTheme {
+        AdMainPage()
+    }
+}
+

+ 15 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdNativePage.kt

@@ -0,0 +1,15 @@
+package com.convenient.android.lib.ui.sample.ad.page
+
+import androidx.compose.runtime.Composable
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:
+ */
+
+@Composable
+fun AdNativePage(){
+    
+}

+ 15 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/page/AdRewardVideoPage.kt

@@ -0,0 +1,15 @@
+package com.convenient.android.lib.ui.sample.ad.page
+
+import androidx.compose.runtime.Composable
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:
+ */
+
+@Composable
+fun AdRewardVideoPage(){
+    
+}

+ 25 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/util/CoilImageLoader.kt

@@ -0,0 +1,25 @@
+package com.convenient.android.lib.ui.sample.ad.util
+
+import android.content.Context
+import android.view.View
+import androidx.appcompat.widget.AppCompatImageView
+import coil.Coil
+import coil.load
+import com.composition.android.lib.ad.interfaces.ImageLoader
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/7
+ * description:
+ */
+class CoilImageLoader : ImageLoader<AppCompatImageView> {
+
+    override fun displayImage(context: Context?, path: Any?, imageView: AppCompatImageView) {
+        imageView.load(path)
+    }
+
+    override fun createImageView(context: Context?): AppCompatImageView {
+        return AppCompatImageView(context!!)
+    }
+}

+ 39 - 0
app/src/main/java/com/convenient/android/lib/ui/sample/ad/viewmodel/AdConfigViewModel.kt

@@ -0,0 +1,39 @@
+package com.convenient.android.lib.ui.sample.ad.viewmodel
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.lifecycle.ViewModel
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.basic.Advertisers
+import com.composition.android.lib.ad.impl.UnknownAdLoader
+import com.composition.android.lib.ad.interfaces.AdLoader
+import com.composition.android.ad.admob.load.AdmobAdLoader
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:
+ */
+class AdConfigViewModel : ViewModel() {
+
+    var advertisers = mutableStateOf(Advertisers.UNKNOWN)
+
+
+    fun changeRegisterAdvertisers(advertisers: Advertisers){
+        if (this.advertisers.value == advertisers){
+            this.advertisers.value = Advertisers.UNKNOWN
+        }else{
+            this.advertisers.value = advertisers
+        }
+        AdManager.instance.registerAdLoader(getAdLoader(this.advertisers.value))
+    }
+
+    fun getAdLoader(advertisers: Advertisers) : Pair<Advertisers, AdLoader>{
+       return when(advertisers){
+            Advertisers.Admob-> Advertisers.Admob to AdmobAdLoader()
+            else-> Advertisers.UNKNOWN to UnknownAdLoader()
+        }
+    }
+
+
+}

+ 0 - 5
app/src/main/java/com/convenient/android/lib/ui/sample/media/MediaFilesPage.kt

@@ -1,12 +1,8 @@
 package com.convenient.android.lib.ui.sample.media
 
 import android.Manifest
-import android.app.Activity
 import android.content.Intent
 import android.content.pm.PackageManager
-import android.os.Build
-import android.os.Environment
-import android.provider.Settings
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.Image
@@ -16,7 +12,6 @@ import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.*
 import androidx.compose.material.Icon
 import androidx.compose.material.RadioButton
 import androidx.compose.material.Text

+ 0 - 2
app/src/main/java/com/convenient/android/lib/ui/theme/Theme.kt

@@ -40,7 +40,6 @@ private val LightColors = lightColorScheme(
     inverseOnSurface = md_theme_light_inverseOnSurface,
     inverseSurface = md_theme_light_inverseSurface,
     inversePrimary = md_theme_light_inversePrimary,
-    surfaceTint = md_theme_light_surfaceTint,
 //    surfaceTintColor = md_theme_light_surfaceTintColor,
 )
 
@@ -72,7 +71,6 @@ private val DarkColors = darkColorScheme(
     inverseOnSurface = md_theme_dark_inverseOnSurface,
     inverseSurface = md_theme_dark_inverseSurface,
     inversePrimary = md_theme_dark_inversePrimary,
-    surfaceTint = md_theme_dark_surfaceTint,
 //    surfaceTintColor = md_theme_dark_surfaceTintColor,
 )
 

+ 30 - 0
app/src/main/res/layout/activity_ad_app_open.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_load_app_open"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="加载开屏广告" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_show_app_open"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="显示WelcomeActivity展示开屏广告" />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_open_bg_app_open"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/app_open_1"
+        />
+
+
+
+
+</LinearLayout>

+ 56 - 0
app/src/main/res/layout/activity_ad_banner.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:layout_height="match_parent">
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_load_banner"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="加载广告"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_show_banner"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="显示广告"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_load_and_show"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="加载并显示广告"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <LinearLayout
+        android:id="@+id/ll_ad_content"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        />
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="8dp"
+        android:text="以下为自动加载广告View,先配置好AdUnitBean,这里只需要指定ad_slot_name(广告位名称)即可"
+        />
+
+    <com.composition.android.lib.ad.widget.BannerAdView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:ad_slot_name="banner"
+        />
+
+</LinearLayout>

+ 46 - 0
app/src/main/res/layout/activity_ad_interstitial.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:orientation="vertical"
+    android:layout_height="match_parent">
+
+    <androidx.appcompat.widget.AppCompatTextView
+        android:id="@+id/tv_ad_slot_name"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="当前加载广告位为:插屏广告"
+        />
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_load_full_screen_ad"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="加载广告"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_show_full_screen_ad"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="显示广告"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+    <androidx.appcompat.widget.AppCompatButton
+        android:id="@+id/btn_load_and_show"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="加载并显示广告"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"/>
+
+
+
+
+</LinearLayout>

+ 139 - 0
app/src/main/res/layout/activity_ad_native.xml

@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/btn_load_native_in_recyclerview"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="原生广告在RecyclerView中运用"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/btn_load_native"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="加载广告"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/btn_show_native"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="显示广告"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/btn_load_and_show"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="加载并显示广告"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="此处为getNativeAdView, 自定义布局不用写在此界面,通过addView添加到界面中" />
+
+        <LinearLayout
+            android:id="@+id/ll_ad_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" />
+
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:text="原生广告自行加载填充模式,在Xml NativeAdView中设置好配置, View中自动完成加载"
+            />
+
+        <com.composition.android.lib.ad.widget.NativeAdView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:native_ad_slot_name="native"
+            android:padding="8dp"
+            app:native_admob_advertisers_root_view="@layout/layout_admob_native_ad_view_root"
+            app:ad_view_title="@id/tv_ad_title_1"
+            app:ad_view_title_desc="@id/tv_ad_title_desc_1"
+            app:ad_view_icon="@id/iv_ad_icon_1"
+            app:ad_view_media_content_group="@id/fl_ad_media_content_1"
+            app:ad_view_call_to_action="@id/btn_call_to_action_1"
+            >
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:id="@+id/cl_ad_root_1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <androidx.appcompat.widget.AppCompatImageView
+                    android:id="@+id/iv_ad_icon_1"
+                    android:layout_width="50dp"
+                    android:layout_height="50dp"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent"
+                    tools:src="@tools:sample/avatars" />
+
+                <androidx.appcompat.widget.AppCompatTextView
+                    android:id="@+id/tv_ad_title_1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="8dp"
+                    android:textColor="@color/black"
+                    android:textSize="16sp"
+                    app:layout_constraintStart_toEndOf="@id/iv_ad_icon_1"
+                    app:layout_constraintTop_toTopOf="@id/iv_ad_icon_1"
+                    tools:text="Ad Title" />
+
+                <androidx.appcompat.widget.AppCompatTextView
+                    android:id="@+id/tv_ad_title_desc_1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    app:layout_constraintStart_toStartOf="@id/tv_ad_title_1"
+                    app:layout_constraintTop_toBottomOf="@id/tv_ad_title_1"
+                    tools:text="Desc" />
+
+                <FrameLayout
+                    android:id="@+id/fl_ad_media_content_1"
+                    android:layout_width="match_parent"
+                    android:layout_height="200dp"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    android:layout_marginTop="8dp"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@id/iv_ad_icon_1"
+                    tools:background="@tools:sample/backgrounds/scenic"
+                    />
+
+                <androidx.appcompat.widget.AppCompatButton
+                    android:id="@+id/btn_call_to_action_1"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginTop="4dp"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@id/fl_ad_media_content_1" />
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+        </com.composition.android.lib.ad.widget.NativeAdView>
+
+    </LinearLayout>
+
+</ScrollView>

+ 18 - 0
app/src/main/res/layout/activity_ad_native_list.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/recycler_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+
+        />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 0 - 1
app/src/main/res/layout/activity_date.xml

@@ -13,7 +13,6 @@
         android:textSize="14sp"
         app:layout_constraintLeft_toLeftOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
-
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/id_date_activity_tv_time"
         android:layout_width="match_parent"

+ 56 - 0
app/src/main/res/layout/view_ad_native.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_ad_icon"
+            android:layout_width="50dp"
+            android:layout_height="50dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:src="@tools:sample/avatars" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_ad_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:textColor="@color/black"
+            android:textSize="16sp"
+            app:layout_constraintStart_toEndOf="@id/iv_ad_icon"
+            app:layout_constraintTop_toTopOf="@id/iv_ad_icon"
+            tools:text="Ad Title" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_ad_title_desc"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:layout_constraintStart_toStartOf="@id/tv_ad_title"
+            app:layout_constraintTop_toBottomOf="@id/tv_ad_title"
+            tools:text="Desc" />
+
+        <FrameLayout
+            android:id="@+id/fl_ad_media_content"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            android:layout_marginTop="8dp"
+            app:layout_constraintTop_toBottomOf="@id/iv_ad_icon"
+            tools:background="@tools:sample/backgrounds/scenic"
+            tools:layout_height="200dp" />
+
+        <androidx.appcompat.widget.AppCompatButton
+            android:id="@+id/btn_call_to_action"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="4dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/fl_ad_media_content" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>

+ 82 - 0
app/src/main/res/layout/view_rv_ad_native_item.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+
+    <com.composition.android.lib.ad.widget.NativeAdView
+        android:id="@+id/native_ad_view"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="8dp"
+        app:ad_view_call_to_action="@id/btn_call_to_action_1"
+        app:ad_view_icon="@id/iv_ad_icon_1"
+        app:ad_view_media_content_group="@id/fl_ad_media_content_1"
+        app:ad_view_title="@id/tv_ad_title_1"
+        app:ad_view_title_desc="@id/tv_ad_title_desc_1"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:ad_auto_populate="false"
+        app:native_ad_slot_name="native"
+        app:native_admob_advertisers_root_view="@layout/layout_admob_native_ad_view_root">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/cl_ad_root_1"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <androidx.appcompat.widget.AppCompatImageView
+                android:id="@+id/iv_ad_icon_1"
+                android:layout_width="50dp"
+                android:layout_height="50dp"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent"
+                tools:src="@tools:sample/avatars" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_ad_title_1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="8dp"
+                android:textColor="@color/black"
+                android:textSize="16sp"
+                app:layout_constraintStart_toEndOf="@id/iv_ad_icon_1"
+                app:layout_constraintTop_toTopOf="@id/iv_ad_icon_1"
+                tools:text="Ad Title" />
+
+            <androidx.appcompat.widget.AppCompatTextView
+                android:id="@+id/tv_ad_title_desc_1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                app:layout_constraintStart_toStartOf="@id/tv_ad_title_1"
+                app:layout_constraintTop_toBottomOf="@id/tv_ad_title_1"
+                tools:text="Desc" />
+
+            <FrameLayout
+                android:id="@+id/fl_ad_media_content_1"
+                android:layout_width="match_parent"
+                android:layout_height="200dp"
+                android:layout_marginTop="8dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/iv_ad_icon_1"
+                tools:background="@tools:sample/backgrounds/scenic" />
+
+            <androidx.appcompat.widget.AppCompatButton
+                android:id="@+id/btn_call_to_action_1"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="4dp"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/fl_ad_media_content_1" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </com.composition.android.lib.ad.widget.NativeAdView>
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -1,3 +1,7 @@
 <resources>
     <string name="app_name">组件库</string>
+
+    <string name="app_open_1">后台进入前台展示开屏(目前状态:%s)</string>
+    <string name="close">关闭</string>
+    <string name="open">开启</string>
 </resources>

+ 1 - 1
build.gradle

@@ -9,7 +9,7 @@ buildscript {
 plugins {
     id 'com.android.application' version '7.2.0' apply false
     id 'com.android.library' version '7.2.0' apply false
-    id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
+    id 'org.jetbrains.kotlin.android' version '1.7.10' apply false
 }
 
 

+ 1 - 0
lib_ad_admob/.gitignore

@@ -0,0 +1 @@
+/build

+ 43 - 0
lib_ad_admob/build.gradle

@@ -0,0 +1,43 @@
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+}
+
+android {
+    compileSdk 32
+
+    defaultConfig {
+        minSdk 21
+        targetSdk 32
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+    implementation project(':lib_ad_core')
+
+    api 'com.google.android.gms:play-services-ads:21.2.0'
+
+    implementation 'androidx.core:core-ktx:1.8.0'
+    implementation 'androidx.appcompat:appcompat:1.5.1'
+    implementation 'com.google.android.material:material:1.6.1'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}

+ 0 - 0
lib_ad_admob/consumer-rules.pro


+ 21 - 0
lib_ad_admob/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
lib_ad_admob/src/androidTest/java/com/composition/android/ad/admob/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.composition.android.ad.admob
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.composition.android.ad.admob.test", appContext.packageName)
+    }
+}

+ 6 - 0
lib_ad_admob/src/main/AndroidManifest.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.composition.android.ad.admob">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+</manifest>

+ 25 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/bean/AdmobAdUnitBean.kt

@@ -0,0 +1,25 @@
+package com.composition.android.ad.admob.bean
+
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.basic.strategy.IStrategy
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.bean.AdUnitLoadConfig
+import com.composition.android.lib.ad.impl.NormalStrategy
+import com.google.android.gms.ads.AdSize
+import com.composition.android.ad.admob.util.getAdSize
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:
+ */
+class AdmobAdUnitBean(
+    adSlotName: String,
+    advertisersName: String,
+    adType: String,
+    adUnitId: String,
+    var adSize: AdSize = getAdSize(AdManager.instance.context),
+    loadStrategy: IStrategy = NormalStrategy(),
+    requestAdConfig: AdUnitLoadConfig = AdUnitLoadConfig()
+) : AdUnitBean(adSlotName, advertisersName, adType, adUnitId, loadStrategy, requestAdConfig)

+ 21 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/impl/AdmobInitialize.kt

@@ -0,0 +1,21 @@
+package com.composition.android.ad.admob.impl
+
+import android.content.Context
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.interfaces.Initialize
+import com.composition.android.lib.ad.util.adLogE
+import com.google.android.gms.ads.MobileAds
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/13
+ * description: Admob广告初始化
+ */
+class AdmobInitialize : Initialize {
+    override fun init(context: Context) {
+        MobileAds.initialize(context){
+            adLogE(AdManager.TAG,"Admob初始化完成---------->")
+        }
+    }
+}

+ 220 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/AdmobAdLoader.kt

@@ -0,0 +1,220 @@
+package com.composition.android.ad.admob.load
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.AdType
+import com.composition.android.lib.ad.basic.BasicAdView
+import com.composition.android.lib.ad.basic.NativeAdViewHolder
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.AdListener
+import com.composition.android.lib.ad.interfaces.AdLoader
+import com.composition.android.lib.ad.util.adLogE
+import com.google.android.gms.ads.AdError
+import com.google.android.gms.ads.FullScreenContentCallback
+import com.google.android.gms.ads.appopen.AppOpenAd
+import com.google.android.gms.ads.interstitial.InterstitialAd
+import com.google.android.gms.ads.nativead.NativeAdView
+import com.google.android.gms.ads.rewarded.RewardedAd
+import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
+import com.composition.android.ad.admob.load.request.*
+import com.composition.android.ad.admob.load.view.AdmobBannerView
+import com.composition.android.ad.admob.load.view.AdmobNativeAdView
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description: Admob广告加载类
+ */
+class AdmobAdLoader : AdLoader {
+
+
+    override suspend fun load(context: Context, adUnitBean: AdUnitBean): AdResult {
+        if (adUnitBean.isAvailable().not()){
+            return AdResult.Fail(adUnitBean, msg = "AdUnitBean无效")
+        }
+        //获取到具体的广告格式加载类
+        val adFormatRequestImpl = when(adUnitBean.getAdType()){
+            AdType.REWARDED_VIDEO-> AdmobRewardRequestImpl()
+            AdType.INTERSTITIAL-> AdmobInterstitialRequestImpl()
+            AdType.REWARDED_INTERSTITIAL -> AdmobRewardInterstitialRequestImpl()
+            AdType.APP_OPEN -> AdmobAppOpenRequestImpl()
+            AdType.BANNER-> AdmobBannerRequestImpl()
+            AdType.NATIVE-> AdMobNativeRequestImpl()
+            else-> null
+        }
+        //根据策略进行加载
+        return loadByStrategy(context, adUnitBean, adFormatRequestImpl)
+    }
+
+    override fun getBannerView(context: Context, adResult: AdResult.Success): BasicAdView<*>? {
+        return if (adResult.adObject != null){
+            AdmobBannerView(context).apply {
+                render(adResult)
+                AdManager.instance.globalListener?.invoke(adResult.adBean)
+            }
+        }else{
+            null
+        }
+    }
+
+    override fun getNativeAdView(context: Context, adResult: AdResult.Success, viewHolder: NativeAdViewHolder): View? {
+        return if (adResult.adObject != null){
+            AdmobNativeAdView(context).apply {
+                setNativeAdViewHolder(viewHolder)
+                render(adResult)
+                AdManager.instance.globalListener?.invoke(adResult.adBean)
+            }
+        }else{
+            null
+        }
+    }
+
+    override fun populateNativeAdView(context: Context, adResult: AdResult.Success, viewHolder: NativeAdViewHolder, adRootView: View) {
+        if (adRootView is NativeAdView){
+            AdManager.instance.globalListener?.invoke(adResult.adBean)
+            AdmobNativeAdView.populateNativeAdView(context, adResult, viewHolder, adRootView)
+        }else{
+            adLogE(AdManager.TAG, "Admob原生广告填充失败,adRootView 类型错误,根布局应该为:NativeAdView")
+        }
+    }
+
+    /**
+     * 显示插屏广告
+     */
+    override fun showInterstitialAd(activity: Activity, success: AdResult.Success, adListener: AdListener) {
+
+        if (success.adObject is InterstitialAd){
+
+            val interstitialAd = success.adObject as InterstitialAd
+            interstitialAd.fullScreenContentCallback = object : FullScreenContentCallback() {
+                override fun onAdDismissedFullScreenContent() {
+                    super.onAdDismissedFullScreenContent()
+                    adListener.onAdClose()
+                }
+
+                override fun onAdFailedToShowFullScreenContent(p0: AdError) {
+                    super.onAdFailedToShowFullScreenContent(p0)
+                    adListener.onAdLoadedFail()
+                }
+
+                override fun onAdShowedFullScreenContent() {
+                    super.onAdShowedFullScreenContent()
+                    adListener.onAdShow(adUnitBean = success.adBean)
+                    AdManager.instance.globalListener?.invoke(success.adBean)
+                }
+            }
+            interstitialAd.setOnPaidEventListener {
+            }
+            interstitialAd.show(activity)
+        }else{
+            adListener.onAdLoadedFail()
+        }
+    }
+
+    /**
+     * 显示激励广告
+     */
+    override fun showRewardedAd(activity: Activity, success: AdResult.Success, adListener: AdListener) {
+        if (success.adObject is RewardedAd) {
+            val adObj = success.adObject as RewardedAd
+            adObj.fullScreenContentCallback = object : FullScreenContentCallback() {
+
+                override fun onAdDismissedFullScreenContent() {
+                    adListener.onAdClose()
+
+                }
+
+                override fun onAdFailedToShowFullScreenContent(p0: AdError) {
+                    super.onAdFailedToShowFullScreenContent(p0)
+                    adListener.onAdLoadedFail()
+                }
+
+                override fun onAdShowedFullScreenContent() {
+                    adListener.onAdShow(success.adBean)
+                    AdManager.instance.globalListener?.invoke(success.adBean)
+                }
+            }
+            adObj.setOnPaidEventListener {
+            }
+            adObj.show(activity) {
+                adListener.onAdRewarded()
+            }
+        }
+    }
+
+    override fun showRewardedInterstitialAd(activity: Activity, success: AdResult.Success, adListener: AdListener) {
+        if (success.adObject is RewardedInterstitialAd) {
+            val rewardedInterstitialAd = success.adObject as RewardedInterstitialAd
+            rewardedInterstitialAd.fullScreenContentCallback = object : FullScreenContentCallback() {
+                override fun onAdDismissedFullScreenContent() {
+                    adListener.onAdClose()
+                }
+
+                override fun onAdFailedToShowFullScreenContent(p0: AdError) {
+                    super.onAdFailedToShowFullScreenContent(p0)
+                    adListener.onAdLoadedFail()
+                }
+
+                override fun onAdShowedFullScreenContent() {
+                    adListener.onAdShow(success.adBean)
+                    AdManager.instance.globalListener?.invoke(success.adBean)
+                }
+            }
+            rewardedInterstitialAd.setOnPaidEventListener {
+            }
+            rewardedInterstitialAd.show(activity) {
+                adListener.onAdRewarded()
+            }
+        }
+    }
+
+    /**
+     * 显示全屏广告,根据广告类型进行判断需要展示的广告
+     * 包括:
+     * 插屏广告,激励视频广告,激励插屏广告
+     */
+    override fun showFullScreenAd(activity: Activity, success: AdResult.Success, adListener: AdListener) {
+        when(success.adBean.getAdType()){
+            AdType.INTERSTITIAL-> showInterstitialAd(activity, success, adListener)
+            AdType.REWARDED_VIDEO -> showRewardedAd(activity, success, adListener)
+            AdType.REWARDED_INTERSTITIAL -> showRewardedInterstitialAd(activity, success, adListener)
+            else-> adListener.onAdShowFail("无对应广告类型的广告进行展示 AdType:${success.adBean.adType}")
+        }
+    }
+
+    /**
+     * 显示开屏广告
+     */
+    override fun showSplashAd(activity: Activity, splashViewGroup: ViewGroup?, success: AdResult.Success, adListener: AdListener) {
+        val appOpenAd = success.adObject as? AppOpenAd
+        if (appOpenAd != null) {
+            val callback: FullScreenContentCallback = object : FullScreenContentCallback() {
+                override fun onAdDismissedFullScreenContent() {
+                    adListener.onAdSkip()
+                }
+
+                override fun onAdFailedToShowFullScreenContent(adError: AdError) {
+                    adListener.onAdSkip()
+                }
+
+                override fun onAdShowedFullScreenContent() {
+                    adListener.onAdShow(success.adBean)
+                    AdManager.instance.globalListener?.invoke(success.adBean)
+                }
+            }
+            appOpenAd.fullScreenContentCallback = callback
+            appOpenAd.setOnPaidEventListener {
+            }
+            appOpenAd.show(activity)
+        }
+    }
+
+
+
+
+}

+ 53 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdMobNativeRequestImpl.kt

@@ -0,0 +1,53 @@
+package com.composition.android.ad.admob.load.request
+
+import android.content.Context
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.IAdFormatRequest
+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.composition.android.ad.admob.util.printAdInfo
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:Admob原生广告加载实现
+ */
+class AdMobNativeRequestImpl : IAdFormatRequest{
+
+
+    override suspend fun load(context: Context, adUnitBean : AdUnitBean)  : AdResult{
+
+        return suspendCancellableCoroutine {
+            val listener: AdListener = object : AdListener() {
+                override fun onAdFailedToLoad(p0: LoadAdError) {
+                    printAdInfo(adUnitBean, p0.responseInfo)
+                    it.resume(AdResult.Fail(adUnitBean, msg = p0.message))
+                }
+            }
+            val adLoader = AdLoader.Builder(context, adUnitBean.adUnitId)
+                .forNativeAd {nativeAd->
+                    printAdInfo(adUnitBean, nativeAd.responseInfo)
+                    it.resume(
+                        AdResult.Success(
+                            nativeAd,
+                            adUnitBean,
+                            AdLoadCode.SUCCESS,
+                            "AdMob 原生广告加载成功"
+                        )
+                    )
+                }
+                .withAdListener(listener).build()
+            val adRequest = AdRequest.Builder().build()
+            adLoader.loadAd(adRequest)
+        }
+    }
+
+
+}

+ 57 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobAppOpenRequestImpl.kt

@@ -0,0 +1,57 @@
+package com.composition.android.ad.admob.load.request
+
+import android.content.Context
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.IAdFormatRequest
+import com.composition.android.lib.ad.util.adLogE
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.appopen.AppOpenAd
+import com.composition.android.ad.admob.util.printAdInfo
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:
+ */
+class AdmobAppOpenRequestImpl : IAdFormatRequest {
+
+    override suspend fun load(context: Context, adUnitBean: AdUnitBean): AdResult {
+        return suspendCancellableCoroutine {
+
+            val listener: AppOpenAd.AppOpenAdLoadCallback =
+                object : AppOpenAd.AppOpenAdLoadCallback() {
+                    override fun onAdFailedToLoad(p0: LoadAdError) {
+                        adLogE(AdManager.TAG, p0.message)
+                        it.resume(
+                            AdResult.Fail(
+                                adUnitBean,
+                                AdLoadCode.FAIL,
+                                p0.message
+                            )
+                        )
+                    }
+                    override fun onAdLoaded(p0: AppOpenAd) {
+                        printAdInfo(adUnitBean, p0.responseInfo)
+                        it.resume(
+                            AdResult.Success(
+                                p0,
+                                adUnitBean,
+                                AdLoadCode.SUCCESS,
+                                "Admob 开屏广告加载成功"
+                            )
+                        )
+                    }
+                }
+            val adRequest = com.google.android.gms.ads.AdRequest.Builder()
+                .build()
+            AppOpenAd.load(context, adUnitBean.adUnitId, adRequest, AppOpenAd.APP_OPEN_AD_ORIENTATION_PORTRAIT, listener)
+
+        }
+    }
+}

+ 72 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobBannerRequestImpl.kt

@@ -0,0 +1,72 @@
+package com.composition.android.ad.admob.load.request
+
+import android.content.Context
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.IAdFormatRequest
+import com.composition.android.lib.ad.util.adLogE
+import com.google.android.gms.ads.AdListener
+import com.google.android.gms.ads.AdView
+import com.google.android.gms.ads.LoadAdError
+import com.composition.android.ad.admob.bean.AdmobAdUnitBean
+import com.composition.android.ad.admob.util.getAdSize
+import com.composition.android.ad.admob.util.printAdInfo
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:Admob横幅广告加载实现
+ */
+class AdmobBannerRequestImpl : IAdFormatRequest {
+
+
+    override suspend fun load(context: Context, adUnitBean: AdUnitBean): AdResult {
+        return suspendCancellableCoroutine<AdResult> {
+            val adView = AdView(context).apply {
+                adUnitId = adUnitBean.adUnitId
+                setAdSize(if (adUnitBean is AdmobAdUnitBean){
+                    adUnitBean.adSize
+                }else{
+                    getAdSize(context)
+                })
+            }
+
+            val listener: AdListener = object : AdListener() {
+                override fun onAdLoaded() {
+                    super.onAdLoaded()
+                    printAdInfo(adUnitBean, adView.responseInfo)
+                    it.resume(
+                        AdResult.Success(
+                            adView,
+                            adUnitBean,
+                            AdLoadCode.SUCCESS,
+                            "AdMob横幅广告加载成功"
+                        )
+                    )
+                }
+
+                override fun onAdFailedToLoad(p0: LoadAdError) {
+                    super.onAdFailedToLoad(p0)
+                    adLogE(AdManager.TAG, p0.message)
+                    it.resume(
+                        AdResult.Fail(
+                            adUnitBean,
+                            AdLoadCode.FAIL,
+                            p0.message
+                        )
+                    )
+                }
+            }
+
+            adView.adListener = listener
+            val adRequest = com.google.android.gms.ads.AdRequest.Builder()
+                .build()
+            adView.loadAd(adRequest)
+        }
+    }
+}

+ 59 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobInterstitialRequestImpl.kt

@@ -0,0 +1,59 @@
+package com.composition.android.ad.admob.load.request
+
+import android.content.Context
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.IAdFormatRequest
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.interstitial.InterstitialAd
+import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
+import com.composition.android.ad.admob.util.printAdInfo
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * @classname:
+ * @author: LiuXiaoLong
+ * @date: 2022/9/5
+ * description: Admob插屏广告请求实现
+ */
+class AdmobInterstitialRequestImpl : IAdFormatRequest {
+
+    override suspend fun load(context: Context, adUnitBean: AdUnitBean): AdResult {
+
+        return suspendCancellableCoroutine<AdResult> {
+            val listener: InterstitialAdLoadCallback = object : InterstitialAdLoadCallback() {
+                override fun onAdLoaded(p0: InterstitialAd) {
+                    printAdInfo(adUnitBean, p0.responseInfo)
+                    it.resume(
+                        AdResult.Success(
+                            adObject = p0,
+                            adBean = adUnitBean,
+                            code = AdLoadCode.SUCCESS,
+                            msg = "AdMob 插屏广告加载成功"
+                        )
+                    )
+                }
+
+                override fun onAdFailedToLoad(p0: LoadAdError) {
+                    printAdInfo(adUnitBean, p0.responseInfo)
+                    it.resume(
+                        AdResult.Fail(
+                            adUnitBean,
+                            AdLoadCode.FAIL,
+                            p0.message
+                        )
+                    )
+                }
+            }
+
+            InterstitialAd.load(
+                context, adUnitBean.adUnitId,
+                com.google.android.gms.ads.AdRequest.Builder()
+                    .build(),
+                listener
+            )
+        }
+    }
+}

+ 59 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobRewardInterstitialRequestImpl.kt

@@ -0,0 +1,59 @@
+package com.composition.android.ad.admob.load.request
+
+import android.content.Context
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.IAdFormatRequest
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAd
+import com.google.android.gms.ads.rewardedinterstitial.RewardedInterstitialAdLoadCallback
+import com.composition.android.ad.admob.util.getFailedCodeInfo
+import com.composition.android.ad.admob.util.printAdInfo
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:Admob激励插屏广告加载实现类
+ */
+class AdmobRewardInterstitialRequestImpl : IAdFormatRequest {
+
+
+    override suspend fun load(context: Context, adUnitBean: AdUnitBean): AdResult {
+        return suspendCancellableCoroutine {
+            val listener: RewardedInterstitialAdLoadCallback =
+                object : RewardedInterstitialAdLoadCallback() {
+                    override fun onAdLoaded(p0: RewardedInterstitialAd) {
+                        printAdInfo(adUnitBean, p0.responseInfo)
+                        it.resume(
+                            AdResult.Success(
+                                adBean = adUnitBean,
+                                adObject = p0,
+                                code = AdLoadCode.SUCCESS,
+                                msg = "AdMob激励插屏加载成功"
+                            )
+                        )
+                    }
+
+                    override fun onAdFailedToLoad(p0: LoadAdError) {
+                        it.resume(
+                            AdResult.Fail(
+                                adUnitBean,
+                                AdLoadCode.FAIL,
+                                getFailedCodeInfo(p0.code)
+                            )
+                        )
+                    }
+                }
+
+            val adRequest = com.google.android.gms.ads.AdRequest.Builder()
+                .build()
+
+            RewardedInterstitialAd.load(context, adUnitBean.adUnitId, adRequest, listener)
+
+        }
+    }
+}

+ 49 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/request/AdmobRewardRequestImpl.kt

@@ -0,0 +1,49 @@
+package com.composition.android.ad.admob.load.request
+
+import android.content.Context
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.IAdFormatRequest
+import com.google.android.gms.ads.LoadAdError
+import com.google.android.gms.ads.rewarded.RewardedAd
+import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
+import com.composition.android.ad.admob.util.printAdInfo
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlin.coroutines.resume
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description: Admob激励广告实现
+ */
+class AdmobRewardRequestImpl : IAdFormatRequest {
+
+    override suspend fun load(context: Context, adUnitBean: AdUnitBean): AdResult {
+        return suspendCancellableCoroutine<AdResult> {
+            val listener: RewardedAdLoadCallback = object : RewardedAdLoadCallback() {
+
+                override fun onAdFailedToLoad(p0: LoadAdError) {
+                    printAdInfo(adUnitBean, p0.responseInfo)
+                    it.resume(AdResult.Fail(adUnitBean, AdLoadCode.FAIL, p0.message))
+                }
+
+                override fun onAdLoaded(p0: RewardedAd) {
+                    printAdInfo(adUnitBean, p0.responseInfo)
+                    it.resume(
+                        AdResult.Success(
+                            p0,
+                            adUnitBean, AdLoadCode.SUCCESS, "AdMob激励广告加载成功"
+                        )
+                    )
+                }
+            }
+            val adRequest = com.google.android.gms.ads.AdRequest.Builder()
+                .build()
+
+            RewardedAd.load(context, adUnitBean.adUnitId, adRequest, listener)
+        }
+    }
+
+}

+ 34 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/view/AdmobBannerView.kt

@@ -0,0 +1,34 @@
+package com.composition.android.ad.admob.load.view
+
+import android.annotation.SuppressLint
+import android.content.Context
+import com.composition.android.lib.ad.basic.BasicAdView
+import com.google.android.gms.ads.AdView
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:Admob横幅广告
+ */
+@SuppressLint("ViewConstructor")
+class AdmobBannerView(context: Context?) : BasicAdView<AdView>(context) {
+
+    override fun adContentView(): AdView? {
+        return adResult?.adObject as? AdView
+    }
+
+    override fun onResume() {
+        adView?.resume()
+    }
+
+    override fun onPause() {
+        adView?.pause()
+    }
+
+    override fun onDestroy() {
+        adView?.pause()
+        adView?.destroy()
+    }
+
+}

+ 120 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/load/view/AdmobNativeAdView.kt

@@ -0,0 +1,120 @@
+package com.composition.android.ad.admob.load.view
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.appcompat.widget.AppCompatTextView
+import androidx.core.view.isVisible
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.BasicAdView
+import com.composition.android.lib.ad.basic.NativeAdViewHolder
+import com.google.android.gms.ads.nativead.MediaView
+import com.google.android.gms.ads.nativead.NativeAd
+import com.google.android.gms.ads.nativead.NativeAdView
+import com.composition.android.ad.admob.R
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description: Admob原生广告View
+ */
+class AdmobNativeAdView(context: Context?) : BasicAdView<NativeAdView>(context) {
+
+
+    private var nativeAdViewHolder: NativeAdViewHolder? = null
+
+    fun setNativeAdViewHolder(nativeAdViewHolder: NativeAdViewHolder) {
+        this.nativeAdViewHolder = nativeAdViewHolder
+    }
+
+    override fun adContentView(): NativeAdView? {
+
+        if (null == adResult || nativeAdViewHolder == null) {
+            return null
+        }
+        return populateNativeAdView(context, success = adResult!!, nativeAdViewHolder = nativeAdViewHolder!!)
+
+    }
+
+
+    override fun onResume() {
+
+    }
+
+    override fun onPause() {
+
+    }
+
+    override fun onDestroy() {
+        adView?.destroy()
+    }
+
+
+    companion object{
+
+
+        fun populateNativeAdView(context: Context, success: AdResult.Success, nativeAdViewHolder: NativeAdViewHolder) : NativeAdView {
+            val nativeAdView = LayoutInflater.from(context).inflate(R.layout.layout_admob_native_ad_view_root,null) as NativeAdView
+            populateNativeAdView(context, success, nativeAdViewHolder, nativeAdView)
+            return nativeAdView
+        }
+
+
+        fun populateNativeAdView(context: Context, success: AdResult.Success, nativeAdViewHolder: NativeAdViewHolder, nativeAdView: NativeAdView) {
+
+            val nativeAd = success.adObject as NativeAd
+            val mediaView = LayoutInflater.from(context).inflate(R.layout.layout_admob_native_media_view, null) as MediaView
+
+            if (nativeAdView != nativeAdViewHolder.rootView && nativeAdViewHolder.rootView != null){
+               nativeAdView.addView(nativeAdViewHolder.rootView)
+            }
+            nativeAdViewHolder.titleView?.let {
+                it.text = nativeAd.headline
+                nativeAdView.headlineView = it
+            }
+
+            nativeAdViewHolder.contentMediaGroup?.let {
+                it.removeAllViews()
+                it.addView(mediaView)
+                nativeAdView.mediaView = mediaView
+
+            }
+            if (nativeAd.callToAction.isNullOrEmpty()) {
+                nativeAdViewHolder.callActionView?.isVisible = false
+            } else {
+                nativeAdViewHolder.callActionView?.isVisible = true
+                nativeAdViewHolder.callActionView?.let {
+                    it.text = nativeAd.callToAction
+                    nativeAdView.callToActionView = it
+                }
+            }
+            if (nativeAd.icon == null) {
+                nativeAdView.iconView?.visibility = View.GONE
+            } else {
+                nativeAdView.iconView?.visibility = View.VISIBLE
+                nativeAd.icon?.let { icon ->
+                    (nativeAdViewHolder.iconView as? AppCompatImageView)?.let {
+                        it.setImageDrawable(icon.drawable)
+                        nativeAdView.iconView = it
+                    }
+                }
+            }
+
+            if (nativeAd.body == null) {
+                nativeAdViewHolder.titleDescView?.visibility = View.INVISIBLE
+            } else {
+                nativeAdViewHolder.titleDescView?.visibility = View.VISIBLE
+                (nativeAdViewHolder.titleDescView as? AppCompatTextView)?.let {
+                    it.text = nativeAd.body
+                    nativeAdView.bodyView = it
+                }
+            }
+            nativeAdView.setNativeAd(nativeAd)
+        }
+
+    }
+}
+
+

+ 63 - 0
lib_ad_admob/src/main/java/com/composition/android/ad/admob/util/AdmobAdExpan.kt

@@ -0,0 +1,63 @@
+package com.composition.android.ad.admob.util
+
+import android.content.Context
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.util.adLog
+import com.convenient.android.common.extension.getDensity
+import com.convenient.android.common.extension.getScreenWidth
+import com.google.android.gms.ads.AdRequest
+import com.google.android.gms.ads.AdSize
+import com.google.android.gms.ads.ResponseInfo
+
+
+/**
+ * 获取错误代码对应的信息
+ */
+internal fun getFailedCodeInfo(code: Int): String {
+    return when (code) {
+        AdRequest.ERROR_CODE_NO_FILL ->
+            "admob 没有填充广告 code:${AdRequest.ERROR_CODE_NO_FILL}"
+        AdRequest.ERROR_CODE_INTERNAL_ERROR ->
+            "admob 内部发生了一些事情; 例如,从广告服务器收到无效响应 code:${AdRequest.ERROR_CODE_INTERNAL_ERROR}"
+        AdRequest.ERROR_CODE_NETWORK_ERROR ->
+            "admob 由于网络连接,广告请求未成功。code:${AdRequest.ERROR_CODE_NETWORK_ERROR}"
+        AdRequest.ERROR_CODE_INVALID_REQUEST ->
+            "admob 广告请求无效; 例如,广告单元ID不正确。。code:${AdRequest.ERROR_CODE_INVALID_REQUEST}"
+        else -> "其他错误 code: $code"
+    }
+}
+
+internal fun printAdInfo(adBean: AdUnitBean, responseInfo: ResponseInfo?) {
+    adLog(
+        AdManager.TAG,
+        "-------------------------------------------------------------------"
+    )
+
+    adLog(AdManager.TAG, "广告加载成功")
+    adLog(AdManager.TAG, "广告商:${adBean.advertisersName}")
+    adLog(AdManager.TAG, "广告位:${adBean.adSlotName}")
+    adLog(AdManager.TAG, "广告ID:${adBean.adUnitId}")
+    adLog(AdManager.TAG, "广告类型:${adBean.adType}")
+    adLog(AdManager.TAG, "广告中介提供商:${responseInfo?.mediationAdapterClassName}")
+
+    adLog(
+        AdManager.TAG,
+        "-------------------------------------------------------------------"
+    )
+}
+
+
+/**
+ * 横幅广告尺寸
+ */
+internal fun getAdSize(context: Context): AdSize {
+
+    // Step 2 - Determine the screen width (less decorations) to use for the ad width.
+    val widthPixels: Int = context.getScreenWidth()
+    val density: Float =context.getDensity()
+    val adWidth = (widthPixels / density).toInt()
+
+    // Step 3 - Get adaptive ad size and return for setting on the ad view.
+    return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(context, adWidth)
+}

+ 8 - 0
lib_ad_admob/src/main/res/layout/layout_admob_native_ad_view_root.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.gms.ads.nativead.NativeAdView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/native_ad_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+
+</com.google.android.gms.ads.nativead.NativeAdView>

+ 6 - 0
lib_ad_admob/src/main/res/layout/layout_admob_native_media_view.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.gms.ads.nativead.MediaView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</com.google.android.gms.ads.nativead.MediaView>

+ 17 - 0
lib_ad_admob/src/test/java/com/composition/android/ad/admob/ExampleUnitTest.kt

@@ -0,0 +1,17 @@
+package com.composition.android.ad.admob
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        assertEquals(4, 2 + 2)
+    }
+}

+ 1 - 0
lib_ad_core/.gitignore

@@ -0,0 +1 @@
+/build

+ 51 - 0
lib_ad_core/build.gradle

@@ -0,0 +1,51 @@
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+}
+
+android {
+    compileSdk 32
+
+    defaultConfig {
+        minSdk 21
+        targetSdk 32
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    api project(':lib_common')
+
+    implementation 'androidx.core:core-ktx:1.8.0'
+    implementation 'androidx.appcompat:appcompat:1.5.0'
+    implementation 'com.google.android.material:material:1.6.1'
+
+
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+    /*Kotlin*/
+    api "androidx.core:core-ktx:1.8.0"
+    api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+    api 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3'
+    api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3'
+
+
+}

+ 0 - 0
lib_ad_core/consumer-rules.pro


+ 21 - 0
lib_ad_core/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
lib_ad_core/src/androidTest/java/com/composition/android/lib/ad/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.composition.android.lib.ad
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.composition.android.lib.ad.test", appContext.packageName)
+    }
+}

+ 5 - 0
lib_ad_core/src/main/AndroidManifest.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.composition.android.lib.ad">
+
+</manifest>

+ 149 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/AdLoad.kt

@@ -0,0 +1,149 @@
+package com.composition.android.lib.ad
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.composition.android.lib.ad.basic.NativeAdViewHolder
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.impl.Api
+import com.composition.android.lib.ad.impl.NormalAdListener
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告加载类,用于处理广告的加载、展示
+ */
+object AdLoad {
+
+    /**
+     * 每个广告位最大缓存数量
+     */
+    var adSlotMaxCacheCount = 1
+
+    /**
+     * 缓存集合
+     */
+    private var cacheList: MutableList<AdResult.Success> = mutableListOf()
+
+    fun addToCache(success: AdResult.Success){
+        cacheList.add(success)
+    }
+
+    suspend fun preloadAd(context: Context, vararg adSlotNames: String) {
+        val beans = AdUnitConfigManager.instance.getAdUnitBySlotName(adSlotNames.toList())
+        preloadAd(context, beans)
+    }
+
+
+    suspend fun preloadAd(context: Context, adUnitBeans: List<AdUnitBean>) {
+        loadAd(context, adUnitBeans)
+            .collect {
+                if (it is AdResult.Success) {
+                    cacheList.add(it)
+                }
+            }
+    }
+
+    suspend fun hasCacheAd(adSlotName: String): Boolean {
+        return cacheList.any {
+            it.adBean.adSlotName.equals(adSlotName)
+        }
+    }
+
+    suspend fun getCacheAd(adUnitBean: AdUnitBean): AdResult.Success? {
+        val ad = cacheList.find {
+            it.adBean.equals(adUnitBean)
+        }
+        if (ad != null) {
+            cacheList.remove(ad)
+        }
+        return ad
+    }
+
+
+    /**
+     * 根据广告位置名称加载广告
+     * 需要先进行配置广告单元信息
+     * 可传入多个广告位,会按顺序加载多个广告
+     * @param context 上下文,推荐用activity context, 部分中介广告加载必须要activity context
+     * @param adSlotName 广告位名称
+     * @see AdUnitBean
+     * @see AdUnitConfigManager
+     * @return Flow<AdResult> 返回协程Flow流
+     */
+    suspend fun loadAd(context: Context, vararg adSlotNames: String): Flow<AdResult> {
+        val beans = AdUnitConfigManager.instance.getAdUnitBySlotName(adSlotNames.toList())
+        return loadAd(context = context, adUnitBeans = beans)
+    }
+
+
+    /**
+     * 根据广告单元信息加载广告
+     * @param context 上下文,推荐用activity context, 部分中介广告加载必须要activity context
+     *
+     */
+    suspend fun loadAd(context: Context, adUnitBeans: List<AdUnitBean>): Flow<AdResult> {
+        return Api.instance.load(context, adUnitBeans)
+    }
+
+
+    /**
+     * 获取Banner广告View
+     */
+    fun getBannerAdView(context: Context, success: AdResult.Success): View? {
+        return Api.instance.getBannerView(context, success)
+    }
+
+
+    fun getNativeAdView(context: Context, adResult : AdResult.Success, viewHolder: NativeAdViewHolder) : View?{
+        return Api.instance.getNativeAdView(context, adResult, viewHolder)
+    }
+
+    fun populateNativeAdView(context: Context, adResult: AdResult.Success, viewHolder: NativeAdViewHolder, adRootView : View){
+        Api.instance.populateNativeAdView(context, adResult, viewHolder, adRootView)
+    }
+
+    /**
+     * 显示全屏广告,包含插屏、激励视频、激励插屏,根据传入的数据自行判断
+     */
+    fun showFullScreenAd(
+        activity: Activity,
+        success: AdResult.Success,
+        adListener: NormalAdListener.() -> Unit
+    ) {
+        Api.instance.showFullScreenAd(activity, success, adListener = NormalAdListener().also(adListener))
+    }
+
+
+    fun showInterstitialAd(
+        activity: Activity,
+        success: AdResult.Success,
+        adListener: NormalAdListener.() -> Unit
+    ) {
+        Api.instance.showInterstitialAd(activity = activity, success = success, adListener = NormalAdListener().also(adListener))
+    }
+
+
+    fun showRewardedAd(
+        activity: Activity,
+        success: AdResult.Success,
+        adListener: NormalAdListener.() -> Unit
+    ) {
+        Api.instance.showRewardedAd(activity = activity, success = success, adListener = NormalAdListener().also(adListener))
+    }
+
+    fun showSplash(
+        activity: Activity,
+        splashViewGroup: ViewGroup?,
+        success: AdResult.Success,
+        adListener: NormalAdListener.() -> Unit
+    ) {
+        Api.instance.showSplash(activity = activity, splashViewGroup = splashViewGroup, success = success, adListener = NormalAdListener().also(adListener))
+    }
+
+
+}

+ 78 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/AdManager.kt

@@ -0,0 +1,78 @@
+package com.composition.android.lib.ad
+
+import android.content.Context
+import com.composition.android.lib.ad.basic.Advertisers
+import com.composition.android.lib.ad.bean.OnAdShow
+import com.composition.android.lib.ad.impl.NormalAdListener
+import com.composition.android.lib.ad.interfaces.AdListener
+import com.composition.android.lib.ad.interfaces.AdLoader
+import com.composition.android.lib.ad.interfaces.Initialize
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告管理类
+ */
+class AdManager {
+
+
+    companion object {
+        val instance by lazy {
+            AdManager()
+        }
+
+        const val TAG = "广告组件"
+    }
+
+    /**
+     * 上下文,使用application
+     */
+    lateinit var context: Context
+
+    /**
+     * 是否开启日志
+     */
+    var logEnable = false
+
+    /**
+     * 广告全局开关
+     */
+    var enableAd: Boolean = true
+
+    internal var adLoaderLists = mutableListOf<Pair<Advertisers, AdLoader>>()
+
+    var globalListener: OnAdShow? = null
+
+    /**
+     * 广告初始化
+     */
+    fun init(applicationContext: Context, logEnable: Boolean = false) {
+        this.context = applicationContext
+        this.logEnable = logEnable
+    }
+
+    /**
+     * 注册广告加载器
+     */
+    fun registerAdLoader(vararg adLoader: Pair<Advertisers, AdLoader>) {
+        adLoaderLists.clear()
+        adLoaderLists.addAll(adLoader)
+    }
+
+
+    fun addGlobalAdShowListener(adListener: OnAdShow) {
+        globalListener = adListener
+    }
+
+
+    /**
+     * 初始化广告商SDK
+     */
+    fun initAdvertisersSDK(context: Context, vararg advertisersSdk: Initialize) {
+        for (initialize in advertisersSdk) {
+            initialize.init(context)
+        }
+    }
+
+}

+ 46 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/AdUnitConfigManager.kt

@@ -0,0 +1,46 @@
+package com.composition.android.lib.ad
+
+import com.composition.android.lib.ad.bean.AdUnitBean
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告单元配置管理类
+ */
+class AdUnitConfigManager {
+
+    companion object {
+
+        val instance by lazy {
+            AdUnitConfigManager()
+        }
+
+    }
+
+    /**
+     * 配置的广告单元集合
+     */
+    private var adUnitLists: MutableList<AdUnitBean> = mutableListOf<AdUnitBean>()
+
+    /**
+     * 设置广告单元集
+     */
+    fun setAdUnits(adUnits: List<AdUnitBean>) {
+        adUnitLists.clear()
+        adUnitLists.addAll(adUnits)
+    }
+
+
+    /**
+     * 根据广告位名称获取广告单元配置
+     */
+    fun getAdUnitBySlotName(adSlotNames: List<String>): List<AdUnitBean> {
+        val list = adUnitLists.filter {
+            adSlotNames.contains(it.adSlotName)
+        }
+        return list
+    }
+
+
+}

+ 39 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/AdLoadCode.kt

@@ -0,0 +1,39 @@
+package com.composition.android.lib.ad.basic
+
+import androidx.annotation.IntDef
+
+
+/**
+ * @classname:
+ * @author: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告加载code
+ */
+@IntDef(
+    AdLoadCode.SUCCESS,
+    AdLoadCode.CANCEL,
+    AdLoadCode.FAIL
+)
+@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
+annotation class AdLoadCode {
+
+    companion object {
+
+        /**
+         * 广告加载成
+         */
+        const val SUCCESS = 0
+
+        /**
+         * 广告取消
+         */
+        const val CANCEL = 1
+
+        /**
+         * 广告加载失败
+         */
+        const val FAIL = 2
+
+
+    }
+}

+ 59 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/AdResult.kt

@@ -0,0 +1,59 @@
+package com.composition.android.lib.ad.basic
+
+import com.composition.android.lib.ad.bean.AdUnitBean
+import java.io.Serializable
+
+
+/**
+ * 请求广告结果
+ * @param adBean 请求广告的数据类包含广告位置、id等信息
+ * @param code 请求的结果
+ * @param msg 相关信息
+ */
+sealed class AdResult(
+    open var adBean: AdUnitBean,
+    @AdLoadCode open var code: Int,
+    open var msg: String
+) : Serializable {
+
+    /**
+     * 广告请求成功
+     */
+    class Success(
+        var adObject: Any?,
+        override var adBean: AdUnitBean,
+        @AdLoadCode override var code: Int = AdLoadCode.SUCCESS,
+        override var msg: String
+    ) : AdResult(adBean, code, msg)
+
+    /**
+     * 广告请求失败
+     */
+    class Fail(
+        override var adBean: AdUnitBean,
+        @AdLoadCode override var code: Int = AdLoadCode.FAIL,
+        override var msg: String
+    ) : AdResult(adBean, code, msg)
+
+    /**
+     * 广告请求取消
+     */
+    class Cancel(
+        override var adBean: AdUnitBean,
+        @AdLoadCode override var code: Int = AdLoadCode.CANCEL,
+        override var msg: String
+    ) : AdResult(adBean, code, msg)
+
+
+    /**
+     * 空,未知
+     */
+    class Empty(
+        override var adBean: AdUnitBean,
+        @AdLoadCode override var code: Int = AdLoadCode.FAIL,
+        override var msg: String = ""
+    ) : AdResult(adBean, code, msg)
+
+
+}
+

+ 24 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/AdType.kt

@@ -0,0 +1,24 @@
+package com.composition.android.lib.ad.basic
+
+/**
+ * @classname:
+ * @author: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告类型
+ */
+enum class AdType(name : String) {
+
+    APP_OPEN("AppOpen"),
+
+    BANNER("Banner"),
+
+    NATIVE("Native"),
+
+    INTERSTITIAL("Interstitial"),
+
+    REWARDED_VIDEO("RewardedVideo"),
+
+    REWARDED_INTERSTITIAL("RewardedInterstitial"),
+
+    UNKNOWN("Unknown")
+}

+ 41 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/Advertisers.kt

@@ -0,0 +1,41 @@
+package com.composition.android.lib.ad.basic
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告提供商
+ */
+enum class Advertisers {
+
+    /**
+     * 谷歌Admob广告
+     */
+    Admob,
+
+    /**
+     * AppLovin广告
+     */
+    AppLovin,
+
+    /**
+     * AppLovinMax广告
+     */
+    AppLovinMax,
+
+    /**
+     * 穿山甲广告
+     */
+    CSJ,
+
+    /**
+     * 自定义广告
+     */
+    CUSTOM,
+
+    /**
+     * 未知
+     */
+    UNKNOWN,
+
+}

+ 87 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/BasicAdView.kt

@@ -0,0 +1,87 @@
+package com.composition.android.lib.ad.basic
+
+import android.content.Context
+import android.util.AttributeSet
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.lifecycle.*
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.util.adLog
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/6
+ * description:
+ */
+abstract class BasicAdView<T : View> : LinearLayout, DefaultLifecycleObserver {
+
+    var adResult: AdResult.Success? = null
+
+    var adView: T? = null
+
+    constructor(context: Context?) : super(context)
+
+    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
+    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
+
+    /**
+     * 对广告布局进行渲染
+     */
+    fun render(adResult: AdResult.Success) {
+        this.adResult = adResult
+        adView = adContentView()
+        adView?.let {
+            if (it.parent != null) {
+                (it.parent as? ViewGroup)?.removeView(it)
+            }
+            removeAllViews()
+            addView(it)
+        }
+    }
+
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        adLog(AdManager.TAG, javaClass.simpleName + " 绑定到Window")
+
+        findViewTreeLifecycleOwner()?.lifecycle?.addObserver(this)
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        adLog(AdManager.TAG, javaClass.simpleName + " 从Window移除")
+        onDestroy()
+    }
+
+
+    override fun onResume(owner: LifecycleOwner) {
+        super.onResume(owner)
+        adLog(AdManager.TAG, javaClass.simpleName + " onResume")
+        onResume()
+    }
+
+    override fun onPause(owner: LifecycleOwner) {
+        super.onPause(owner)
+        adLog(AdManager.TAG, javaClass.simpleName + " onPause")
+        onPause()
+    }
+
+    override fun onDestroy(owner: LifecycleOwner) {
+        super.onDestroy(owner)
+        adLog(AdManager.TAG, javaClass.simpleName + " onDestroy")
+        onDestroy()
+    }
+
+    abstract fun adContentView(): T?
+
+    abstract fun onResume()
+
+    abstract fun onPause()
+
+    abstract fun onDestroy()
+
+}

+ 42 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/NativeAdViewHolder.kt

@@ -0,0 +1,42 @@
+package com.composition.android.lib.ad.basic
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.widget.AppCompatButton
+import androidx.appcompat.widget.AppCompatImageView
+import androidx.appcompat.widget.AppCompatTextView
+import com.composition.android.lib.ad.interfaces.AdListener
+import com.composition.android.lib.ad.interfaces.ImageLoader
+
+/**
+ * @classname:
+ * @author: LiuXiaoLong
+ * @date: 2022/9/6
+ * description: 原生广告View配置类,指定相关视图
+ */
+class NativeAdViewHolder(
+    var titleView : AppCompatTextView? = null,
+    var titleDescView : View? = null,
+    var iconView : View? = null,
+    var rootView : View? = null,
+    var contentView : View? = null,
+    var contentMediaGroup : ViewGroup? = null,
+    var callActionView : AppCompatButton? = null,
+    var ratingBar : View? = null,
+    var advertiserView : View? = null,
+    var adListener : AdListener? = null,
+    var imageLoader : ImageLoader<AppCompatImageView>? = null
+) {
+
+
+    companion object{
+
+        fun buildNativeAdViewHolder(viewHolder : NativeAdViewHolder.()-> Unit) : NativeAdViewHolder{
+            return NativeAdViewHolder().also(viewHolder)
+        }
+
+    }
+
+}
+
+fun buildAdNativeViewHolder(viewHolder: NativeAdViewHolder.() -> Unit) = NativeAdViewHolder.buildNativeAdViewHolder(viewHolder)

+ 13 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/NormalImageLoader.kt

@@ -0,0 +1,13 @@
+package com.composition.android.lib.ad.basic
+
+import android.content.Context
+import android.widget.ImageView
+import com.composition.android.lib.ad.interfaces.ImageLoader
+
+abstract class NormalImageLoader : ImageLoader<ImageView> {
+
+    override fun createImageView(context: Context?): ImageView {
+        return ImageView(context)
+    }
+
+}

+ 17 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/basic/strategy/IStrategy.kt

@@ -0,0 +1,17 @@
+package com.composition.android.lib.ad.basic.strategy
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告加载、展示策略接口,
+ */
+interface IStrategy {
+
+    /**
+     * 该方法用于控制是否需要展示广告
+     */
+    fun allow() : Boolean
+
+
+}

+ 66 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/bean/AdUnitBean.kt

@@ -0,0 +1,66 @@
+package com.composition.android.lib.ad.bean
+
+import com.composition.android.lib.ad.basic.AdType
+import com.composition.android.lib.ad.basic.Advertisers
+import com.composition.android.lib.ad.impl.NormalStrategy
+import com.composition.android.lib.ad.basic.strategy.IStrategy
+
+
+
+
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告单元信息
+ * 一个广告位,包含使用的广告位名称,广告商、广告类型、广告ID、加载配置、展示配置
+ */
+open class AdUnitBean(
+    var adSlotName : String,
+    var advertisersName: String,
+    var adType: String,
+    var adUnitId: String,
+    var loadStrategy : IStrategy = NormalStrategy(),
+    var requestAdConfig: AdUnitLoadConfig = AdUnitLoadConfig()
+) {
+
+    companion object{
+        fun emptyAdUnitBean() : AdUnitBean = AdUnitBean(adSlotName = "", advertisersName = "", adType = "", adUnitId = "")
+    }
+
+    /**
+     * 获取广告商枚举类型
+     */
+    fun getAdvertisers(): Advertisers {
+        return try {
+            Advertisers.valueOf(advertisersName)
+        } catch (e: Exception) {
+            Advertisers.UNKNOWN
+        }
+    }
+
+    override fun toString(): String {
+        return "广告位:${adSlotName}\n广告商:${advertisersName}\n广告类型:${adType}\n广告id:${adUnitId}"
+    }
+
+    override fun equals(other: Any?): Boolean {
+        return when(other){
+            !is AdUnitBean-> false
+            else-> this === other || this.adSlotName.equals(other.adSlotName) && this.advertisersName.equals(other.advertisersName) && this.adType.equals(other.adType) && this.adUnitId.equals(other.adUnitId)
+        }
+    }
+
+    fun getAdType() : AdType{
+        return try {
+            AdType.valueOf(adType)
+        }catch (e : Exception){
+            AdType.UNKNOWN
+        }
+    }
+
+    fun isAvailable() : Boolean{
+        return adUnitId.isNotEmpty() && advertisersName.isNotEmpty() && adType.isNotEmpty() && adSlotName.isNotEmpty()
+    }
+
+}

+ 19 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/bean/AdUnitLoadConfig.kt

@@ -0,0 +1,19 @@
+package com.composition.android.lib.ad.bean
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告单元加载配置
+ */
+data class AdUnitLoadConfig(
+    /**
+     * 广告加载重试次数
+     */
+    var adLoadRetryCount : Int = 1,
+    /**
+     * 广告加载超时时间,单位:毫秒
+     */
+    var adLoadTimeOut : Long = 10000
+
+)

+ 8 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/bean/Alias.kt

@@ -0,0 +1,8 @@
+package com.composition.android.lib.ad.bean
+
+
+typealias OnAdShow = (AdUnitBean) -> Unit
+
+typealias OnNormal = () -> Unit
+
+typealias OnAdShowFail = (msg : String) -> Unit

+ 7 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/exception/AdLoadFailException.kt

@@ -0,0 +1,7 @@
+package com.composition.android.lib.ad.exception
+
+/**
+ * 广告加载失败异常,如果广告加载失败,抛出该异常,flow中判断改异常进行重试加载
+ * @author xiaolong.liu
+ */
+class AdLoadFailException: Exception()

+ 27 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/factory/AdLoaderFactory.kt

@@ -0,0 +1,27 @@
+package com.composition.android.lib.ad.factory
+
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.impl.UnknownAdLoader
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.AdLoader
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:
+ */
+object AdLoaderFactory {
+
+
+    fun getAdLoader(adUnitBean: AdUnitBean) : AdLoader{
+        return try {
+            AdManager.instance.adLoaderLists.find {
+                it.first == adUnitBean.getAdvertisers()
+            }?.second?: UnknownAdLoader()
+        }catch (e : Exception){
+            UnknownAdLoader()
+        }
+    }
+
+}

+ 14 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/factory/AdRequestFactory.kt

@@ -0,0 +1,14 @@
+package com.composition.android.lib.ad.factory
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:
+ */
+object AdRequestFactory {
+
+
+
+
+}

+ 131 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/impl/Api.kt

@@ -0,0 +1,131 @@
+package com.composition.android.lib.ad.impl
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.NativeAdViewHolder
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.factory.AdLoaderFactory
+import com.composition.android.lib.ad.interfaces.AdLoader
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description:
+ */
+class Api {
+
+    companion object{
+        val instance by lazy {
+            Api()
+        }
+    }
+
+    private fun getAdLoader(adUnitBean: AdUnitBean) : AdLoader{
+        return AdLoaderFactory.getAdLoader(adUnitBean)
+    }
+
+
+    fun load(context: Context, adUnitBeans : List<AdUnitBean>) : Flow<AdResult>{
+        return flow {
+            if (adUnitBeans.isNullOrEmpty()){
+                emit(AdResult.Fail(adBean = AdUnitBean.emptyAdUnitBean(), msg = "AdUnitBeans 为空"))
+                return@flow
+            }
+            for (adUnitBean in adUnitBeans) {
+                if (adUnitBean.loadStrategy.allow().not()) {
+                    emit(AdResult.Fail(adUnitBean, AdLoadCode.FAIL, msg = "策略未通过"))
+                    continue
+                }
+                val cacheAd = AdLoad.getCacheAd(adUnitBean)
+                if (cacheAd != null){
+                    emit(cacheAd)
+                    continue
+                }
+                val result = getAdLoader(adUnitBean).load(context, adUnitBean)
+                emit(result)
+            }
+        }
+    }
+
+
+    fun getBannerView(context: Context, adResult: AdResult.Success) : View?{
+        if (adResult.adBean.loadStrategy.allow().not()){
+            return null
+        }
+        return getAdLoader(adResult.adBean).getBannerView(context, adResult)
+    }
+
+    fun getNativeAdView(context: Context, adResult : AdResult.Success, viewHolder: NativeAdViewHolder) : View?{
+        if (adResult.adBean.loadStrategy.allow().not()){
+            return null
+        }
+        return getAdLoader(adResult.adBean).getNativeAdView(context, adResult, viewHolder)
+    }
+
+
+    fun populateNativeAdView(context: Context, adResult: AdResult.Success, viewHolder: NativeAdViewHolder, adRootView : View){
+        getAdLoader(adResult.adBean).populateNativeAdView(context, adResult, viewHolder, adRootView)
+    }
+
+
+    fun showInterstitialAd(
+        activity: Activity,
+        success: AdResult.Success,
+        adListener: NormalAdListener
+    ){
+        if (success.adBean.loadStrategy.allow().not()){
+            adListener.onAdShowFail("展示策略未通过")
+            return
+        }
+        getAdLoader(success.adBean).showInterstitialAd(activity, success, adListener)
+    }
+
+
+    fun showRewardedAd(
+        activity: Activity,
+        success: AdResult.Success,
+        adListener: NormalAdListener
+    ){
+        if (success.adBean.loadStrategy.allow().not()){
+            adListener.onAdShowFail("展示策略未通过")
+            return
+        }
+        getAdLoader(success.adBean).showRewardedAd(activity, success, adListener)
+    }
+
+
+    fun showSplash(
+        activity: Activity,
+        splashViewGroup: ViewGroup?,
+        success: AdResult.Success,
+        adListener: NormalAdListener
+    ){
+        if (success.adBean.loadStrategy.allow().not()){
+            adListener.onAdShowFail("展示策略未通过")
+            return
+        }
+        getAdLoader(success.adBean).showSplashAd(activity, splashViewGroup, success, adListener)
+    }
+
+    fun showFullScreenAd(
+        activity: Activity,
+        success: AdResult.Success,
+        adListener: NormalAdListener
+    ){
+        if (success.adBean.loadStrategy.allow().not()){
+            adListener.onAdShowFail("展示策略未通过")
+            return
+        }
+        getAdLoader(success.adBean).showFullScreenAd(activity, success, adListener)
+    }
+
+
+}

+ 108 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/impl/NormalAdListener.kt

@@ -0,0 +1,108 @@
+package com.composition.android.lib.ad.impl
+
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.bean.OnAdShow
+import com.composition.android.lib.ad.bean.OnAdShowFail
+import com.composition.android.lib.ad.bean.OnNormal
+import com.composition.android.lib.ad.interfaces.AdListener
+
+/**
+ * @classname:
+ * @author: LiuXiaoLong
+ * @date: 2022/9/5
+ * description: 广告监听类默认实现
+ */
+open class NormalAdListener : AdListener {
+
+    private var onAdShow: OnAdShow? = null
+    private var onAdClose : OnNormal? = null
+    private var onAdClick : OnNormal? = null
+    private var onAdLoaded : OnNormal? = null
+    private var onAdRewarded : OnNormal? = null
+    private var onAdLoadFail : OnNormal? = null
+    private var onAdDisLike : OnNormal? = null
+    private var onAdSkip: OnNormal? = null
+    private var onAdTimeOver : OnNormal? = null
+    private var onAdShowFail : OnAdShowFail? = null
+
+    override fun onAdShow(adUnitBean: AdUnitBean) {
+        onAdShow?.invoke(adUnitBean)
+    }
+
+    override fun onAdClose() {
+        onAdClose?.invoke()
+    }
+
+    override fun onAdClick() {
+        onAdClick?.invoke()
+    }
+
+    override fun onAdLoaded() {
+        onAdLoaded?.invoke()
+    }
+
+    override fun onAdRewarded() {
+        onAdRewarded?.invoke()
+    }
+
+    override fun onAdLoadedFail() {
+        onAdLoadFail?.invoke()
+    }
+
+    override fun onAdDisLike() {
+        onAdDisLike?.invoke()
+    }
+
+    override fun onAdSkip() {
+        onAdSkip?.invoke()
+    }
+
+    override fun onTimeOver() {
+        onAdTimeOver?.invoke()
+    }
+
+    override fun onAdShowFail(msg: String) {
+        onAdShowFail?.invoke(msg)
+    }
+
+    fun onAdShow(onAdShow: OnAdShow){
+        this.onAdShow = onAdShow
+    }
+
+    fun onAdClose(adClose : OnNormal){
+        this.onAdClose = adClose
+    }
+
+    fun onAdClick(onAdClick : OnNormal){
+        this.onAdClick = onAdClick
+    }
+
+    fun onAdLoaded(onAdLoaded : OnNormal){
+        this.onAdLoaded = onAdLoaded
+    }
+
+    fun onAdRewarded(onAdRewarded : OnNormal){
+        this.onAdRewarded = onAdRewarded
+    }
+
+    fun onAdLoadedFail(onAdLoadedFail : OnNormal){
+        this.onAdLoadFail = onAdLoadedFail
+    }
+
+    fun onAdDisLike(onAdDisLike : OnNormal){
+        this.onAdDisLike = onAdDisLike
+    }
+
+    fun onAdSkip(onAdSkip : OnNormal){
+        this.onAdSkip = onAdSkip
+    }
+
+    fun onTimeOver(onTimeOver : OnNormal){
+        this.onAdTimeOver = onTimeOver
+    }
+
+    fun onAdShowFail(onAdShowFail: OnAdShowFail){
+        this.onAdShowFail = onAdShowFail
+    }
+
+}

+ 24 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/impl/NormalStrategy.kt

@@ -0,0 +1,24 @@
+package com.composition.android.lib.ad.impl
+
+import com.composition.android.lib.ad.AdManager
+import com.composition.android.lib.ad.basic.strategy.IStrategy
+import com.convenient.android.common.extension.isNetworkAvailable
+
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:默认的广告展示策略,默认一直可以加载展示
+ */
+class NormalStrategy : IStrategy {
+
+    override fun allow(): Boolean {
+        if (AdManager.instance.enableAd.not()) return false
+        if (AdManager.instance.context.isNetworkAvailable().not()) return false
+        return true
+    }
+
+
+
+}

+ 64 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/impl/UnknownAdLoader.kt

@@ -0,0 +1,64 @@
+package com.composition.android.lib.ad.impl
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.BasicAdView
+import com.composition.android.lib.ad.basic.NativeAdViewHolder
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.interfaces.AdListener
+import com.composition.android.lib.ad.interfaces.AdLoader
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * @classname:
+ * @author: LiuXiaoLong
+ * @date: 2022/9/5
+ * description: 位置的广告商加载器接口默认实现, 配置的Advertisers 填错了
+ */
+class UnknownAdLoader : AdLoader {
+
+
+    override suspend fun load(context: Context, adUnitBean: AdUnitBean): AdResult {
+        return AdResult.Fail(adUnitBean, AdLoadCode.FAIL, "未知的广告商,请检查Advertisers,当前:${adUnitBean.advertisersName}")
+    }
+
+    override fun getBannerView(context: Context, adResult: AdResult.Success): BasicAdView<*>? {
+        return null
+    }
+
+    override fun getNativeAdView(context: Context, adResult: AdResult.Success, viewHolder: NativeAdViewHolder): View? {
+        return null
+    }
+
+    override fun populateNativeAdView(context: Context, adResult: AdResult.Success, viewHolder: NativeAdViewHolder, adRootView: View) {
+
+    }
+
+    override fun showInterstitialAd(activity: Activity, success: AdResult.Success, adListener: AdListener) {
+        adListener.onAdShowFail("未知的广告加载器")
+
+    }
+
+    override fun showRewardedAd(activity: Activity, success: AdResult.Success, adListener: AdListener) {
+        adListener.onAdShowFail("未知的广告加载器")
+
+    }
+
+    override fun showSplashAd(activity: Activity, splashViewGroup: ViewGroup?, success: AdResult.Success, adListener: AdListener) {
+        adListener.onAdShowFail("未知的广告加载器")
+
+    }
+
+    override fun showFullScreenAd(activity: Activity, success: AdResult.Success, adListener: AdListener) {
+        adListener.onAdShowFail("未知的广告加载器")
+    }
+
+    override fun showRewardedInterstitialAd(activity: Activity, success: AdResult.Success, adListener: AdListener) {
+        adListener.onAdShowFail("未知的广告加载器")
+    }
+}

+ 61 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/AdListener.kt

@@ -0,0 +1,61 @@
+package com.composition.android.lib.ad.interfaces
+
+import com.composition.android.lib.ad.bean.AdUnitBean
+
+
+/**
+ * @classname:
+ * @author: LiuXiaoLong
+ * @date: 2022/9/5
+ * description: 广告监听接口
+ */
+interface AdListener {
+
+    /**
+     * 广告展示
+     */
+    fun onAdShow(adUnitBean: AdUnitBean)
+
+    /**
+     * 广告关闭
+     */
+    fun onAdClose()
+
+    /**
+     * 广告点击
+     */
+    fun onAdClick()
+
+    /**
+     * 广告加载结束
+     */
+    fun onAdLoaded()
+
+    /**
+     * 下发激励广告
+     */
+    fun onAdRewarded()
+
+    /**
+     * 广告加载失败
+     */
+    fun onAdLoadedFail()
+
+    /**
+     * 点击不喜欢广告
+     */
+    fun onAdDisLike()
+
+    /**
+     * 广告跳过
+     */
+    fun onAdSkip()
+
+    /**
+     * 广告超时
+     */
+    fun onTimeOver()
+
+    fun onAdShowFail(msg : String)
+
+}

+ 82 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/AdLoader.kt

@@ -0,0 +1,82 @@
+package com.composition.android.lib.ad.interfaces
+
+import android.app.Activity
+import android.content.Context
+import android.view.View
+import android.view.ViewGroup
+import com.composition.android.lib.ad.basic.AdLoadCode
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.BasicAdView
+import com.composition.android.lib.ad.basic.NativeAdViewHolder
+import com.composition.android.lib.ad.bean.AdUnitBean
+import com.composition.android.lib.ad.exception.AdLoadFailException
+import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.withTimeoutOrNull
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/1
+ * description:广告商加载广告接口
+ */
+interface AdLoader {
+
+
+    suspend fun load(context: Context, adUnitBean : AdUnitBean) : AdResult
+
+
+    /**
+     * 广告根据策略配置进行加载
+     * 可配置:
+     * 加载超时时间、重试次数
+     *
+     */
+    suspend fun loadByStrategy(context: Context, adUnitBean: AdUnitBean, iAdFormatRequest: IAdFormatRequest?) : AdResult{
+        if (iAdFormatRequest == null){
+            return AdResult.Fail(adUnitBean, msg = "广告类型解析出错,请检查adType")
+        }
+        return withTimeoutOrNull(adUnitBean.requestAdConfig.adLoadTimeOut){
+
+            flow<AdResult> {
+                emit(iAdFormatRequest.load(context, adUnitBean))
+            }.onEach {
+                if (it is AdResult.Fail){
+                    throw AdLoadFailException()
+                }
+            }.retry(adUnitBean.requestAdConfig.adLoadRetryCount.toLong()){
+                it is AdLoadFailException
+            }.firstOrNull()
+        }?:AdResult.Fail(adUnitBean, AdLoadCode.FAIL, "加载失败")
+    }
+
+    /**
+     * 获取BannerAd View
+     */
+    fun getBannerView(context: Context, adResult: AdResult.Success) : BasicAdView<*>?
+
+    /**
+     * 获取原生广告View
+     *
+     */
+    fun getNativeAdView(context: Context, adResult: AdResult.Success, viewHolder: NativeAdViewHolder) :View?
+
+    /**
+     * 填充原生广告,不会返回View
+     * @param adResult 请求成功的广告实例
+     * @param viewHolder 要填充的广告布局内容
+     * @param adRootView 广告的根布局,例如Admob: NativeAdView
+     * 此方法适用于在外部直接写好所有整体的布局 NativeAdView中包含了具体布局,比如在RecyclerView中,这样布局已经绘制,只要填充内容就好
+     */
+    fun populateNativeAdView(context: Context, adResult: AdResult.Success, viewHolder: NativeAdViewHolder, adRootView : View)
+
+    fun showInterstitialAd(activity: Activity, success: AdResult.Success,adListener: AdListener)
+
+    fun showRewardedAd(activity: Activity, success: AdResult.Success, adListener: AdListener)
+
+    fun showRewardedInterstitialAd(activity: Activity, success: AdResult.Success, adListener: AdListener)
+
+    fun showSplashAd(activity: Activity, splashViewGroup : ViewGroup?, success: AdResult.Success, adListener: AdListener)
+
+    fun showFullScreenAd(activity: Activity, success: AdResult.Success, adListener: AdListener)
+
+}

+ 17 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/IAdFormatRequest.kt

@@ -0,0 +1,17 @@
+package com.composition.android.lib.ad.interfaces
+
+import android.content.Context
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.bean.AdUnitBean
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/5
+ * description: 各类样式广告请求接口
+ */
+interface IAdFormatRequest {
+
+    suspend fun load(context: Context, adUnitBean: AdUnitBean) : AdResult
+
+}

+ 16 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/ImageLoader.kt

@@ -0,0 +1,16 @@
+package com.composition.android.lib.ad.interfaces
+
+import android.content.Context
+import android.view.View
+import java.io.Serializable
+
+/**
+ * 原生广告加载图片接口
+ */
+interface ImageLoader<T : View> : Serializable {
+
+    fun displayImage(context: Context?, path: Any?, imageView: T)
+
+    fun createImageView(context: Context?): T
+
+}

+ 15 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/interfaces/Initialize.kt

@@ -0,0 +1,15 @@
+package com.composition.android.lib.ad.interfaces
+
+import android.content.Context
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/13
+ * description:
+ */
+interface Initialize {
+
+    fun init(context: Context)
+
+}

+ 22 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/util/AdLog.kt

@@ -0,0 +1,22 @@
+package com.composition.android.lib.ad.util
+
+import android.util.Log
+import com.composition.android.lib.ad.AdManager
+
+
+fun adLog(tag : String, msg : String?){
+    if (AdManager.instance.logEnable){
+        msg?.let {
+            Log.i(tag, msg)
+        }
+    }
+}
+
+
+fun adLogE(tag : String, msg : String?){
+    if (AdManager.instance.logEnable){
+        msg?.let {
+            Log.e(tag, msg)
+        }
+    }
+}

+ 84 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/widget/BannerAdView.kt

@@ -0,0 +1,84 @@
+package com.composition.android.lib.ad.widget
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.R
+import com.composition.android.lib.ad.basic.AdResult
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @author: LiuXiaoLong
+ * @date: 2022/9/7
+ * description: 横幅广告View
+ * 可以直接写入在布局中,设置ad_slot_name(广告位名称)后,自动进行加载展示横幅广告
+ * 广告跟随生命周期自动销毁
+ */
+class BannerAdView : LinearLayout{
+
+    /**
+     * 广告位名称
+     */
+    private var adSlotName : String = ""
+
+    init {
+        orientation = VERTICAL
+    }
+
+    constructor(context: Context?) : super(context)
+
+    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){
+        init(attrs)
+    }
+    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+
+    private fun init(attrs: AttributeSet?){
+        val typedArray: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.BannerAdView)
+        //获取到配置的广告名称
+        adSlotName = typedArray.getString(R.styleable.BannerAdView_ad_slot_name)?:""
+        typedArray.recycle()
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        loadAd()
+    }
+
+    private fun loadAd(){
+        findViewTreeLifecycleOwner()?.lifecycleScope?.launch {
+            val adResult = AdLoad.loadAd(context, adSlotName)
+                .firstOrNull()
+            if (adResult is AdResult.Success){
+                AdLoad.getBannerAdView(context, adResult)?.let {
+                    removeAllViews()
+                    addView(it)
+                }
+            }
+        }
+    }
+
+    /**
+     * 更新广告
+     */
+    fun updateAd(adSlotName : String){
+        this.adSlotName = adSlotName
+        loadAd()
+    }
+
+    /**
+     * 移除广告
+     */
+    fun removeAd(){
+        removeAllViews()
+        isVisible = false
+    }
+
+
+}

+ 188 - 0
lib_ad_core/src/main/java/com/composition/android/lib/ad/widget/NativeAdView.kt

@@ -0,0 +1,188 @@
+package com.composition.android.lib.ad.widget
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.composition.android.lib.ad.AdLoad
+import com.composition.android.lib.ad.AdUnitConfigManager
+import com.composition.android.lib.ad.R
+import com.composition.android.lib.ad.basic.AdResult
+import com.composition.android.lib.ad.basic.Advertisers
+import com.composition.android.lib.ad.basic.NativeAdViewHolder
+import com.composition.android.lib.ad.basic.buildAdNativeViewHolder
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.launch
+
+/**
+ * @classname:
+ * @auther: LiuXiaoLong
+ * @date: 2022/9/7
+ * description: 自定义的原生广告View, 通过在xml中的配置,view中进行加载广告并展示
+ *
+ * <NativeAdView
+ *  //对应的NativeAdViewHolder view配置,设置view的id..还有其他的,具体查看attr.xml/NativeAdView
+ *  app:ad_view_title="@id/tv_ad_title"
+ *  app:ad_view_title_desc="..."
+ *
+ *  //不同广告厂商的原生广告根布局,在每个库中都会默认定义一个,或者自行添加
+ *  admob : layout_admob_native_ad_view_root.xml   gms.NativeAdView
+ *
+ *  app:native_admob_advertisers_root_view="@layout/layout_admob_native_ad_view_root"
+ *  app:native_csj_advertisers_root_view="xxx"
+ *  app:native_app_lovin_advertisers_root_view="xxx"
+ *  app:native_custom_advertisers_root_view="xxx"
+ *
+ * >
+ *
+ *     //具体的原生广告view布局
+ *     <TextView
+ *      android:id="@+id/tv_ad_title"
+ *     />
+ *
+ * </NativeAdView>
+ *
+ *
+ *
+ */
+class NativeAdView : LinearLayout {
+
+    /**
+     * 广告位名称
+     */
+    private var adSlotName: String = ""
+
+    private var viewHolder: NativeAdViewHolder? = null
+
+    /**
+     * NativeAdViewHolder 中view的id -------->
+     */
+    private var adTitleId: Int = 0
+
+    private var adTitleDescId: Int = 0
+
+    private var adIconId = 0
+
+    private var adMediaContentId = 0
+
+    private var adCallToActionId = 0
+
+    private var adRatingBarId = 0
+
+    private var adRootView: View? = null
+    /**
+     * <-----------
+     */
+
+    private var autoPopulate = true
+
+    constructor(context: Context?) : super(context)
+    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
+        init(attrs)
+    }
+
+    private fun init(attrs: AttributeSet?) {
+        val typedArray: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.NativeAdView)
+        //获取到配置的广告名称
+        adSlotName = typedArray.getString(R.styleable.NativeAdView_native_ad_slot_name) ?: ""
+        val adUnitBean = AdUnitConfigManager.instance.getAdUnitBySlotName(adSlotNames = listOf(adSlotName)).getOrNull(0)
+
+        when (adUnitBean?.getAdvertisers()) {
+            Advertisers.Admob -> attachedAdvertisersNativeAdViewToRoot(typedArray, R.styleable.NativeAdView_native_admob_advertisers_root_view)
+            Advertisers.CSJ -> attachedAdvertisersNativeAdViewToRoot(typedArray, R.styleable.NativeAdView_native_csj_advertisers_root_view)
+            Advertisers.AppLovin-> attachedAdvertisersNativeAdViewToRoot(typedArray, R.styleable.NativeAdView_native_app_lovin_advertisers_root_view)
+            Advertisers.AppLovinMax -> attachedAdvertisersNativeAdViewToRoot(typedArray, R.styleable.NativeAdView_native_app_lovin_max_advertisers_root_view)
+            Advertisers.CUSTOM -> attachedAdvertisersNativeAdViewToRoot(typedArray, R.styleable.NativeAdView_native_custom_advertisers_root_view)
+            else -> {}
+        }
+
+        adTitleId = typedArray.getResourceId(R.styleable.NativeAdView_ad_view_title, 0)
+        adTitleDescId = typedArray.getResourceId(R.styleable.NativeAdView_ad_view_title_desc, 0)
+        adIconId = typedArray.getResourceId(R.styleable.NativeAdView_ad_view_icon, 0)
+        adMediaContentId = typedArray.getResourceId(R.styleable.NativeAdView_ad_view_media_content_group, 0)
+        adCallToActionId = typedArray.getResourceId(R.styleable.NativeAdView_ad_view_call_to_action, 0)
+        adRatingBarId = typedArray.getResourceId(R.styleable.NativeAdView_ad_view_rating_bar, 0)
+
+        autoPopulate = typedArray.getBoolean(R.styleable.NativeAdView_ad_auto_populate, true)
+        typedArray.recycle()
+
+    }
+
+    override fun addView(child: View?, params: ViewGroup.LayoutParams?) {
+        if (childCount != 0 && getChildAt(0) is ViewGroup){
+            //把写的原生广告子布局添加到对应的广告商ViewGroup中
+            (getChildAt(0) as ViewGroup).addView(child, params)
+        }else{
+            //这里第一次会在 attachedAdvertisersNativeAdViewToRoot 中调用,把广告商的原生广告根布局添加进来
+            super.addView(child, params)
+        }
+    }
+
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        initViewHolder()
+        if (autoPopulate){
+            loadAd()
+        }
+    }
+
+    private fun initViewHolder(){
+        if (viewHolder == null){
+            viewHolder = buildAdNativeViewHolder {
+                this.titleView = findViewById(adTitleId)
+                this.titleDescView = findViewById(adTitleDescId)
+                this.iconView = findViewById(adIconId)
+                this.callActionView = findViewById(adCallToActionId)
+                this.ratingBar = findViewById(adRatingBarId)
+                this.contentMediaGroup = findViewById(adMediaContentId)
+            }
+        }
+    }
+
+    private fun attachedAdvertisersNativeAdViewToRoot(typedArray: TypedArray, id: Int) {
+        return typedArray.getResourceId(id, -1).let {
+            if (it != -1) {
+                LayoutInflater.from(context).inflate(it, this, true)
+                adRootView = getChildAt(0)
+            }
+        }
+    }
+
+    private fun loadAd(){
+        findViewTreeLifecycleOwner()?.lifecycleScope?.launch {
+            val adResult = AdLoad.loadAd(context, adSlotName).firstOrNull()
+            if (adResult != null && adResult is AdResult.Success && viewHolder != null && adRootView != null){
+                AdLoad.populateNativeAdView(context, adResult, viewHolder!!, adRootView!!)
+                isVisible = true
+            }
+        }
+    }
+
+    /**
+     * 可以不使用View中的自动加载,在这里进行自行填充,非常适用于在RecyclerView中使用
+     */
+    fun populateNativeAd(adResult: AdResult.Success){
+        initViewHolder()
+        if (viewHolder!= null && adRootView != null){
+            AdLoad.populateNativeAdView(context, adResult, viewHolder!!, adRootView!!)
+        }
+    }
+
+
+    fun refreshAd(){
+        loadAd()
+    }
+
+    fun removeAd(){
+        isVisible = false
+    }
+
+}

+ 32 - 0
lib_ad_core/src/main/res/values/attr.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+
+    <declare-styleable name="BannerAdView">
+        <attr name="ad_slot_name" format="string"/>
+    </declare-styleable>
+
+
+    <declare-styleable name="NativeAdView">
+        <!-- NativeAdViewHolder -->
+        <attr name="ad_view_title" format="reference"/>
+        <attr name="ad_view_title_desc" format="reference"/>
+        <attr name="ad_view_icon" format="reference"/>
+        <attr name="ad_view_media_content_group" format="reference"/>
+        <attr name="ad_view_call_to_action" format="reference"/>
+        <attr name="ad_view_rating_bar" format="reference"/>
+        <!-- 这里指定广告商提供的原生广告根节点view, 各个广告库中已经写了默认的-->
+        <!-- lib_ad_admob/res/layout/layout_admob_native_ad_view_root -->
+
+        <attr name="native_csj_advertisers_root_view" format="reference"/>
+        <attr name="native_admob_advertisers_root_view" format="reference"/>
+        <attr name="native_app_lovin_advertisers_root_view" format="reference"/>
+        <attr name="native_app_lovin_max_advertisers_root_view" format="reference"/>
+        <attr name="native_custom_advertisers_root_view" format="reference"/>
+        <attr name="native_ad_slot_name" format="string"/>
+
+        <attr name="ad_auto_populate" format="boolean"/>
+
+    </declare-styleable>
+
+</resources>

+ 22 - 0
lib_ad_core/src/test/java/com/composition/android/lib/ad/ExampleUnitTest.kt

@@ -0,0 +1,22 @@
+package com.composition.android.lib.ad
+
+import com.composition.android.lib.ad.basic.Advertisers
+
+import org.junit.Test
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+    @Test
+    fun addition_isCorrect() {
+        val advertisers = try {
+            Advertisers.valueOf("sdf")
+        }catch (e : Exception){
+            Advertisers.UNKNOWN
+        }
+       println(advertisers.name)
+    }
+}

+ 1 - 0
lib_ad_csj/.gitignore

@@ -0,0 +1 @@
+/build

+ 40 - 0
lib_ad_csj/build.gradle

@@ -0,0 +1,40 @@
+plugins {
+    id 'com.android.library'
+    id 'org.jetbrains.kotlin.android'
+}
+
+android {
+    compileSdk 32
+
+    defaultConfig {
+        minSdk 21
+        targetSdk 32
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles "consumer-rules.pro"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+}
+
+dependencies {
+
+    implementation 'androidx.core:core-ktx:1.7.0'
+    implementation 'androidx.appcompat:appcompat:1.5.1'
+    implementation 'com.google.android.material:material:1.6.1'
+    testImplementation 'junit:junit:4.13.2'
+    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+}

+ 0 - 0
lib_ad_csj/consumer-rules.pro


+ 21 - 0
lib_ad_csj/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 24 - 0
lib_ad_csj/src/androidTest/java/com/composition/android/ad/csj/ExampleInstrumentedTest.kt

@@ -0,0 +1,24 @@
+package com.composition.android.ad.csj
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+    @Test
+    fun useAppContext() {
+        // Context of the app under test.
+        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+        assertEquals("com.composition.android.ad.csj.test", appContext.packageName)
+    }
+}

+ 5 - 0
lib_ad_csj/src/main/AndroidManifest.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.composition.android.ad.csj">
+
+</manifest>

+ 0 - 0
lib_ad_csj/src/test/java/com/composition/android/ad/csj/ExampleUnitTest.kt


Some files were not shown because too many files changed in this diff