Android — 多进程中使用广告SDK问题与一种解决方案

大部分App都会接入广告SDK来变现流量增加收入,如果App中使用了多进程,对广告SDK会有什么影响呢?

多进程中使用广告SDK问题

使用AdMob SDK做了一个简单的示例demo进行演示,如下:

  • 在主进程和子进程中同时初始化MobileAds,不同进程分别使用对应的实例。

在主进程和子进程都初始化SDK,初始化时不会报错,但是在子进程中播放插屏和激励广告时播放失败,如下图:

PS: 通过support询问了AdMob和Max(AppLovin)这两个比较主流的广告SDK的支持团队,都建议仅在主进程初始化一次SDK,避免出现异常。

  • 仅在主进程初始化MobileAds。

在主进程中初始化SDK,子进程通过广播发送消息到主进程,主进程调用显示广告,显示广告失败,错误信息如下图:

另外Banner广告需要添加在Activity的rootView中,两个进程间无法获取对方的Activity实例,所以Banner广告也无法正常使用。

实现多进程使用广告SDK

根据官方团队的建议,最好是仅在主进程初始化,所以主要解决仅在主进程初始化Sdk时遇到的问题。我想到的解决方案其实也很简单,在显示子进程页面的同时,在其上覆盖一个主进程的透明页面专门用于播放广告,两个页面之间通过广播来传递事件。

透明广告页

透明Activity可以在AndroidManifest中通过改变主题来实现。

  • 透明主题
ruby 复制代码
<resources xmlns:tools="http://schemas.android.com/tools">

    <style name="TransparentAdActivity" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>
    
</resources>
  • AndroidManifest中配置主题。
xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        ......
        >

        ......

        <activity
            android:name=".tripartite.admob.multipleprocess.TransparentAdActivity"
            android:theme="@style/TransparentAdActivity" />
    </application>
</manifest>

加载显示广告以及进程间通信

  • 广告SDK控制类

控制SDK的初始化,广告的加载、显示、隐藏和销毁。

kotlin 复制代码
object AdvertiseSdkController {

    private var tempActivity: Activity? = null

    var advertiseStatusListener: AdvertiseStatusListener? = null

    fun initAdMobSdk(activity: Activity) {
        tempActivity = activity
        MobileAds.initialize(activity.applicationContext) {
            loadInterstitialAd()
            loadRewardedVideoAd()
            createBannerAdView(activity)
        }
    }

    //<editor-folder desc = "interstitial ad">

    private var interstitialAd: InterstitialAd? = null

    // 插屏广告加载状态的回调
    private val interstitialAdLoadCallback = object : InterstitialAdLoadCallback() {
        override fun onAdLoaded(interstitialAd: InterstitialAd) {
            super.onAdLoaded(interstitialAd)
            // 加载成功
            this@AdvertiseSdkController.interstitialAd = interstitialAd
            // 设置广告事件回调
            this@AdvertiseSdkController.interstitialAd?.fullScreenContentCallback = interstitialAdCallback
            advertiseStatusListener?.availableChange(ADVERTISE_TYPE_INTERSTITIAL, true)
        }

        override fun onAdFailedToLoad(loadAdError: LoadAdError) {
            super.onAdFailedToLoad(loadAdError)
            // 加载失败
            advertiseStatusListener?.availableChange(ADVERTISE_TYPE_INTERSTITIAL, false)
        }
    }

    // 插屏广告相关事件回调
    private val interstitialAdCallback = object : FullScreenContentCallback() {
        override fun onAdImpression() {
            super.onAdImpression()
            // 被记录为展示成功时调用
        }

        override fun onAdShowedFullScreenContent() {
            super.onAdShowedFullScreenContent()
            // 显示时调用
        }

        override fun onAdClicked() {
            super.onAdClicked()
            // 被点击时调用
        }

        override fun onAdDismissedFullScreenContent() {
            super.onAdDismissedFullScreenContent()
            // 隐藏时调用,此时销毁当前的插屏广告对象,重新加载插屏广告
            interstitialAd = null
            loadInterstitialAd()
        }

        override fun onAdFailedToShowFullScreenContent(adError: AdError) {
            super.onAdFailedToShowFullScreenContent(adError)
            // 展示失败时调用,此时销毁当前的插屏广告对象,重新加载插屏广告
            interstitialAd = null
            loadInterstitialAd()
            advertiseStatusListener?.showFailure("Interstitial Ad show failure due to ${adError.message}")
        }
    }

    private fun loadInterstitialAd() {
        tempActivity?.let { InterstitialAd.load(it.applicationContext, "ca-app-pub-3940256099942544/1033173712", AdRequest.Builder().build(), interstitialAdLoadCallback) }
    }

    fun showInterstitialAd() {
        tempActivity?.let { interstitialAd?.show(it) }
    }
    //</editor-folder>

    //<editor-folder desc = "rewarded video ad">

    private var rewardedAd: RewardedAd? = null

    private val rewardedAdLoadCallback = object : RewardedAdLoadCallback() {
        override fun onAdLoaded(rewardedAd: RewardedAd) {
            super.onAdLoaded(rewardedAd)
            // 加载成功
            this@AdvertiseSdkController.rewardedAd = rewardedAd
            // 设置广告事件回调
            this@AdvertiseSdkController.rewardedAd?.fullScreenContentCallback = rewardedVideoAdCallback
            advertiseStatusListener?.availableChange(ADVERTISE_TYPE_REWARDED, true)
        }

        override fun onAdFailedToLoad(loadAdError: LoadAdError) {
            super.onAdFailedToLoad(loadAdError)
            // 加载失败
            advertiseStatusListener?.availableChange(ADVERTISE_TYPE_REWARDED, false)
        }
    }
    private val rewardedVideoAdCallback = object : FullScreenContentCallback() {
        override fun onAdImpression() {
            super.onAdImpression()
            // 被记录为展示成功时调用
        }

        override fun onAdShowedFullScreenContent() {
            super.onAdShowedFullScreenContent()
            // 显示时调用
        }

        override fun onAdClicked() {
            super.onAdClicked()
            // 被点击时调用
        }

        override fun onAdDismissedFullScreenContent() {
            super.onAdDismissedFullScreenContent()
            // 隐藏时调用,此时销毁当前的激励视频广告对象,重新加载激励视频广告
            rewardedAd = null
            loadRewardedVideoAd()
        }

        override fun onAdFailedToShowFullScreenContent(adError: AdError) {
            super.onAdFailedToShowFullScreenContent(adError)
            // 展示失败时调用,此时销毁当前的激励视频广告对象,重新加载激励视频广告
            rewardedAd = null
            loadRewardedVideoAd()
            advertiseStatusListener?.showFailure("Rewarded Ad show failure due to ${adError.message}")
        }

    }
    private val rewardedVideoAdEarnedCallback = OnUserEarnedRewardListener {
        // 用户获得奖励
        // 奖励的类型
        val type = it.type
        // 奖励的金额
        val amount = it.amount
    }


    private fun loadRewardedVideoAd() {
        tempActivity?.let { RewardedAd.load(it.applicationContext, "ca-app-pub-3940256099942544/5224354917", AdRequest.Builder().build(), rewardedAdLoadCallback) }
    }

    fun showRewardedVideo() {
        tempActivity?.let { rewardedAd?.show(it, rewardedVideoAdEarnedCallback) }
    }
    //</editor-folder>

    // <editor-folder desc = "banner ad">

    private var bannerAdView: AdView? = null

    private val bannerListener = object : AdListener() {
        override fun onAdLoaded() {
            super.onAdLoaded()
            // 广告加载成功
            advertiseStatusListener?.availableChange(ADVERTISE_TYPE_BANNER, true)
        }

        override fun onAdFailedToLoad(loadAdError: LoadAdError) {
            super.onAdFailedToLoad(loadAdError)
            // 广告加载失败
            advertiseStatusListener?.availableChange(ADVERTISE_TYPE_BANNER, false)
        }

        override fun onAdImpression() {
            super.onAdImpression()
            // 被记录为展示成功时调用
        }

        override fun onAdClicked() {
            super.onAdClicked()
            // 被点击时调用
        }

        override fun onAdOpened() {
            super.onAdOpened()
            // 广告落地页打开时调用
        }

        override fun onAdClosed() {
            super.onAdClosed()
            // 广告落地页关闭时调用
        }
    }

    private fun createBannerAdView(activity: Activity) {
        // 获取页面的根布局
        val rootView = activity.findViewById<FrameLayout>(android.R.id.content)
        bannerAdView = AdView(activity)
        bannerAdView?.run {
            // 设置Banner的尺寸
            setAdSize(AdSize.BANNER)
            adUnitId = "ca-app-pub-3940256099942544/6300978111"
            // 设置广告事件回调
            adListener = bannerListener
            val bannerViewLayoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
            // 设置显示在页面的底部中间
            bannerViewLayoutParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
            layoutParams = bannerViewLayoutParams
            alpha = 0f
            // 把 Banner Ad 添加到根布局
            rootView.addView(this)
            //加载广告
            loadAd(AdRequest.Builder().build())
        }
    }

    fun showBanner() {
        bannerAdView?.alpha = 1f
    }

    fun hideBanner() {
        bannerAdView?.alpha = 0f
    }

    private fun removeViewInParent(targetView: View) {
        try {
            (targetView.parent as? ViewGroup)?.removeView(targetView)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun releaseBanner() {
        bannerAdView?.let {
            removeViewInParent(it)
            it.destroy()
        }
        bannerAdView = null
    }
    //</editor-folder>

    fun changeActivity(currentActivity: Activity) {
        if (tempActivity != currentActivity) {
            tempActivity = currentActivity
            releaseBanner()
            createBannerAdView(currentActivity)
        }
    }
}
  • 主进程示例页面

初始化广告,提供打开子进程入口。

kotlin 复制代码
class MainProcessActivity : AppCompatActivity() {

    private lateinit var layoutMultipleProcessAdvertiseActivityBinding: LayoutMultipleProcessAdvertiseActivityBinding

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        layoutMultipleProcessAdvertiseActivityBinding = LayoutMultipleProcessAdvertiseActivityBinding.inflate(layoutInflater).also {
            setContentView(it.root)
            it.includeTitle.tvTitle.text = "MainProcessExampleActivity"
            it.btnOpenSubprocess.run {
                visibility = View.VISIBLE
                setOnClickListener { startActivity(Intent(this@MainProcessActivity, SubProcessActivity::class.java)) }
            }
            it.btnShowInterstitialAd.setOnClickListener { AdvertiseSdkController.showInterstitialAd() }
            it.btnShowRewardedAd.setOnClickListener { AdvertiseSdkController.showRewardedVideo() }
            it.btnShowBannerAd.setOnClickListener { AdvertiseSdkController.showBanner() }
            it.btnHideBannerAd.setOnClickListener { AdvertiseSdkController.hideBanner() }
        }
        AdvertiseSdkController.advertiseStatusListener = object : AdvertiseStatusListener {
            override fun availableChange(adType: Int, enable: Boolean) {
                when (adType) {
                    ADVERTISE_TYPE_INTERSTITIAL -> layoutMultipleProcessAdvertiseActivityBinding.btnShowInterstitialAd.isEnabled = enable
                    ADVERTISE_TYPE_REWARDED -> layoutMultipleProcessAdvertiseActivityBinding.btnShowRewardedAd.isEnabled = enable
                    ADVERTISE_TYPE_BANNER -> {
                        layoutMultipleProcessAdvertiseActivityBinding.btnShowBannerAd.isEnabled = enable
                        layoutMultipleProcessAdvertiseActivityBinding.btnHideBannerAd.isEnabled = enable
                    }
                }
            }

            override fun showFailure(message: String) {
                showSnakeBar(message)
            }
        }
        AdvertiseSdkController.initAdMobSdk(this)
    }

    override fun onResume() {
        super.onResume()
        AdvertiseSdkController.changeActivity(this)
    }

    private fun showSnakeBar(message: String) {
        runOnUiThread {
            Snackbar.make(layoutMultipleProcessAdvertiseActivityBinding.root, message, Snackbar.LENGTH_SHORT).show()
        }
    }
}
  • 主进程透明广告页面

向子进程发送触摸事件,接收子进程调用广告事件并显示对应广告。

kotlin 复制代码
class TransparentAdActivity : AppCompatActivity() {

    private val subprocessEventReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            intent?.run {
                if (action == SUBPROCESS_ACTION) {
                    when (intent.getStringExtra(EVENT_NAME)) {
                        EVENT_SHOW_INTERSTITIAL -> AdvertiseSdkController.showInterstitialAd()
                        EVENT_SHOW_REWARDED -> AdvertiseSdkController.showRewardedVideo()
                        EVENT_SHOW_BANNER -> AdvertiseSdkController.showBanner()
                        EVENT_HIDE_BANNER -> AdvertiseSdkController.hideBanner()
                        EVENT_BACK -> finish()
                    }
                }
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.layout_transparent_ad_activity)
        ContextCompat.registerReceiver(this, subprocessEventReceiver, IntentFilter(SUBPROCESS_ACTION), ContextCompat.RECEIVER_NOT_EXPORTED)
        AdvertiseSdkController.changeActivity(this)
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        ev?.let {
            sendBroadcast(Intent(MAIN_ACTION).apply {
                setPackage(packageName)
                putExtra(EVENT_NAME, EVENT_MOTION)
                putExtra(EVENT_PARAMS, it)
            })
        }
        return super.dispatchTouchEvent(ev)
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(subprocessEventReceiver)
    }
}
  • 子进程页面

接收主进程触摸事件,向主进程发送调用广告事件。

kotlin 复制代码
const val SUBPROCESS_ACTION = "SUBPROCESS_EVENT"
const val MAIN_ACTION = "SUBPROCESS_EVENT"

const val EVENT_NAME = "eventName"
const val EVENT_PARAMS = "eventParams"

const val EVENT_MOTION = "motion"
const val EVENT_SHOW_INTERSTITIAL = "showInterstitial"
const val EVENT_SHOW_REWARDED = "showRewarded"
const val EVENT_SHOW_BANNER = "showBanner"
const val EVENT_HIDE_BANNER = "hideBanner"
const val EVENT_BACK = "back"

class SubProcessActivity : AppCompatActivity() {

    private lateinit var layoutMultipleProcessAdvertiseActivityBinding: LayoutMultipleProcessAdvertiseActivityBinding

    private val mainProcessEventReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            intent?.run {
                if (action == MAIN_ACTION) {
                    when (intent.getStringExtra(EVENT_NAME)) {
                        EVENT_MOTION -> {
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                                getParcelableExtra(EVENT_PARAMS, MotionEvent::class.java)
                            } else {
                                getParcelableExtra(EVENT_PARAMS) as? MotionEvent
                            }?.let {
                                dispatchTouchEvent(it)
                            }
                        }
                    }
                }
            }
        }
    }

    @SuppressLint("SetTextI18n")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        layoutMultipleProcessAdvertiseActivityBinding = LayoutMultipleProcessAdvertiseActivityBinding.inflate(layoutInflater).also {
            setContentView(it.root)
            it.includeTitle.tvTitle.text = "SubprocessExampleActivity"
            it.btnBack.setOnClickListener {
                sendEventToMainProcess(EVENT_BACK)
                finish()
            }
            it.btnShowInterstitialAd.isEnabled = true
            it.btnShowInterstitialAd.setOnClickListener { sendEventToMainProcess(EVENT_SHOW_INTERSTITIAL) }
            it.btnShowRewardedAd.isEnabled = true
            it.btnShowRewardedAd.setOnClickListener { sendEventToMainProcess(EVENT_SHOW_REWARDED) }
            it.btnShowBannerAd.isEnabled = true
            it.btnShowBannerAd.setOnClickListener { sendEventToMainProcess(EVENT_SHOW_BANNER) }
            it.btnHideBannerAd.isEnabled = true
            it.btnHideBannerAd.setOnClickListener { sendEventToMainProcess(EVENT_HIDE_BANNER) }
        }
        ContextCompat.registerReceiver(this, mainProcessEventReceiver, IntentFilter(MAIN_ACTION), ContextCompat.RECEIVER_NOT_EXPORTED)
        startActivity(Intent(this, TransparentAdActivity::class.java))
    }

    private fun sendEventToMainProcess(eventName: String) {
        sendBroadcast(Intent(SUBPROCESS_ACTION).apply {
            setPackage(packageName)
            putExtra(EVENT_NAME, eventName)
        })
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(mainProcessEventReceiver)
    }
}

效果演示与示例代码

最终效果如下图:

演示代码已在示例Demo中添加。

ExampleDemo github

ExampleDemo gitee

相关推荐
Dnelic-1 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen4 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年11 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿14 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神15 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛15 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法15 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter17 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快18 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl18 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5