ソースコード分析
ContextクラスのobtainStyledAttributes()メソッドから下を見ると、
/**
* Retrieve styled attribute information in this Context's theme. See
* {@link android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* for more information.
*
* @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
*/
@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 が呼び出されます。これは実際には現在のアクティビティのテーマです。アクティビティのテーマは、アクティビティのラベル属性 android:theme を通じてマニフェスト ファイルで設定できます。設定されていない場合、テーマはアプリケーションのラベル属性 android:theme に設定されます。これらのどちらも設定されていない場合は、現在のターゲット SDK バージョンに従ってデフォルトのテーマが選択されます。選択したデフォルトのテーマには、次のコードが表示されます。
/**
* Returns the most appropriate default theme for the specified target SDK version.
* <ul>
* <li>Below API 11: Gingerbread
* <li>APIs 12 thru 14: Holo
* <li>APIs 15 thru 23: Device default dark
* <li>APIs 24 and above: Device default light with dark action bar
* </ul>
*
* @param curTheme The current theme, or 0 if not specified.
* @param targetSdkVersion The target SDK version.
* @return A theme resource identifier
* @hide
*/
@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);
}
/** @hide */
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 クラスの getStyledAttributes() メソッドを呼び出します。
@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 のメソッド getStyledAttributes が呼び出されていることがわかります。
@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);
// XXX note that for now we only work with compiled XML files.
// To support generic XML files we will need to manually parse
// out the attributes from the XML file (applying type information
// contained in the resources and such).
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 を初期化し、次にパラメータ セットを 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;
// Reset the assets, which may have changed due to configuration changes
// or further resource loading.
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 の簡単な分析 (1) 使い方」でも、このクラスのメンバーである mData と mIndices を紹介しました。属性のサイズに応じて、対応する設定の長さが設定されていることがわかります。STYLE_NUM_ENTRIES の値は 7、mData 配列の長さは 7*len、mIndices の長さは len+1 です。2 つの配列 mData と mIndices は runtime.newNonMovableArray メソッドによって定義され、配列のアドレスを移動できないことを示します。次に、runtime.addressOf() メソッドを通じて配列のアドレスを取得し、mDataAddress と mIndicesAddress の値を指定します。mDataAddress と mIndicesAddress はパラメータとして C++ 層に渡されるため、mData と mIndices は C++ 層でアドレスによって見つけることができます。
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) {
// Need to synchronize on AssetManager because we will be accessing
// the native implementation of AssetManager.
ensureValidLocked();
nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
outIndicesAddress);
}
}
ネイティブ メソッドnativeApplyStyle()が以下で呼び出されます。ローカル メソッドを呼び出す前に、パーサーのメンバー mParseState がパラメーターとして渡されます。これは、C++ レイヤーの ResXMLParser クラス オブジェクトのアドレスです。C++ レイヤーに入った後、C++ レイヤーを通じて ResXMLParser クラス オブジェクトを見つけることができます。前述したように、レイアウト XML ファイル内のコントロールに対応するすべての属性の値が含まれています。
ここから、Theme はメンバー変数 ThemeImpl を通じて実装され、ThemeImpl は C++ 層のクラス オブジェクトを指すメンバー変数 mTheme を通じて実装されていることがわかります。ThemeImpl クラスのメンバー型 AssetManager 変数 mAssets は、そのメンバー変数 mObject を通じて C++ 層のクラス オブジェクトを指すことによって実現されます。
C++ レイヤーの実装を確認します。これは platform\frameworks\base\core\jni\android_util_AssetManager.cpp に実装されています。
static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, 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クラスに対応していることが分かる。対応する ResXMLParser クラス オブジェクトもパラメーター xml_parser_ptr を通じて見つかります
。out_values と out_indices はそれぞれ、前述の Java クラス TypedArray クラス オブジェクトの mData 配列と mIndices 配列のアドレスです。
次に、platform\frameworks\base\libs\androidfw\AttributeResolution.cpp にある applyStyle() 関数を呼び出します。
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();
// Load default style from attribute, if specified...
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()));
}
// Retrieve the style resource ID associated with the current XML tag's style attribute.
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);
// Now iterate through all of the attributes that the client has requested,
// filling in each with whatever data we can find.
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;
// 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.
// Walk through the xml attributes looking for the requested attribute.
const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
if (xml_attr_idx != xml_attr_finder.end()) {
// We found the attribute we were looking for.
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) {
// Walk through the style class values looking for the requested attribute.
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) {
// Walk through the default style values looking for the requested attribute.
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) {
// Take care of resolving the found resource to its final value.
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 we still don't have a value for this attribute, try to find it in the theme!
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 /* cache_value */);
if (UNLIKELY(IsIOError(result))) {
return base::unexpected(GetIOError(result.error()));
}
DEBUG_LOG("-> Resolved theme: type=0x%x, data=0x%08x", value.type, value.data);
// TODO: set value_source_resid for the style in the theme that was used.
}
}
// Deal with the special @null value -- it turns back to TYPE_NULL.
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);
// Write the final value back to Java.
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 must NOT be nullptr.
out_indices[++indices_idx] = ii;
}
out_values += STYLE_NUM_ENTRIES;
}
// out_indices must NOT be nullptr.
out_indices[0] = indices_idx;
return {
};
}
コードの 14 行目で、GetStyleBag() メソッドは、テーマに設定された属性、または自分で設定したスタイルから結果セット default_style_bag を取得します。テーマ内で対応する属性が取得できた場合は、自身で設定したスタイルの値は取りません。対応する属性がテーマに設定されていない場合は、独自に設定されたスタイルに移動して値を取得します。この結果セットは、前述の優先度 3、パラメータ defStyleAttrおよび 4、パラメータ defStyleResに対応します。
コードの 22 行目で、GetXmlStyleBag() メソッドは、レイアウト ファイル内の対応するスタイル属性セットから結果セット xml_style_bag を取得します。これは、前述の優先度 2に対応し、 XML コントロールによって設定されたスタイル属性 27 および 28 は、
上記の 2 つの結果セットを BagAttributeFinder クラス オブジェクトに変換します。このオブジェクトは、後で検索する属性値をクエリするために使用されます。
行 29 は XmlAttributeFinder オブジェクトを取得します。これは、レイアウト ファイルの直接設定された属性から対応する結果をクエリします。これは、前述の優先度 1に対応し、 XML コントロールでプロパティを直接設定し
、その後のサイクルになります。attrs_length は、クエリされる属性キューの長さです。このサイクルを通じて、クエリ対象のデータの値が TypedArray のメンバー変数 mData と mIndices に設定されます。
45行目、まずxml_attr_finderから検索します。これはレイアウトファイルに設定されている単一の属性から検索するもので、ここで正しい値が見つかった場合、他の場所から値を取得することはないことがわかります。それでレイアウトはXML コントロールでプロパティを直接設定することが最も優先されます。
56行目、上記で正しい値が取得できなかった場合は、xml_style_attr_finderから値を取得します。レイアウトファイルに設定されているstyle属性に相当します。したがって、XML コントロールによって設定されたスタイル属性は2 番目に優先されます。
最後に、値は def_style_attr_finder から取得されます。対応するテーマに設定されている属性値、または独自に定義したスタイルに対応します。そして、上で述べたように、この 2 つを比較すると、テーマに設定されている属性値が最初に取得されます。つまり、パラメータ defStyleAttr が3 番目に優先され、パラメータ defStyleRes が4 番目に優先されます。Android TypedArray の簡単な解析 (1)記事内の例の値の順序の問題についてはこれで説明できます。
コードの 80 行目で、取得した値が参照型の場合は、theme->ResolveAttributeReference(value) を呼び出して解析を続けます。
また、属性のシーケンス値が最初のビットから out_indices に格納されていることがわかります。0 番目のビットには、見つかった属性値の数である indices_idx が格納されます。
上記は、ApplyStyle() メソッドの全体的な処理です。詳細については、以下を参照してください。Android は、テーマの対応する属性の結果、または設定したスタイルの結果を取得します。