大部分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中添加。