Android平台Admob广告接入和封装

前言

关于Admob如何接入,官方文档已经写的很清楚,所以不是本文的重点。本文主要讲Admob的调用封装。因为作为开发者接入Admob之后,其实工作远没有结束,可能想做预加载,想做些封装让接口更加简洁友好,此外还想让业务代码尽可能不与SDK耦合,等等。其实这部分内容也是一个轮子,那我们就不要重复造轮子了。

Admob接入

1. 官方网站:https://developers.google.cn/admob/android/quick-start

官网文档有中英文版本,阅读上不是什么问题,实际接入难度不大。只是随着版本的更新,文档同步的不是很及时。

2. 官方开发论坛:https://groups.google.com/forum/#!forum/google-admob-ads-sdk

遇到一些问题可以到论坛搜索下。我在开发时遇到一个兼容性问题,某些手机上激励广告播放时会突然被强制关闭,后来就是在论坛上找到了解决方案:正在播放的激励广告需要有明确的引用持有,否则可能被系统回收导致广告关闭。

二次封装

完整代码可以查看本文后面的github项目地址。

AdmobManager

框架内唯一暴露给业务层的接口,是一个单例。业务层只需要跟它打交道就可以进行加载、获取、显示广告等操作。

object AdmobManager {
    const val TAG = "AdmobManager"

    /**
     * 一个id对应一个广告仓库
     */
    var rewardedAdRepoMap: MutableMap<String, RewardAdRepository> = HashMap()

    var nativeAdRepoMap: MutableMap<String, NativeAdRepository> = HashMap()

    /**
     * 初始化
     */
    fun init(context: Context) {
        MobileAds.initialize(context)
    }


    //-------------------------激励广告-----------------


    /**
     * 是否有[id]对应的已经加载完成的激励广告
     */
    fun isRewardAdReady(context: Context?, id: String): Boolean {
        if (context == null || TextUtils.isEmpty(id)) {
            return false
        }

        var rewardAdRepo = rewardedAdRepoMap[id]

        if (rewardAdRepo == null) {
            rewardAdRepo = RewardAdRepository(id)
            rewardedAdRepoMap[id] = rewardAdRepo
        }

        return rewardAdRepo.isReady(context)
    }

    /**
     * 加载[id]激励广告,如果仓库没有建立,则建立完再加载
     */
    fun loadRewardAd(context: Context?, id: String, preloadCount: Int = 1) {
        if (context == null || TextUtils.isEmpty(id)) {
            return
        }

        var rewardAdRepo = rewardedAdRepoMap[id]

        if (rewardAdRepo == null) {
            rewardAdRepo = RewardAdRepository(id)
            rewardedAdRepoMap[id] = rewardAdRepo
        }

        rewardAdRepo.setPreloadCount(preloadCount)
        rewardAdRepo.loadAds(context)
    }

    /**
     * 显示激励广告
     *
     * return true : 显示成功
     * return false : 显示失败,可能广告仓库没建立,或者广告还没加载完成
     */
    fun showRewardAd(activity: Activity, id: String, listener: RewardAdShowListener?): Boolean {
        if (activity == null || TextUtils.isEmpty(id)) {
            return false
        }

        var rewardAdRepo = rewardedAdRepoMap[id]

        if (rewardAdRepo == null) {
            rewardAdRepo = RewardAdRepository(id)
            rewardedAdRepoMap[id] = rewardAdRepo
        }

        rewardAdRepo?.let {
            if (it.isReady(activity)) {
                it.show(activity, listener)

                return true
            }
        }

        Log.e(TAG, "$id reward ad is not ready")

        return false
    }

    /**
     * 添加激励广告加载监听器。注意:监听器会在广告的一轮加载成功后被全部移除。
     */
    fun addRewardAdLoadListener(context: Context?, id: String, listener: RewardAdLoadListener?) {
        if (context == null || TextUtils.isEmpty(id) || listener == null) {
            return
        }

        var rewardAdRepo = rewardedAdRepoMap[id]

        if (rewardAdRepo == null) {
            rewardAdRepo = RewardAdRepository(id)
            rewardedAdRepoMap[id] = rewardAdRepo
        }

        rewardAdRepo.addLoadListener(listener)
    }

    /**
     * 移除激励广告加载监听器
     */
    fun removeRewardAdLoadListener(context: Context?, id: String, listener: RewardAdLoadListener?) {
        if (context == null || TextUtils.isEmpty(id) || listener == null) {
            return
        }

        var rewardAdRepo = rewardedAdRepoMap[id]

        if (rewardAdRepo == null) {
            rewardAdRepo = RewardAdRepository(id)
            rewardedAdRepoMap[id] = rewardAdRepo
        }

        rewardAdRepo.removeLoadListener(listener)
    }


    //-------------------------原生广告-----------------

    /**
     * 是否有加载好的[id]对应的原生广告
     */
    fun isNativeAdReady(context: Context?, id: String): Boolean {
        if (context == null || TextUtils.isEmpty(id)) {
            return false
        }

        var nativeAdRepo = nativeAdRepoMap[id]

        if (nativeAdRepo == null) {
            nativeAdRepo = NativeAdRepository(id)
            nativeAdRepoMap[id] = nativeAdRepo
        }

        return nativeAdRepo.isReady(context)
    }

    /**
     * 加载原生广告
     */
    fun loadNativeAd(context: Context?, id: String, preloadCount: Int = 1) {
        if (context == null || TextUtils.isEmpty(id)) {
            return
        }

        var nativeAdRepo = nativeAdRepoMap[id]

        if (nativeAdRepo == null) {
            nativeAdRepo = NativeAdRepository(id)
            nativeAdRepoMap[id] = nativeAdRepo
        }

        nativeAdRepo.setPreloadCount(preloadCount)
        nativeAdRepo.loadAds(context)
    }

    /**
     * 获取原生广告内容
     */
    fun getNativeAd(context: Context?, id: String): UnifiedNativeAd? {
        if (context == null || TextUtils.isEmpty(id)) {
            return null
        }

        var nativeAdRepo = nativeAdRepoMap[id]

        if (nativeAdRepo == null) {
            nativeAdRepo = NativeAdRepository(id)
            nativeAdRepoMap[id] = nativeAdRepo
        }

        return nativeAdRepo.getAd(context)
    }

    /**
     * 添加原生广告加载监听器。注意:监听器会在广告的一轮加载成功后被全部移除。
     */
    fun addNativeAdLoadListener(context: Context?, id: String, listener: NativeAdLoadListener?) {
        if (context == null || TextUtils.isEmpty(id) || listener == null) {
            return
        }

        var nativeAdRepo = nativeAdRepoMap[id]

        if (nativeAdRepo == null) {
            nativeAdRepo = NativeAdRepository(id)
            nativeAdRepoMap[id] = nativeAdRepo
        }

        nativeAdRepo.addLoadListener(listener)
    }

    /**
     * 移除原生广告加载监听器
     */
    fun removeNativeAdLoadListener(context: Context?, id: String, listener: NativeAdLoadListener?) {
        if (context == null || TextUtils.isEmpty(id) || listener == null) {
            return
        }

        var nativeAdRepo = nativeAdRepoMap[id]

        if (nativeAdRepo == null) {
            nativeAdRepo = NativeAdRepository(id)
            nativeAdRepoMap[id] = nativeAdRepo
        }

        nativeAdRepo.removeLoadListener(listener)
    }
}

NativeAdRepository

原生高级广告仓库,一个广告位id对应一个广告仓库。本质是一个广告池,可实现给定数量的广告预加载,并在广告被使用后自动进行个数的加载补足。

/**
 * 一个id对应一个广告仓库,仓库会自动进行广告的预加载(个数preloadCount),并向外提供广告内容
 *
 * id默认是测试广告
 */
class NativeAdRepository constructor(private var id: String = "ca-app-pub-3940256099942544/2247696110",
                                     private var preloadCount: Int = 1) {
    /**
     * 已加载的广告列表
     */
    var adList = LinkedList<UnifiedNativeAd>()

    /**
     * 是否正在进行广告加载
     */
    var isLoading = false

    /**
     * 正在加载的广告个数
     */
    var loadingCount = 0

    var loadListenerList = ArrayList<NativeAdLoadListener>()

    /**
     * 进行广告加载
     */
    fun loadAds(context: Context) {
        if (isLoading || adList.size >= preloadCount) {
            Log.w(AdmobManager.TAG, "$id NativeAd don't need load")
            return
        }

        isLoading = true //开始加载

        val request = AdRequest.Builder().build()

        val adLoader = AdLoader.Builder(context, id).forUnifiedNativeAd {//广告加载完成
            adList.offer(it)

            for(listener in loadListenerList) {
                listener.onLoaded()
            }

            onAdLoaded()
        }.withAdListener(object : AdListener() {
            override fun onAdFailedToLoad(errorCode: Int) {
                onAdLoaded()

                Log.w(AdmobManager.TAG, "$id NativeAd load failed, errorCode = $errorCode")
            }
        }).build()

        //开始加载
        loadingCount = preloadCount - adList.size
        adLoader.loadAds(request, loadingCount)
    }

    /**
     * 一个广告加载完成时调用
     */
    private fun onAdLoaded() {
        //更新正在加载的广告个数
        loadingCount--

        //所有广告加载完成,状态更新成未在加载中
        if (loadingCount <= 0) {
            isLoading = false

            //一般广告加载成功,收到一次通知即可,所以将所有监听器在这里进行移除
            if(adList.size > 0) {
                loadListenerList.clear()
            }
        }
    }

    /**
     * 是否已经有广告加载完成
     */
    fun isReady(context: Context): Boolean {
        return adList.isNotEmpty()
    }

    /**
     * 获取加载好的原生广告
     */
    fun getAd(context: Context): UnifiedNativeAd? {
        var ad = adList.poll()

        //广告被取走后,需要重新加载广告,保证池子里面有一定数量的预加载广告
        loadAds(context)

        return ad
    }

    /**
     * 设置预加载个数
     */
    fun setPreloadCount(value: Int) {
        preloadCount = value
    }


    /**
     * 添加广告加载监听器。注意:监听器会在广告的一轮加载成功后被全部移除。
     */
    fun addLoadListener(listener: NativeAdLoadListener) {
        if (!loadListenerList.contains(listener)) {
            loadListenerList.add(listener)
        }
    }

    /**
     * 移除广告加载监听器
     */
    fun removeLoadListener(listener: NativeAdLoadListener) {
        loadListenerList.remove(listener)
    }
}

RewardAdRepository

激励广告仓库,一个广告位id对应一个广告参考,实现方式与原生高级广告基本一致。


class RewardAdRepository constructor(private var id: String = "ca-app-pub-3940256099942544/5224354917",
                                     private var preloadCount: Int = 1) {
    /**
     * 是否正在加载
     */
    var isLoading = false

    var adList = ArrayList<RewardedAd>()

    /**
     * 正在加载的广告个数
     */
    var loadingCount = 0

    /**
     * 正在播放的广告需要有明确的引用持有,否则在有些系统里面广告会被强制回收关闭
     */
    var rewardedAdShowing: RewardedAd? = null

    var loadListenerList = ArrayList<RewardAdLoadListener>()

    val rewardAdLoaderCallback = object : RewardedAdLoadCallback() {
        override fun onRewardedAdLoaded() {
            Log.i(AdmobManager.TAG, "$id Reward Ad loaded")

            for(listener in loadListenerList) {
                listener.onLoaded()
            }

            onAdLoaded()
        }

        override fun onRewardedAdFailedToLoad(var1: Int) {
            Log.e(AdmobManager.TAG, "$id Reward Ad load failed, errorCode = $var1")

            onAdLoaded()
        }
    }

    private fun onAdLoaded() {
        loadingCount--

        if (loadingCount <= 0) {
            isLoading = false

            //一般广告加载成功,收到一次通知即可,所以将所有监听器在这里进行移除
            if(adList.size > 0) {
                loadListenerList.clear()
            }
        }
    }

    /**
     * 加载广告
     */
    fun loadAds(context: Context) {
        if (isLoading) {
            return
        }

        //用来放没有加载成功的广告
        var removeList = ArrayList<RewardedAd>()

        for (rewardAd in adList) {
            //没加载成功的广告就丢到待删除列表里
            if (!rewardAd.isLoaded) {
                removeList.add(rewardAd)
            }
        }

        adList.removeAll(removeList)

        loadingCount = preloadCount - adList.size

        val request = AdRequest.Builder().build()

        while (adList.size < preloadCount) {
            var rewardedAd =  RewardedAd(context, id)
            adList.add(rewardedAd)
            rewardedAd.loadAd(request, rewardAdLoaderCallback)

            isLoading = true

            Log.i(AdmobManager.TAG, "$id Reward Ad loading")
        }
    }

    /**
     * 是否有加载完成的激励广告
     */
    fun isReady(context: Context): Boolean {
        for(rewardAd in adList) {
            if (rewardAd.isLoaded) {
                return true
            }
        }

        return false
    }

    /**
     * 显示激励广告
     */
    fun show(activity: Activity, listener: RewardAdShowListener?) {
        for(rewardAd in adList) {
            if (rewardAd.isLoaded) {
                Log.i(AdmobManager.TAG, "$id Reward Ad show")

                rewardedAdShowing = rewardAd

                rewardAd.show(activity, object : RewardedAdCallback() {
                    override fun onRewardedAdOpened() {
                        listener?.onShowed()
                    }

                    override fun onRewardedAdClosed() {
                        listener?.onClosed()

                        rewardedAdShowing = null
                    }

                    override fun onUserEarnedReward(@NonNull var1: RewardItem) {
                        listener?.onEarned()
                    }

                    override fun onRewardedAdFailedToShow(var1: Int) {
                        listener?.onShowFailed(var1)
                    }
                })
            }
        }
    }

    /**
     * 设置预加载个数
     */
    fun setPreloadCount(value: Int) {
        preloadCount = value
    }

    /**
     * 添加广告加载监听器。注意:监听器会在广告的一轮加载成功后被全部移除。
     */
    fun addLoadListener(listener: RewardAdLoadListener) {
        if (!loadListenerList.contains(listener)) {
            loadListenerList.add(listener)
        }
    }

    /**
     * 移除广告加载监听器
     */
    fun removeLoadListener(listener: RewardAdLoadListener) {
        loadListenerList.remove(listener)
    }
}

Demo

项目地址:https://github.com/CoddZhang/AdmobDemo

包含:

1. 对SDK调用的封装代码

2. 使用测试id实现激励广告和原生高级广告。

image

写在最后

Admob其实是被Google收购的,并非土著的Google开发团队作品,所以SDK的设计水准感觉一般,友好性和自由度上做的不太好,不够赏心悦目。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349