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

相关推荐
liang_jy29 分钟前
Android 事件分发机制(二)—— 点击事件透传
android·面试·源码
圆号本昊3 小时前
Flutter Android Live2D 2026 实战:模型加载 + 集成渲染 + 显示全流程 + 10 个核心坑( OpenGL )
android·flutter·live2d
冬奇Lab4 小时前
ANR实战分析:一次audioserver死锁引发的系统级故障排查
android·性能优化·debug
冬奇Lab4 小时前
Android车机卡顿案例剖析:从Binder耗尽到单例缺失的深度排查
android·性能优化·debug
ZHANG13HAO5 小时前
调用脚本实现 App 自动升级(无需无感、允许进程中断)
android
圆号本昊6 小时前
【2025最新】Flutter 加载显示 Live2D 角色,实战与踩坑全链路分享
android·flutter
小曹要微笑6 小时前
MySQL的TRIM函数
android·数据库·mysql
mrsyf8 小时前
Android Studio Otter 2(2025.2.2版本)安装和Gradle配置
android·ide·android studio
DB虚空行者8 小时前
MySQL恢复之Binlog格式详解
android·数据库·mysql