记录某次解决使用ConstraintLayout遇到的问题
问题1:如何快速知道最新版本的ConstraintLayout
问题2:ConstraintLayout
中使用Group
,且项目使用了AndResGuard 资源压缩工具,导致无法更新 Group 引用的相关子视图
分析问题思路
-
怀疑可能是混淆导致的? 尝试去掉混淆后,发现问题依旧没有解决,故排除它
-
怀疑是
AndResGuard
压缩工具导致的?去掉压缩任务后,重新编包,可以正常更新视图,通过实验结果,这个问题肯定和
AndResGuard
脱离不了关系 -
确定了导致问题发生的原因,寻找最优解
根据官方文档可知:
AndResGuard
将原本冗长的资源路径变短,例如将res/drawable/xxx
变为r/d/a
R.string.name
变为R.string.a
,相关原理请参考这篇文章项目中使用情况:
在代码中我们动态去更新这些被Group
关联的视图控件:
STATUS_LOADDING -> {
contentGroup.visibility = ConstraintLayout.INVISIBLE
restGroup.visibility = ConstraintLayout.INVISIBLE
loaddingGroup.visibility = ConstraintLayout.VISIBLE
}
///...
}
// 调用updatePreLayout 间接去刷新Group所关联的视图,这里表现为隐藏所有关联子视图
contentGroup.updatePreLayout(contraintLayoutRoot)
restGroup.updatePreLayout(contraintLayoutRoot)
loaddingGroup.updatePreLayout(contraintLayoutRoot)
为什么调用updatePreLayout
能够刷新它所关联的子控件,接着看源码Group.java
public void updatePreLayout(ConstraintLayout container) {
int visibility = this.getVisibility();
float elevation = 0.0F;
if (VERSION.SDK_INT >= 21) {
elevation = this.getElevation();
}
for(int i = 0; i < this.mCount; ++i) {
int id = this.mIds[i];
View view = container.getViewById(id);
if (view != null) {
view.setVisibility(visibility);
if (elevation > 0.0F && VERSION.SDK_INT >= 21) {
view.setElevation(elevation);
}
}
}
}
从源码中可以看出,它总共做了两件事:
- 遍历所有
Group
所关联的视图id, 并通过ConstraintLayout
找到视图实例 - 设置视图可见性 & 阴影
由上述可知,我们需要去研究:
-
Group
所关联视图 的ID值 是通过何种方式获取到的?找到
mIds
定义处:Group
的父类ConstraintHelper
, 其构造方法:TypedArray a = this.getContext().obtainStyledAttributes(attrs, styleable.ConstraintLayout_Layout); int N = a.getIndexCount(); for(int i = 0; i < N; ++i) { int attr = a.getIndex(i); if (attr == styleable.ConstraintLayout_Layout_constraint_referenced_ids) { this.mReferenceIds = a.getString(attr); // app:constraint_referenced_ids配置的字符串 // 拆解ids this.setIds(this.mReferenceIds); } } ///.... private void setIds(String idList) { if (idList != null) { int begin = 0; while(true) { int end = idList.indexOf(44, begin); if (end == -1) { this.addID(idList.substring(begin)); return; } // 获取id值并保存到数组中 this.addID(idList.substring(begin, end)); begin = end + 1; } } } .... private void addID(String idString) { .... try { Class res = id.class; Field field = res.getField(idString); tag = field.getInt((Object)null); } catch (Exception var5) { ; } if (tag == 0) { // 注意这里是通过getIdentifier这种方式获取资源id值 tag = this.myContext.getResources().getIdentifier(idString, "id", this.myContext.getPackageName()); } .... if (tag != 0) { this.setTag(tag, (Object)null); } else { // 若获取不到id资源值,将打印以下异常信息 Log.w("ConstraintHelper", "Could not find id of \"" + idString + "\""); } } ...
从源码中得出:Group
所关联的视图 是通过getIdentifier
获取到的,由于AndResguard 对原有的id 做了混淆变更处理,getIdentifier 传递的是未混淆之前的id ,导致无法获取到,进而就无法为mIds 数组中赋值
ConstraintLayout
是如何通过id找到视图引用的?
public class ConstraintLayout extends ViewGroup {
SparseArray<View> mChildrenByIds = new SparseArray();
}
/**
* Called when a new child is added to this ViewGroup. Overrides should always
* call super.onViewAdded.
* @param child the added child view
*/
public void onViewAdded(View view) {
...
// 子视图添加到当前是容器中,将id 和 引用进行关联缓存
this.mChildrenByIds.put(view.getId(), view);
this.mDirtyHierarchy = true;
}
// 此方法恰好在Group#updatePreLayout 时用到,由于andresguard 导致id 值无法找到,所以也就没法找到对应的视图引用,进而也就无法更新关联视图的可见性
public View getViewById(int id) {
return (View)this.mChildrenByIds.get(id);
}
综上:由于AndResGuard
混淆了视图id name,而ConstraintHelper
在获取所有关联的视图id时,使用了混淆之前的视图id name,导致无法获取到正确id值,进而在执行Group updatePreLayout
找不到对应关联的视图引用导致界面可见性无法改变
解决问题
- 方案一:
AndResGuard
官方文档也申明了若使用了getIdentifier
方法访问的资源,都需要加入白名单,但是笔者不建议这样做,因为后续开发过程中若用到了Group refreshID
,都需要手动去维护白名单,成本较高。这块已经有对应的开发者提出了issue - 方案二:不在xml 中定义
Group
, 在代码中初始化Group
, 再调用ConstraintHelper#setReferencedIds
绑定关联视图id, 绕开构造方法中getIdentifier
获取Group
所关联的视图的id值 这种方案