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

相关推荐
恋猫de小郭1 小时前
Android Studio 正式版 10 周年回顾,承载 Androider 的峥嵘十年
android·ide·android studio
aaaweiaaaaaa4 小时前
php的使用及 phpstorm环境部署
android·web安全·网络安全·php·storm
工程师老罗6 小时前
Android记事本App设计开发项目实战教程2025最新版Android Studio
android
pengyu10 小时前
系统化掌握 Dart 编程之异常处理(二):从防御到艺术的进阶之路
android·flutter·dart
消失的旧时光-194310 小时前
android Camera 的进化
android
基哥的奋斗历程11 小时前
Openfga 授权模型搭建
android·adb
Pakho love1 天前
Linux:文件与fd(被打开的文件)
android·linux·c语言·c++
勿忘初心911 天前
Android车机DIY开发之软件篇(九) NXP AutomotiveOS编译
android·arm开发·经验分享·嵌入式硬件·mcu
lingllllove1 天前
PHP中配置 variables_order详解
android·开发语言·php
消失的旧时光-19431 天前
Android-音频采集
android·音视频