源代码分析
??从Context类的obtainStyledAttributes()方法一直往下看,
@NonNull
public final TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
@NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
return getTheme().obtainStyledAttributes(
set, attrs, defStyleAttr, defStyleRes);
}
??首先调用了getTheme,这个Context一般都是Activity实例,在这调用的是ContextThemeWrapper类的getTheme,这个其实就是得到当前Activity的主题。Activity的Theme可以在Manifest文件中通过Activity的标签属性android:theme来进行设置,如果没有设置,这个主题就是在Application的标签属性android:theme来进行设置。如果这俩都没有进行设置,则会根据当前的目标SDK版本来选择默认的Theme。选择的默认Theme可以看下面的代码:
@UnsupportedAppUsage
public static int selectDefaultTheme(int curTheme, int targetSdkVersion) {
return selectSystemTheme(curTheme, targetSdkVersion,
com.android.internal.R.style.Theme,
com.android.internal.R.style.Theme_Holo,
com.android.internal.R.style.Theme_DeviceDefault,
com.android.internal.R.style.Theme_DeviceDefault_Light_DarkActionBar);
}
public static int selectSystemTheme(int curTheme, int targetSdkVersion, int orig, int holo,
int dark, int deviceDefault) {
if (curTheme != ID_NULL) {
return curTheme;
}
if (targetSdkVersion < Build.VERSION_CODES.HONEYCOMB) {
return orig;
}
if (targetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
return holo;
}
if (targetSdkVersion < Build.VERSION_CODES.N) {
return dark;
}
return deviceDefault;
}
??接着会去调用Theme类的obtainStyledAttributes()方法:
@NonNull
public TypedArray obtainStyledAttributes(@Nullable AttributeSet set,
@NonNull @StyleableRes int[] attrs, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
synchronized (mLock) {
return mThemeImpl.obtainStyledAttributes(this, set, attrs, defStyleAttr,
defStyleRes);
}
}
??可见又调用到ResourcesImpl类的内部类ThemeImpl的方法obtainStyledAttributes
@NonNull
TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
AttributeSet set,
@StyleableRes int[] attrs,
@AttrRes int defStyleAttr,
@StyleRes int defStyleRes) {
final int len = attrs.length;
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
final XmlBlock.Parser parser = (XmlBlock.Parser) set;
mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
array.mDataAddress, array.mIndicesAddress);
array.mTheme = wrapper;
array.mXml = parser;
return array;
}
??该方法先初始化TypedArray,接着将参数set转化成XmlBlock.Parser类,该类对应着C++层中的ResXMLParser,它是解析二进制资源文件的工具类,包含着布局xml文件中该控件对应的所有属性的值。最后调用AssetManager类实例mAssets的applyStyle方法,最后将结果返回。
TypedArray的初始化
先看看TypedArray的初始化
static TypedArray obtain(Resources res, int len) {
TypedArray attrs = res.mTypedArrayPool.acquire();
if (attrs == null) {
attrs = new TypedArray(res);
}
attrs.mRecycled = false;
attrs.mAssets = res.getAssets();
attrs.mMetrics = res.getDisplayMetrics();
attrs.resize(len);
return attrs;
}
??首先从res的缓存池里获取,如果没有获取成功,初始化TypedArray。然后设置相应的属性,之后调用attrs.resize(len)方法,设置长度大小。可以看到这个长度大小是从obtainStyledAttributes()函数的参数attrs的长度,这个参数就是要找到的属性的整数值的集合。
private void resize(int len) {
mLength = len;
final int dataLen = len * STYLE_NUM_ENTRIES;
final int indicesLen = len + 1;
final VMRuntime runtime = VMRuntime.getRuntime();
if (mDataAddress == 0 || mData.length < dataLen) {
mData = (int[]) runtime.newNonMovableArray(int.class, dataLen);
mDataAddress = runtime.addressOf(mData);
mIndices = (int[]) runtime.newNonMovableArray(int.class, indicesLen);
mIndicesAddress = runtime.addressOf(mIndices);
}
}
??这个方法主要就是设置TypedArray 类的成员变量mLength,mData,mDataAddress,mIndices,mIndicesAddress。前面那篇文章 Android TypedArray简单分析(一)使用 也介绍了该类成员mData和mIndices。可以看到设置的对应长度就是按照属性的大小来设置的。STYLE_NUM_ENTRIES的值是7,mData数组的长度为7*len,mIndices的长度为len+1。mData,mIndices这两个数组都是通过runtime.newNonMovableArray方法来定义的,表明数组的地址不能移动。接着又通过runtime.addressOf()方法获得数组的地址来指定mDataAddress和mIndicesAddress的值。mDataAddress和mIndicesAddress会作为参数传递到c++层,这样在c++层就能通过地址找到mData和mIndices。
AssetManager类实例mAssets的applyStyle方法
??再接着就是调用AssetManager类实例mAssets的applyStyle方法,可以看到参数5和参数6确实是TypedArray类的mDataAddress和mIndicesAddress:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
@Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
long outIndicesAddress) {
Objects.requireNonNull(inAttrs, "inAttrs");
synchronized (this) {
ensureValidLocked();
nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
outIndicesAddress);
}
}
??下面就会调用本地方法nativeApplyStyle()。在调用本地方法之前,会将parser的成员mParseState 作为参数传进去,它是C++层中ResXMLParser类对象的地址,等进入到C++层中,通过它就可以找到ResXMLParser类对象。上面也说了,它包含着布局xml文件中该控件对应的所有属性的值。 ??从这里能看到Theme通过成员变量ThemeImpl实现,而ThemeImpl则通过它的成员变量mTheme指向C++层的类对象来实现。ThemeImpl类的成员类型AssetManager变量mAssets则通过其成员变量mObject指向C++层的类对象来实现。 ??看下C++层实现,实现在platform\frameworks\base\core\jni\android_util_AssetManager.cpp
static void NativeApplyStyle(JNIEnv* env, jclass , jlong ptr, jlong theme_ptr,
jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr,
jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
CHECK(theme->GetAssetManager() == &(*assetmanager));
(void) assetmanager;
ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr);
uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr);
jsize attrs_len = env->GetArrayLength(java_attrs);
jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
if (attrs == nullptr) {
return;
}
ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr),
static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len,
out_values, out_indices);
env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
}
??从这看到,Java层的AssetManager对应C++层的AssetManager2类,Java层的Theme对应C++层的Theme类。还通过参数xml_parser_ptr找到对应的ResXMLParser类对象, ??out_values 和out_indices 分别是上面说的Java类TypedArray类对象的mData和mIndices数组的地址。 ??再往下就是调用ApplyStyle()函数,它在platform\frameworks\base\libs\androidfw\AttributeResolution.cpp,看下它
base::expected<std::monostate, IOError> ApplyStyle(Theme* theme, ResXMLParser* xml_parser,
uint32_t def_style_attr,
uint32_t def_style_resid,
const uint32_t* attrs, size_t attrs_length,
uint32_t* out_values, uint32_t* out_indices) {
DEBUG_LOG("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme,
def_style_attr, def_style_resid, xml_parser);
int indices_idx = 0;
const AssetManager2* assetmanager = theme->GetAssetManager();
uint32_t def_style_theme_flags = 0U;
const auto default_style_bag = GetStyleBag(theme, def_style_attr, def_style_resid,
&def_style_theme_flags);
if (IsIOError(default_style_bag)) {
return base::unexpected(GetIOError(default_style_bag.error()));
}
uint32_t xml_style_theme_flags = 0U;
const auto xml_style_bag = GetXmlStyleBag(theme, xml_parser, &def_style_theme_flags);
if (IsIOError(xml_style_bag)) {
return base::unexpected(GetIOError(xml_style_bag.error()));
}
BagAttributeFinder def_style_attr_finder(default_style_bag.value_or(nullptr));
BagAttributeFinder xml_style_attr_finder(xml_style_bag.value_or(nullptr));
XmlAttributeFinder xml_attr_finder(xml_parser);
for (size_t ii = 0; ii < attrs_length; ii++) {
const uint32_t cur_ident = attrs[ii];
DEBUG_LOG("RETRIEVING ATTR 0x%08x...", cur_ident);
AssetManager2::SelectedValue value{};
uint32_t value_source_resid = 0;
const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
if (xml_attr_idx != xml_attr_finder.end()) {
Res_value attribute_value{};
xml_parser->getAttributeValue(xml_attr_idx, &attribute_value);
value.type = attribute_value.dataType;
value.data = attribute_value.data;
value_source_resid = xml_parser->getSourceResourceId();
DEBUG_LOG("-> From XML: type=0x%x, data=0x%08x", value.type, value.data);
}
if (value.type == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident);
if (entry != xml_style_attr_finder.end()) {
value = AssetManager2::SelectedValue(*xml_style_bag, *entry);
value.flags |= xml_style_theme_flags;
value_source_resid = entry->style;
DEBUG_LOG("-> From style: type=0x%x, data=0x%08x, style=0x%08x", value.type, value.data,
value_source_resid);
}
}
if (value.type == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident);
if (entry != def_style_attr_finder.end()) {
value = AssetManager2::SelectedValue(*default_style_bag, *entry);
value.flags |= def_style_theme_flags;
value_source_resid = entry->style;
DEBUG_LOG("-> From def style: type=0x%x, data=0x%08x, style=0x%08x", value.type, value.data,
entry->style);
}
}
if (value.type != Res_value::TYPE_NULL) {
auto result = theme->ResolveAttributeReference(value);
if (UNLIKELY(IsIOError(result))) {
return base::unexpected(GetIOError(result.error()));
}
DEBUG_LOG("-> Resolved attr: type=0x%x, data=0x%08x", value.type, value.data);
} else if (value.data != Res_value::DATA_NULL_EMPTY) {
if (auto attr_value = theme->GetAttribute(cur_ident)) {
value = *attr_value;
DEBUG_LOG("-> From theme: type=0x%x, data=0x%08x", value.type, value.data);
auto result = assetmanager->ResolveReference(value, true );
if (UNLIKELY(IsIOError(result))) {
return base::unexpected(GetIOError(result.error()));
}
DEBUG_LOG("-> Resolved theme: type=0x%x, data=0x%08x", value.type, value.data);
}
}
if (value.type == Res_value::TYPE_REFERENCE && value.data == 0U) {
DEBUG_LOG("-> Setting to @null!");
value.type = Res_value::TYPE_NULL;
value.data = Res_value::DATA_NULL_UNDEFINED;
value.cookie = kInvalidCookie;
}
DEBUG_LOG("Attribute 0x%08x: type=0x%x, data=0x%08x", cur_ident, value.type, value.data);
out_values[STYLE_TYPE] = value.type;
out_values[STYLE_DATA] = value.data;
out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(value.cookie);
out_values[STYLE_RESOURCE_ID] = value.resid;
out_values[STYLE_CHANGING_CONFIGURATIONS] = value.flags;
out_values[STYLE_DENSITY] = value.config.density;
out_values[STYLE_SOURCE_RESOURCE_ID] = value_source_resid;
if (value.type != Res_value::TYPE_NULL || value.data == Res_value::DATA_NULL_EMPTY) {
out_indices[++indices_idx] = ii;
}
out_values += STYLE_NUM_ENTRIES;
}
out_indices[0] = indices_idx;
return {};
}
??14行代码处,GetStyleBag()方法,得到的结果是从主题中设置的属性,或者自己设置的style中得到结果集default_style_bag。如果主题中能取到对应的属性,就不会再去取自己设置的style中取值。如果主题中未设置对应属性,则会去自己设置的style中取值。这个结果集对应着上文说的优先级 3、参数 defStyleAttr和4、参数 defStyleRes。 ??22行代码处,GetXmlStyleBag()方法,通过布局文件中设置的对应style属性中取到结果集xml_style_bag。这个对应着上文说的优先级 2、XML控件设置的style属性 ??27、28行将上面两个结果集再转成BagAttributeFinder类对象,后面会通过该对象,去查询需要查找的属性值。 ??29行得到XmlAttributeFinder对象,这个是从布局文件的直接设置的属性中查询对应结果。这个对应着上文说的优先级 1、XML控件里直接设置属性 ??接着,是一个循环。attrs_length就是需要查询的属性队列的长度。通过这个循环,将需要查询的数据的值,都设置到TypedArray中的成员变量mData和mIndices中。 ??45行,先从xml_attr_finder中搜索,这个就是从布局文件设置的单个属性中查找,如果从这里找到了正确值,可以看到,就不会再从其他地方取值了。所以布局XML控件里直接设置属性,优先级最高。 ??56行,如果上面没有取到正确值,则会从xml_style_attr_finder中取值。它对应布局文件中设置的style属性。所以XML控件设置的style属性的优先级排在第二。 ??最后会从def_style_attr_finder中取值。它对应着,对应的主题中设置的属性值或者自己定义设置的style。并且上面说了,这俩相比,会先取主题中设置的属性值,也就是参数 defStyleAttr优先级第三、参数 defStyleRes优先级第四。这样就能解释清楚 Android TypedArray简单分析(一)使用 文章中例子取值顺序的问题了。 ??也可以看到,out_indices中从第1位开始存储属性的序列值。第0位存储的是indices_idx,它就是查找到属性值得数量。 ??上面是说的ApplyStyle()方法的整体流程,具体的细节见下文。
|