学习记录|踩坑!穿山甲广告Android SDK接入

随着流量变现的兴起,越来越多的广告SDK汹涌而来,除了字节的穿山甲,还有腾讯的优量汇、百度的广告联盟,其他的例如AdView和万普世纪已经逐渐退出历史的舞台。
本篇文章将基于com.pangle.cn:ads-sdk:3.6.1.8重点介绍下穿山甲Android SDK接入文档的集成过程,以及适配过程中踩过的坑。
基本集成
project目录的build.gradle文件中添加Maven的引用

allprojects { repositories { maven { url 'https://artifact.bytedance.com/repository/pangle'} } }

在主modulebuild.gradle文件添加SDK依赖
dependencies { implementation 'com.pangle.cn:ads-sdk:3.6.1.8' }

在AndroidManifest.xml中添加权限

注意: 穿山甲SDK不强制获取以上权限,即使没有获取可选权限SDK也能正常运行;获取以上权限将帮助穿山甲优化投放广告精准度和用户的交互体验,提高eCPM。
建议在广告请求前,合适的时机调用SDK提供的方法,在用户可以授权的情况下获取到声明中的权限,提高广告变现效率
//TTAdManager接口中的方法,context可以是Activity或Application void requestPermissionIfNecessary(Context context);

在AndroidManifest.xml中添加provider
注意:
  • 为不影响下载类型广告使用 无论APP处于任何阶段provider都需要在清单文件中正常配置
  • 为不影响到广告的转化及收益 请务必在清单文件中配置xxx.TTMultiProvider
适配Anroid7.0及以上
如果您的应用需要在Anroid7.0及以上环境运行,请在AndroidManifest中添加如下代码:

在res/xml目录下,新建一个xml文件file_paths,在该文件中添加如下代码:

注意:单进程或多进程都必须配置

运行环境配置
【学习记录|踩坑!穿山甲广告Android SDK接入】本SDK可运行于Android4.0 (API Level 14) 及以上版本。

如果开发者声明targetSdkVersion到API 23以上,请确保调用本SDK的任何接口前,已经申请到了SDK要求的所有权限,否则SDK部分特性可能受限
代码混淆设置
如果您需要使用proguard混淆代码,需确保不要混淆SDK的代码。 请在proguard.cfg文件(或其他混淆文件)尾部添加如下配置:
-keep class com.bytedance.sdk.openadsdk.** { *; } -keep public interface com.bytedance.sdk.openadsdk.downloadnew.** {*; } -keep class com.pgl.sys.ces.** {*; } -keep class com.bytedance.embed_dr.** {*; } -keep class com.bytedance.embedapplog.** {*; }

注意: SDK代码被混淆后会导致广告无法展现或者其它异常,一定要尽量避免混淆SDK中的代码
SDK支持架构
注意: SDK中使用的so文件支持五种架构:x86,x86_64,armeabi,armeabi-v7a,arm64-v8a如果您应用中
支持的架构超出这 五种,请在build.gradle中使用abiFilters选择支持的架构。如下所示:
ndk { // 设置支持的 SO 库构架,注意这里要根据你的实际情况来设置 abiFilters 'armeabi-v7a', 'arm64-v8a', x86', x86_64', 'armeabi' }

SDK初始化
注意:
  1. 穿山甲SDK需要在主线程中初始化
  2. 多进程涉及WebView的使用,用户想要使用自己的数据路径,可以在SDK初始化之前调用WebView.setDataDirectorySuffix()
  3. 目前SDK已支持多进程,如果明确某个进程不会使用到广告SDK,可以只针对特定进程初始化广告sdk
/** * 可以用一个单例来保存TTAdManager实例,在需要初始化sdk的时候调用 */ class TTAdManagerHolder private constructor(){companion object { private val instance: TTAdManagerHolder by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { TTAdManagerHolder() } @JvmStatic fun get() : TTAdManagerHolder { return instance } @JvmStatic fun getTTAdManager() : TTAdManager? { if (!TTAdSdk.isInitSuccess()) { return null } return TTAdSdk.getAdManager() } }//step1:接入网盟广告sdk的初始化操作,详情见接入文档和穿山甲平台说明 //穿山甲SDK初始化 //强烈建议在应用对应的Application#onCreate()方法中调用,避免出现content为null的异常 fun doInit(context: Context) { ///经测试发现TTAdSdk.isInitSuccess()参数无效初始化成功之后还是false? ALog.i("穿山甲SDK初始化结果",TTAdSdk.isInitSuccess()); AppData.get().querySystemConfig()?.tt_app_id?.let { TTAdSdk.init(context,buildConfig(context,it),object :TTAdSdk.InitCallback{ override fun success() { ALog.i("穿山甲SDK初始化成功",TTAdSdk.isInitSuccess(),TTAdSdk.getAdManager().sdkVersion); LiveEventBus.get(DataTag.tt_sdk_init_result) .post(TaskEventBean(key = 0)) } override fun fail(p0: Int, p1: String?) { ALog.i("穿山甲SDK初始化失败",p0,p1); LiveEventBus.get(DataTag.tt_sdk_init_result) .post(TaskEventBean(key = p0,value = https://www.it610.com/article/p1)) } }) } }private fun buildConfig(context: Context,appId:String): TTAdConfig { return TTAdConfig.Builder() .appId(appId) .appName(DataUtils.getString(R.string.app_name)) .paid(true) ///添加这个控制之后可能造成模拟器闪退/不能下载文件 /*.customController(object : TTCustomController() { override fun isCanUseLocation(): Boolean { return false }override fun isCanUsePhoneState(): Boolean { return false }override fun isCanUseWifiState(): Boolean { return false }override fun isCanUseWriteExternal(): Boolean { return false } })*/ .useTextureView(true) //使用TextureView控件播放视频,默认为SurfaceView,当有SurfaceView冲突的场景,可以使用TextureView .allowShowNotify(true) //是否允许sdk展示通知栏提示 //.allowShowPageWhenScreenLock(true) // 锁屏下穿山甲SDK不会再出落地页,此API已废弃,调用没有任何效果 .debug(BuildConfig.DEBUG) //测试阶段打开,可以通过日志排查问题,上线时去除该调用 .directDownloadNetworkType(TTAdConstant.NETWORK_STATE_WIFI, TTAdConstant.NETWORK_STATE_3G, TTAdConstant.NETWORK_STATE_4G) //允许直接下载的网络状态集合 .supportMultiProcess(false) //是否支持多进程 .needClearTaskReset() .httpStack(TTAdStack())//.httpStack(new MyOkStack3())//自定义网络库,demo中给出了okhttp3版本的样例,其余请自行开发或者咨询工作人员。 .build() } }

然后在Application#onCreate()中调用
TTAdManagerHolder.get().doInit(it)

初始化配置参数说明
public static class TTAdConfig.Builder { private String mAppId; // 必选参数,设置应用的AppId private String mAppName; // 必选参数,设置应用名称 private boolean mIsPaid = false; // 可选参数,设置是否为计费用户:true计费用户、false非计费用户。默认为false非计费用户。须征得用户同意才可传入该参数 private String mKeywords; // 可选参数,设置用户画像的关键词列表 **不能超过为1000个字符**。须征得用户同意才可传入该参数 private String mData; // 可选参数,设置额外的用户信息 **不能超过为1000个字符** private int mTitleBarTheme = TTAdConstant.TITLE_BAR_THEME_LIGHT; // 可选参数,设置落地页主题,默认为TTAdConstant#TITLE_BAR_THEME_LIGHT private boolean mAllowShowNotify = true; // 可选参数,设置是否允许SDK弹出通知:true允许、false禁止。默认为true允许 private boolean mIsDebug = false; // 可选参数,是否打开debug调试信息输出:true打开、false关闭。默认false关闭 private int[] mDirectDownloadNetworkType; //允许直接下载的网络状态集合,不调用则所有网络下均无下载确认弹窗,调用不传参数则所有网络均有下载确认弹窗 private boolean mIsUseTextureView = false; // 可选参数,设置是否使用texture播放视频:true使用、false不使用。默认为false不使用(使用的是surface) private boolean mIsSupportMultiProcess = false; // 可选参数,设置是否支持多进程:true支持、false不支持。默认为false不支持 private IHttpStack mHttpStack; //可选参数,设置外部网络请求,默认为urlconnection private boolean mIsAsyncInit = false; //是否异步初始化sdk,开启可减少初始化耗时 private TTCustomController mCustomController; //可选参数,可以设置隐私信息控制开关 }

隐私信息控制开关
public abstract class TTCustomController { /** * 是否允许SDK主动使用地理位置信息 * * @return true可以获取,false禁止获取。默认为true */ public boolean isCanUseLocation() { return true; }/** * 当isCanUseLocation=false时,可传入地理位置信息,穿山甲sdk使用您传入的地理位置信息 * * @return 地理位置参数 */ public TTLocation getTTLocation() { return null; }/** * 是否允许SDK主动使用手机硬件参数,如:imei * * @return true可以使用,false禁止使用。默认为true */ public boolean isCanUsePhoneState() { return true; }/** * 当isCanUsePhoneState=false时,可传入imei信息,穿山甲sdk使用您传入的imei信息 * * @return imei信息 */ public String getDevImei() { return null; }/** * 是否允许SDK主动使用ACCESS_WIFI_STATE权限 * * @return true可以使用,false禁止使用。默认为true */ public boolean isCanUseWifiState() { return true; }/** * 是否允许SDK主动使用WRITE_EXTERNAL_STORAGE权限 * * @return true可以使用,false禁止使用。默认为true */ public boolean isCanUseWriteExternal() { return true; }/** * 开发者可以传入oaid * * @return oaid */ public String getDevOaid() { return null; } }

注意:
  • 重写getTTLocation()之前需要设置isCanUseLocation()
  • 重写getDevImei()之前需要先设置isCanUsePhoneState()
TTAdManger接口说明
public interface TTAdManager {// 创建TTAdNative对象 必须采用activity TTAdNative createAdNative(Context context); //部分机型需要主动申请权限,如 READ_PHONE_STATE权限 void requestPermissionIfNecessary(Context context); //退出时尝试显示"提示安装app"对话框,返回值:true显示对话框、false不显示对话框 boolean tryShowInstallDialogWhenExit(Context context, final ExitInstallListener listener); //获取穿山甲sdk版本号 String getSDKVersion(); // 3600版本新增接口设置夜间模式 // 设置主题类型,0:正常模式;1:夜间模式;默认为0;传非法值,按照0处理 void setThemeStatus(int themeStatus); // 获取当前主题类型 int getThemeStatus(); }

SDK导入需要注意的点:
1、TTAdManagerHolder保持单例模式;
2、单进程多次初始化SDK以第一次初始设置的信息为主;
3、appId是平台上穿件应用生成的7位数`字;
4、任何类型的视频广告加载后播放出现黑屏都可以按以下方式尝试解决:
清单文件中的黑屏权限是否添加 尝试使用TextureView控件播放视频

5、appName不为空即可 建议和平台创建的应用名称保持一致
6、directDownloadNetworkType配置:
可根据具体的产品需求自行配置,方法中的网络集合为允许直接下载的网络
若需要在任何网络下用户下载都有确认弹窗,方法中里直接写空(非null)即可
7、supportMultiProcess说明:
若项目是单进程必须更改为false
若项目是多进程,则需要设置true。多进程中如果每个进程中都需要展示广告 必须每个进程都进行SDK的初始化
如果您的应用需要支持多进程,请务必设置TTAdConfig.supportMultiProcess(true)。
确认app多进程支持判断方法:
a、穿山甲sdk初始化
b、穿山甲广告获取
c、穿山甲广告展示
这三个关键点的调用在不同进程则为多进程,否则即为单进程
如非必要尽量不要使用多进程开关,多进程效率不如单进程高
8、provider配置
无论单进程还是多进程都必须配置穿山甲所需provider
无论平台应用处于测试状态还是正式状态都需要配置provider
为不影响到广告的转化及收益 请务必在清单文件中配置xxx.TTMultiProvider
9、如果您的应用对资源也进行混淆(如andResGuard),请不要混淆穿山甲的任何资源,防止资源找不到而发生崩溃的问题。
SDK压缩文件内whiteList.txt 白名单上的资源不支持混淆 ,开发者在SDK更新迭代过程中需要重新检查一遍白名单内容
开屏广告
注意:
  • 开屏广告view:width =屏幕宽;height需要>=75%屏幕高 ,否则会影响计费。
  • 支持的广告尺寸:开屏广告的尺寸为开发者请求广告时设置的尺寸,建议设置的请求尺寸传入与展示区域大小保持一致,避免素材变形。
  • SDK渲染开屏广告: 默认提供;模板渲染开屏广告: 白名单,需要申请开通。
  • 模板渲染开屏和SDK渲染开屏的广告在请求方式上没有差异,仅是请求参数上的差异。模板渲染的开屏请求方法中需要设置setExpressViewAcceptedSize参数,SDK渲染开屏广告不需要设置此参数。
private val expressViewWidth by lazy { UIUtils.getScreenWidthDp(getThis()) } private val expressViewHeight by lazy { UIUtils.getHeight(getThis()) }//模板广告需要设置期望个性化模板广告的大小,单位dp,代码位是否属于个性化模板广告,请在穿山甲平台查看。且仅是模板渲染的代码位ID使用,非模板渲染代码位切勿使用 val adSlot = AdSlot.Builder() .setCodeId() //.setUserID() //用户id,可选参数 .setSupportDeepLink(true) .setImageAcceptedSize(expressViewWidth.toInt(), expressViewHeight.toInt()) .build() //请求广告,调用开屏广告异步请求接口,对请求回调的广告作渲染处理 TTAdManagerHolder.getTTAdManager()?.createAdNative(this)?.loadSplashAd(adSlot, object : SplashAdListener { @MainThread override fun onError(code: Int, message: String?) { ALog.i("开屏error", code ,message); }@MainThread override fun onTimeout() { ALog.i("开屏广告加载超时"); }@MainThread override fun onSplashAdLoad(ad: TTSplashAd?) { //获取SplashView viewData?.flContainer?.let { if (!isFinishing){ it.removeAllViews() if (ad?.splashView?.parent !=null){ (ad.splashView.parent as ViewGroup).removeView(ad.splashView) } it.addView(ad?.splashView) } } //设置SplashView的交互监听器 ad?.setSplashInteractionListener(object : TTSplashAd.AdInteractionListener { override fun onAdClicked(view: View?, type: Int) { ALog.i("开屏广告点击"); }override fun onAdShow(view: View?, type: Int) { ALog.i("开屏广告展示"); }override fun onAdSkip() { ALog.i("开屏广告跳过"); }override fun onAdTimeOver() { ALog.i("开屏广告倒计时结束"); } }) //Splash广告的交互类型 2在浏览器内打开 (普通类型)3落地页(普通类型),5:拨打电话 -1 未知类型 if (ad?.interactionType == TTAdConstant.INTERACTION_TYPE_DOWNLOAD) { ad.setDownloadListener(TTAppDownloadListenerImpl()) } } }, 3500)

其中TTAppDownloadListenerImpl()为用户点击下载之后的回调:
class TTAppDownloadListenerImpl: TTAppDownloadListener { override fun onIdle() { //ALog.i("onIdle"); }override fun onDownloadActive(p0: Long, p1: Long, p2: String?, p3: String?) { //ALog.i("onDownloadActive",p0,p1,p2,p3); }override fun onDownloadPaused(p0: Long, p1: Long, p2: String?, p3: String?) { //ALog.i("onDownloadPaused",p0,p1,p2,p3); }override fun onDownloadFailed(p0: Long, p1: Long, p2: String?, p3: String?) { //ALog.i("onDownloadFailed",p0,p1,p2,p3); }override fun onDownloadFinished(p0: Long, p1: String?, p2: String?) { //ALog.i("onDownloadFinished",p0,p1,p2); }override fun onInstalled(p0: String?, p1: String?) { //ALog.i("onDownloadFinished",p0,p1); } }

开屏广告注意点:
1、模板渲染的开屏请求方法需设置setExpressViewAcceptedSize参数 单位dp。非模板渲染开屏请求方法需设置setImageAcceptedSize参数 单位px 。切记不可使用错误
2、开屏广告加载超时时间建议大于3500ms,最大程度的保证广告的展示率可开屏体验,示例设置了3500ms
3、为了收益最大化所有开屏广告均要实时请求,不可缓存。
4、开发者要在onError()、onTimeout()、onAdSkip()、onAdTimeOver()回调及TTSplashAd广告为null时开发者做跳转主页面的处理,跳转之后开屏控件上的view移除。
5、需要开发者在开屏ActivityonStop()中做一个标记 在onResume()中做跳转主页面的逻辑处理,跳转之后开屏控件上的view移除。例如:
@Override protected void onResume() { //判断是否该跳转到主页面 if (mForceGoMain) { goToMainActivity(); } super.onResume(); }@Override protected void onStop() { super.onStop(); mForceGoMain = true; }

6、如果要在底部添加自身logo,需要缩小的开屏广告的展示区域,此时请求的时候宽和高填写裁剪后宽和高即可,即请求尺寸为屏幕宽*(屏幕高-logo高)
激励视频
激励视频广告需要让用户主动选择去观看,广告的效果为观看完毕视频广告,发放奖励给用户。使用场景包括但不限于:
  1. 游戏等应用内观看视频广告获得游戏内金币等;
  2. 积分类应用接入;
注意:
  • 支持的广告尺寸: 全屏横屏(宽高比16:9)、全屏竖屏(宽高比9:16)。Android端暂不支持重力旋转。
  • 模板渲染激励视频和SDK渲染激励视频的广告请求方式上没有差异,仅是请求参数上的差异。模板渲染的激励视频请求方法中需要设置setExpressViewAcceptedSize参数,设置的值大于0即可。
  • 模板渲染代码位setExpressViewAcceptedSize必填 ,SDK模板渲染代码位ID勿传。
  • 为保证广告视频播放的流畅建议在onRewardVideoCached回调加载完毕后,再在主线程中调用showRewardVideoAd方法进行广告的展示。 展示广告完毕之后及时把广告对象置为null
var mttRewardVideoAd: TTRewardVideoAd? = null //创建模板 val adSlot = AdSlot.Builder() .setCodeId() .setSupportDeepLink(true) .setRewardName("金币") //奖励的名称 .setRewardAmount(3) //奖励的数量 .setExpressViewAcceptedSize(500f, 500f)//模板广告需要设置期望个性化模板广告的大小,单位dp,激励视频场景,只要设置的值大于0即可 //.setUserID(DataTag.ttad_user_id) //用户id,必传参数 .setMediaExtra("media_extra") //附加参数,可选 .setOrientation(TTAdConstant.VERTICAL) //必填参数,期望视频的播放方向:TTAdConstant.HORIZONTAL 或 TTAdConstant.VERTICAL .build() //请求广告 mTTAdNative.loadRewardVideoAd(adSlot, object : TTAdNative.RewardVideoAdListener { override fun onError(code: Int, message: String) { ALog.e("onError",code,message); }//视频广告加载后,视频资源缓存到本地的回调,在此回调后,播放本地视频,流畅不阻塞。 override fun onRewardVideoCached() { ALog.e("onRewardVideoCached"); //mIsLoaded = true mttRewardVideoAd?.showRewardVideoAd(getThis(), TTAdConstant.RitScenes.CUSTOMIZE_SCENES, "scenes_test") mttRewardVideoAd = null }//视频广告的素材加载完毕,比如视频url等,在此回调后,可以播放在线视频,网络不好可能出现加载缓冲,影响体验。 override fun onRewardVideoAdLoad(ad: TTRewardVideoAd) { ALog.e( "onRewardVideoAdLoad",ad.interactionType) //mIsLoaded = false mttRewardVideoAd = ad ad.setRewardAdInteractionListener(object : TTRewardVideoAd.RewardAdInteractionListener { override fun onAdShow() { ALog.d("onAdShow") }override fun onAdVideoBarClick() { ALog.d( "onAdVideoBarClick") }override fun onAdClose() { ALog.d("onAdClose") }//视频播放完成回调 override fun onVideoComplete() { ALog.d( "onVideoComplete") }override fun onVideoError() { ALog.e("onVideoError") }//视频播放完成后,奖励验证回调,rewardVerify:是否有效,rewardAmount:奖励梳理,rewardName:奖励名称 override fun onRewardVerify(rewardVerify: Boolean, rewardAmount: Int, rewardName: String, errorCode: Int, errorMsg: String) { ALog.e("onRewardVerify",rewardVerify,rewardAmount,rewardName,errorCode,errorMsg) }override fun onSkippedVideo() { ALog.i("onSkippedVideo"); } }) if (mttRewardVideoAd?.interactionType == TTAdConstant.INTERACTION_TYPE_DOWNLOAD) { mttRewardVideoAd!!.setDownloadListener(TTAppDownloadListenerImpl()) } } })override fun onDestroy() { super.onDestroy() //在Activity的onDestroy方法中清空广告对象 mttRewardVideoAd = null }

全屏视频
全屏视频广告,该广告的效果播放全屏的视频,视频一定时间后可跳过,无需全程观看完。
注意:
  • 支持的广告尺寸: 全屏横屏(宽高比16:9)、全屏竖屏(宽高比9:16)。Android端暂不支持重力旋转。
  • 模板渲染全屏和SDK渲染全屏的广告请求方式上没有差异,仅是请求参数上的差异。模板渲染的全屏视频请求方法中需要设置setExpressViewAcceptedSize参数 设置的值大于0即可。
  • 模板渲染代码位setExpressViewAcceptedSize必填 ,SDK模板渲染代码位ID勿传。
  • 为广告视频素材的流畅,建议在onFullScreenVideoCached方法加载完毕后 再在主线程中调用showFullScreenVideoAd方法进行广告的展示。 展示广告完毕之后及时把广告对象置为null。
//创建TTAdNative对象,createAdNative(Context context) context需要传入Activity对象 TTAdNative mTTAdNative = TTAdSdk.getAdManager().createAdNative(this); AdSlot adSlot = new AdSlot.Builder() .setCodeId(codeId) //模板广告需要设置期望个性化模板广告的大小,单位dp,激励视频场景,只要设置的值大于0即可 //且仅是模板渲染的代码位ID使用,非模板渲染代码位切勿使用 .setExpressViewAcceptedSize(500,500) .setSupportDeepLink(true) .setOrientation(orientation)//必填参数,期望视频的播放方向:TTAdConstant.HORIZONTAL 或 TTAdConstant.VERTICAL .build(); mTTAdNative.loadFullScreenVideoAd(adSlot, new TTAdNative.FullScreenVideoAdListener() { //请求广告失败 @Override public void onError(int code, String message) {} //广告物料加载完成的回调 @Override public void onFullScreenVideoAdLoad(TTFullScreenVideoAd ad){} //广告视频本地加载完成的回调,接入方可以在这个回调后直接播放本地视频 @Override public void onFullScreenVideoCached() { if (mttFullVideoAd != null&&mIsLoaded) { //展示广告,并传入广告展示的场景 mttFullVideoAd.showFullScreenVideoAd(FullScreenVideoActivity.this, TTAdConstant.RitScenes.GAME_GIFT_BONUS, null); mttFullVideoAd = null; } else { TToast.show(FullScreenVideoActivity.this, "请先加载广告"); } } ); //在Activity的onDestroy方法中清空广告对象 @Override protected void onDestroy() { super.onDestroy(); if (mttFullVideoAd != null) { mttFullVideoAd = null; } }

    推荐阅读