插件styleable资源问题
当我们自定义View的时候经常也会自定属性,也就是__declare-styleable__。但是当我们尝试在插件中使用该自定义View的时候,经常会出现自定义属性未正常解析的问题。我们知道,对于插件和宿主的资源,一般的处理方式是隔离并分配不同的资源ID。然而,这个问题正是由于插件和宿主attr资源ID或者顺序不一致引起的,举个栗子:
在宿主中定义和使用styleable资源
styles.xml:
<declare-styleable name="ContentView">
<attr name="layout_empty" format="reference"/>
<attr name="layout_error" format="reference"/>
<attr name="layout_loading" format="reference"/>
</declare-styleable>
com.test.ContentView.java:
protected void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ContentView);
int emptyResId = a.getResourceId(R.styleable.ContentView_layout_empty, 0);
int errorResId = a.getResourceId(R.styleable.ContentView_layout_error, 0);
int loadingResId = a.getResourceId(R.styleable.ContentView_layout_loading, 0);
a.recycle();
}
}
R.txt:
......
int attr layout_empty 0x7f03007a
int attr layout_error 0x7f03007b
int attr layout_loading 0x7f03007c
......
int[] styleable ContentView { 0x7f03007a, 0x7f03007b, 0x7f03007c }
int styleable ContentView_layout_empty 0
int styleable ContentView_layout_error 1
int styleable ContentView_layout_loading 2
.......
因styleable资源是定义在宿主中,ContentView也是定义在宿主中,因此ContentView使用的将是宿主的资源ID。
在插件中应用styleable资源
<com.test.ContentView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_loading="@drawable/test_drawable"
app:layout_error="@drawable/test_drawable"
app:layout_empty="@drawable/test_drawable"/>
在插件中使用该styleable资源的时候,如果插件资源独立(也就是为这个styleable中的attr重新分配资源ID),那么将导致该资源ID与宿主资源ID不一致。
Android底层AXML解析
Android binary xml 文件格式参考:
Android底层xml文件解析过程(对应context.obtainStyledAttributes方法):
core/jni/android_util_AssetManager.cpp
static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject clazz,
jlong themeToken,
jint defStyleAttr,
jint defStyleRes,
jlong xmlParserToken,
jintArray attrs,
jintArray outValues,
jintArray outIndices)
{
......
// 解析styleable数组长度
const jsize NI = env->GetArrayLength(attrs);
......
// 解析styleable数组内容
jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
......
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
ssize_t block = 0;
uint32_t typeSetFlags;
// 尝试解析styleable数组对应的各个资源
for (jsize ii=0; ii<NI; ii++) {
// curIdent为styleable数组中attr资源的ID
const uint32_t curIdent = (uint32_t)src[ii];
DEBUG_STYLES(ALOGI("RETRIEVING ATTR 0x%08x...", curIdent));
// Try to find a value for this attribute... we prioritize values
// coming from, first XML attributes, then XML style, then default
// style, and finally the theme.
value.dataType = Res_value::TYPE_NULL;
value.data = 0;
typeSetFlags = 0;
config.density = 0;
// Skip through XML attributes until the end or the next possible match.
// 尝试查找ID匹配的attr:
// curXmlAttr为当前ix对应attr资源的ID
// 从这里可以看出attr资源的ID在xml中是按升序排列的
// styleable数组中attr资源的ID也是按升序排列的
while (ix < NX && curIdent > curXmlAttr) {
ix++;
curXmlAttr = xmlParser->getAttributeNameResID(ix);
}
// Retrieve the current XML attribute if it matches, and step to next.
// 解析该attr对应的资源信息
if (ix < NX && curIdent == curXmlAttr) {
block = kXmlBlock;
xmlParser->getAttributeValue(ix, &value);
ix++;
curXmlAttr = xmlParser->getAttributeNameResID(ix);
DEBUG_STYLES(ALOGI("-> From XML: type=0x%x, data=0x%08x",
value.dataType, value.data));
}
......
}
......
}
libs/androidfw/ResourceTypes.cpp
// 解析idx对应的attr资源的ID
uint32_t ResXMLParser::getAttributeNameResID(size_t idx) const
{
int32_t id = getAttributeNameID(idx);
if (id >= 0 && (size_t)id < mTree.mNumResIds) {
return dtohl(mTree.mResIds[id]);
}
return 0;
}
// 解析idx对应的attr资源的名称(name)在ResStringPool中的index,
// 这个index同时也是该attr资源的ID在ResourceMap中index
int32_t ResXMLParser::getAttributeNameID(size_t idx) const
{
if (mEventCode == START_TAG) {
const ResXMLTree_attrExt* tag = (const ResXMLTree_attrExt*)mCurExt;
if (idx < dtohs(tag->attributeCount)) {
const ResXMLTree_attribute* attr = (const ResXMLTree_attribute*)
(((const uint8_t*)tag)
+ dtohs(tag->attributeStart)
+ (dtohs(tag->attributeSize)*idx));
return dtohl(attr->name.index);
}
}
return -1;
}
// mResIds即ResourceMap数组
status_t ResXMLTree::setTo(const void* data, size_t size, bool copyData)
{
......
if (type == RES_STRING_POOL_TYPE) {
mStrings.setTo(chunk, size);
} else if (type == RES_XML_RESOURCE_MAP_TYPE) {
mResIds = (const uint32_t*)
(((const uint8_t*)chunk)+dtohs(chunk->headerSize));
mNumResIds = (dtohl(chunk->size)-dtohs(chunk->headerSize))/sizeof(uint32_t);
} else if (type >= RES_XML_FIRST_CHUNK_TYPE
&& type <= RES_XML_LAST_CHUNK_TYPE) {
......
break;
} else {
XML_NOISY(printf("Skipping unknown chunk!\n"));
}
......
}
分析源码可知,Android系统在解析Android binary xml文件的时候,是根据attr资源的ID进行匹配和解析,结合开篇的例子可以推断:
-
ContentView使用宿主的styleable中的attr资源的ID进行解析
-
如果子插件重新为attr资源分配资源ID
那么将导致__该styleable(attr)资源无法被正确解析__
解决
- 保持宿主和插件styleable(attr)资源__ID和顺序__一致,例如利用__aapt2__的__--stable-ids__参数
- 禁止在插件中使用宿主定义的styleable资源
- 其他待续