问题
1、广告六要素
虽然我不参与广告 sdk 接入等相关工作,但是最近总是听到一个词广告六要素
。这到底是什么?
国内下载类广告
,尤其是针对移动应用推广的广告,其成功实施往往围绕几个关键要素进行,这些要素能够帮助广告主更有效地触达目标用户,促进应用下载。
经查阅知道,国内各大广告 SDK 平台优量汇、穿山甲、快手等已逐步开始(早已)支持广告六要素,提供了获取应用的下载六项信息:
- 应用名称
- 开发者公司名称
- 应用版本
- 隐私协议超链
- 权限列表超链
- 产品功能
下图以荣耀广告
为例,这个是六要素控件显示正常的情况:
2、布局错乱
在不使用自定义混淆(包含微信资源混淆 AndResGuard)出包运行布局是正确的!
使用微信资源混淆之后,运行错乱,六要素重叠显示!
AndResGuard Github
排查
通过使用uiautomatorviewer
布局分析,定位找到了该广告的布局文件honor_ads_six_factor.xml
,并确定了每个显示文本对应的控件,uiautomatorviewer 工具跟随 Android studio,安装目录下可以找到,也可以全局搜索查找。
C:\Users\Primer\AppData\Local\Android\Sdk\tools\bin\uiautomatorviewer.bat
我们知道,父容器使用的是约束布局ConstraintLayout
(减少布局嵌套,布局优化),既然是因为使用了资源混淆导致问题出现,得了解资源混淆的配置是什么?
andResGuard {
mappingFile = null
use7zip = true
useSign = true
// 打开这个开关,会keep住所有资源的原始路径,只混淆资源的名字
keepRoot = false
// 设置这个值,会把arsc name列混淆成相同的名字,减少string常量池的大小
fixedResName = "arg"
// 打开这个开关会合并所有哈希值相同的资源,但请不要过度依赖这个功能去除去冗余资源
mergeDuplicatedRes = true
//混淆白名单
whiteList = [
// for your icon
"R.drawable.icon",
// for fabric
"R.string.com.crashlytics.*",
// for google-services
"R.string.google_app_id",
"R.string.gcm_defaultSenderId",
"R.string.default_web_client_id",
"R.string.ga_trackingId",
"R.string.firebase_database_url",
"R.string.google_api_key",
"R.string.google_crash_reporting_api_key"
]
compressFilePattern = [
"*.png",
"*.jpg",
"*.jpeg",
"*.gif",
]
sevenzip {
artifact = 'com.tencent.mm:SevenZip:1.2.21'
}
/**
* 可选: 如果不设置则会默认覆盖assemble输出的apk
**/
// finalApkBackupPath = "${project.rootDir}/final.apk"
/**
* 可选: 指定v1签名时生成jar文件的摘要算法
* 默认值为“SHA-1”
**/
// digestalg = "SHA-256"
}
1、首先,布局文件加白名单
白名单配置是whiteList
,一开始我们是连同布局文件也混淆的(上图是加白之后的),查看荣耀广告 SDK 的资源文件,发现文件名称都有统一前缀 honor_ads
,所以加白了下面的资源,测试 -> 运行 -> 布局错乱 -> 异常
"R.drawable.honor_ads*",
"R.interpolator.honor_ads*",
"R.anim.honor_ads*",
"R.animator.honor_ads*",
"R.attr.honor_ads*",
"R.color.honor_ads*",
"R.dimen.honor_ads*",
"R.mipmap.honor_ads*",
"R.id.honor_ads*",
"R.string.honor_ads*",
"R.layout.honor_ads*",
"R.style.honor_ads*",
2、仔细查看布局
上图的布局细看是这样的:
① 名称 广告 公司
② 隐私 权限 介绍 版本
- 两行,七个控件
- 第一行三个,第二行四个控件
- 猜想:第二行如何很好的约束布局(按照我们的正常编码思维哈)
- 1、第二行可能是:app:layout_constraintTop_toBottomOf=“第一行”,这个里是把第一行和第二行分别看成一个整体
【很明显,该布局代码不是这样的(简单布局嵌套过深,没必要)】 - 2、第二行的第一个可能是:app:layout_constraintTop_toBottomOf=“第一行的第一个(名称)”,然后第二行的其他元素像第一个或上一个控件对齐
【很明显,布局代码也不是这样的(想必大多数人会这样写吧,没错我就是那大多数人)】 - 3、使用 androidx.constraintlayout.widget.Barrier 进一步约束布局
【代码确实是这样】
- 1、第二行可能是:app:layout_constraintTop_toBottomOf=“第一行”,这个里是把第一行和第二行分别看成一个整体
此前,我从未使用或了解过约束布局中的 androidx.constraintlayout.widget.Barrier
这到底是什么?大概意思就是可以控制布局,具体如何使用自行搜索。
简书:Barrier的使用及实例
查资料他有两个重要的属性:
- barrierDirection:方向
- constraint_referenced_ids:被约束的控件 id 名称集合
<androidx.constraintlayout.widget.Barrier
android:id="@+id/xlp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<!-- 这里编译后是 3,我推测是 Bottom (你们可以验证下)-->
app:barrierDirection="3"
app:constraint_referenced_ids="ad_empty_view_top,ad_brand_name,ad_flag_view,ad_developer_view"/>
我们知道约束布局中 Direction 是这样的:
所以布局中的 Barrier 起到什么作用你应该知道了。
问题的关键是要找到关键问题(手动狗头),关键果然是 Barrier 的 app:constraint_referenced_ids,因为只有他会约束布局影响控件显示位置!
再细看 Barrier !
比如像这种属性值能正常替换
constraint_referenced_ids 属性值为什么不能被替换?
- 像是插件还不支持比较新的属性值替换?
- 或者属性值为数组列表的格式不同导致没有被替换?
3、解决
总之,问题清晰了,如果要进一步看资源混淆插件源码排查属性值为什么没被替换,自己尝试适配插件那可麻烦,耗时费力(除非你真的激情满满),所以最终解决就是给资源 id 名称加入白名单,保持 constraint_referenced_ids 集合里面的名称不被混淆即可!
还记的之前的加白里面虽然包含了 id,但是 keep 的是 honor_ads*
,无效啊!
"R.id.honor_ads*",
最终,再次提取加白前缀,那就是
"R.id.ad_*",
app:constraint_referenced_ids="
ad_empty_view_top,
ad_brand_name,
ad_flag_view,
ad_developer_view"
荣耀联运 SDK 竟也有坑,只是当时没测到~