最近入职了新公司,开始接触广告,以前没怎么搞过,看了其他项目的广告老演员的封装,就是搞一个类,命名为 xx广告Manager,然后什么代码全写里面,好家伙,果然厉害,代码直面本质让人一下就看懂了,果然是老演员。
......
所以决定自己去封装一下。
看了 github 上相关的项目,只找到 TogetherAd 还有 EasyAds-Android 两个比较好的,看了一下,感觉都很复杂,各种配置,使用方法也不是我想要的。
现在接的都是融合 sdk,感觉广告不需要再分平台了,然后个人喜欢那种初始化一次,一句话调用的形式,不管内部写得好不好,API 必须给整漂亮。所以还是自己写一个吧。
先上仓库地址: EasyAdvApp
目前只封装了本人用到的广告场景,如果有更复杂的广告场景可以提出共同完善,觉得还行可以支持一下。
下面只会大概说下封装过程中一些简单的思路,框架的具体使用在 github 上有写。
使用:
kotlin
EasyAdv.splashConfig()
.setCodeId("KFC_V_WO_50")
.setActivity(this)
.setWidth(getScreenWidth())
.setHeight(getScreenHeight())
.setContainer(findViewById<FrameLayout>(R.id.adLayout))
.setSplashAdvListener(onAdTimeOver = {
toMain()
})
.showSplashAdv()
首先总结下广告封装需要什么功能:
- 尽量和业务逻辑分离,统一 api 调用
- 因为各种广告sdk的回调很多,最后能有一个地方全局回调,可以统一处理埋点之类的需求
- 使用过程中不需要关心某个广告的具体实现,只管调用就行。
- 其他...(不知道怎么吹了)
设计(以开屏为例):
各种功能的实现应该是基于接口形式的,这样可以将具体功能与框架逻辑分离,易于替换。
kotlin
interface ISdkPlatform {
fun initAdvSdk(
builder: AdvSDKBuilder,
callback: AdvSdkInitCallback
)
}
首先是广告 sdk 的初始化,实现该接口的 initAdvSdk 方法来初始化具体的sdk,builder 里面存储各种需要的参数,初始化完成通过 callback 回调给框架:
kotlin
class TTAdSdkPlatform : ISdkPlatform {
override fun initAdvSdk(builder: AdvSDKBuilder, callback: AdvSdkInitCallback) {
TTAdSdk.init(...)
TTAdSdk.start(object : TTAdSdk.Callback {
override fun success() {
callback.onInitSuccess()
}
override fun fail(code: Int, msg: String?) {
callback.onInitFail(code, msg)
}
})
}
}
同理然后各种广告的具体实现也设计成对应的接口:
kotlin
interface ISplashAdvEngine {
fun showSplashAdv(config: SplashAdvConfig)
}
interface IBannerAdvEngine {
fun showBannerAdv(config: BannerAdvConfig)
}
...
然后在框架初始化时设置进去就行
kotlin
EasyAdv.init(this)
.setPlatform(TTAdSdkPlatform())
.setSplashAdvEngine(TTSplashAdvEngine())
在使用框架功能时,就再也不用管广告的实现了,因为初始化的时候已经设置好了。
一开始说到,我们需要一些全局性的东西,比如一些公共参数,回调监听等,好处是设置了一次就能用,不需要每次都调用一次,比如埋点等功能。
那可以用一个实体类来存储这些参数,可以通过builder等方式去构建:
javascript
class GlobalAdvConfig {
var splashAdvListener: SplashAdvListener? = null
var userId: String = ""
}
其实 builder 模式挺麻烦,要写各种 set,最后还要 build 一下,这里可以用 DSL 去优化一下:
kotlin
fun sdkConfig(options: AdvSDKBuilder.() -> Unit) = apply {
advSDKBuilder = AdvSDKBuilder().also { options(it) }
}
EasyAdv.init(this)
.setGlobalAdvConfig {
userId = "KFC_V_WO_50"
splashAdvListener = xxx()
}
可以看到会简洁和方便很多。
框架初始化的东西差不多是这些,其他都同理差不多,下面讲讲开屏广告的问题
开屏广告分为冷启动和热启动
先说冷启动,遇到的问题其实主要是冷启动其实挺快的,因为sdk初始化要在同意隐私合规之后才能进行,然而广告的初始化过程是异步的,往往你调了开屏广告方法,sdk还没初始化完成的情况。。
因为框架的设置是初始化和使用分开的,所以调用冷启动广告时,要判断 sdk 是否初始化完成,没完成的时候要等待完成才去执行,然后还要有超时逻辑,超过一定时间没反应就直接进主页了。
如何实现这个等待过程,一开始想着用粘性广播,貌似有点复杂,然后想着 looper ,感觉用 while 循环貌似可行。
上面广告 sdk 初始化接口中,我们可以在 callback 拿到结果,那么可以用个变量标记下成功与否:
kotlin
sdkPlatform?.initAdvSdk(builder, object : AdvSdkInitCallback {
override fun onInitSuccess() {
isFinishSdkInit = true
isInitSdkSuccess = true
}
override fun onInitFail(code: Int, msg: String?) {
isFinishSdkInit = true
isInitSdkSuccess = false
}
})
一个代表是否完成初始化,一个代表是否初始化成功。
那么在 showSplashAdv 的时候,就可以通过这两个变量来进行等待逻辑了:
kotlin
private var splashAdvFlag = false
fun showSplashAdv(config: SplashAdvConfig, splashAdvEngine: ISplashAdvEngine?) {
showSplashAdvTime = System.currentTimeMillis()
//等待sdk初始化完成
while (!splashAdvFlag) {
if (EasyAdv.isFinishSdkInit) {
splashAdvFlag = if (EasyAdv.isInitSdkSuccess) {
splashAdvEngine?.showSplashAdv(config)
true
} else {
config.listener(CallbackType.ERROR, EasyAdv.ERROR_SDK, "sdk init fail")
true
}
} else {
if (EasyAdv.sdkTimeOutMillis > 0) {
if (System.currentTimeMillis() - showSplashAdvTime > EasyAdv.sdkTimeOutMillis) {
config.listener(CallbackType.ERROR, EasyAdv.ERROR_SDK, "sdk init time out")
splashAdvFlag = true
}
}
}
}
}
用 splashAdvFlag 来标识是否结束循环,然后通过判断两个变量来进行等待逻辑,只有都为 true 的时候执行具体的开屏方法,否则回调失败或者超时等逻辑。
热启动开屏
热启动其实就是监听 ActivityLifecycleCallbacks 然后再对应的弹出广告。这里我想的是,我只把判断是否是热启动的逻辑封装到框架中,具体广告逻辑还是放开给用户。
所以还是接口整一个:
kotlin
interface IHotSplashAdvStrategy {
fun getCodeId(): String
fun showRequirement(activity: Activity): Boolean
fun showHotSplashAdv(activity: Activity, codeId: String)
}
showRequirement 是显示条件,比如我要10分钟显示一次,或者哪个页面不显示等逻辑可以写里面,showHotSplashAdv 就是具体的显示逻辑了。其实常规做法就是弹个全屏 Dialog,里面调 开屏广告那一套即可。
就这样吧,写得垃圾,不写了。。。