Android
style

Viewにxmlに書いた要素やThemeが反映されるまでのコードを追ってみたメモ

More than 3 years have passed since last update.

ただのメモです。

全体的に追いきれていないので、間違っていたらご指摘ください、、

AppCompatActivityなどを継承していると結構違うコードとなりますが、基本的には同じようなことをしているはずです。

最初にまとめるとこんなかんじです。

LayoutInflaterでxmlから要素を取り出す

AttributeSet attributeSet = Xml.asAttributeSet(getResources().getLayout(R.layout.activity_main));

Viewでテーマをコンテキストから取得して適応した要素一覧をTypedArrayで取得

 final Resources.Theme theme = context.getTheme();

TypedArray a = theme.obtainStyledAttributes(
attributeSet, textViewStylable, defStyleAttr, defStyleRes);)

それを反映する

        int n = a.getIndexCount();

for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.TextView_text:
text = a.getText(attr);
break;


追ってみた


xmlから要素を取得するまで

おなじみのおまじない

            setContentView(R.layout.activity_main);

ActivityでWindowのクラスのsetContentViewを呼ぶ

    public void setContentView(@LayoutRes int layoutResID) {

getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

PhoneWindowでmLayoutInflater.inflateを呼ぶ

    @Override

public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}

以下のようにAttributeSetをxmlから取り出しています。

LayoutInflater内

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {

final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}

final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {

synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;

try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}

if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}

final String name = parser.getName();

if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}

if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}

rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

ViewGroup.LayoutParams params = null;

if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}

if (DEBUG) {
System.out.println("-----> start inflating children");
}

// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);

if (DEBUG) {
System.out.println("-----> done inflating children");
}

// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}

} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

return result;
}
}

それをcreateViewFromTagでViewのコンストラクタをリフレクションで呼び出したりなどがあるのですが、ここでは書きません。

とりあえずレイアウトの内容が取得できそうなattributeSetが取得出来ました。


xmlから取得した要素をViewに反映するまで


TextViewコンストラクタ内

このattributeSetがViewのコンストラクタで渡ってきます。

    public TextView(Context context, @Nullable AttributeSet attrs) {

this(context, attrs, com.android.internal.R.attr.textViewStyle);
}

attributeSetを利用してこんな感じでTypedArrayを取得します(ここではTextViewを例に話します)

 final Resources.Theme theme = context.getTheme();

TypedArray a = theme.obtainStyledAttributes(
attributeSet, textViewStylable, defStyleAttr, defStyleRes);)

となっており、getTheme()でActivityはContextThemeWrapperを継承しているので、そのメンバ変数にセットされているResources.Themeを取得します ここでManifestなどで設定したテーマの反映がなされるようです

ここまで読んでいる方はご存知かもしれませんが、Contextに設定されているテーマによってViewのテーマが変わります。


引数について


  • textViewStylableはdeclare-styleableで定義された要素(例:com.android.internal.R.styleable.TextView)

    layout_widthなどはdeclare-styleableで定義されたものレイアウトのようです。

   <TextView

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />

参考: http://y-anz-m.blogspot.jp/2011/06/declare-styleable.html


  • defStyleAttrはcom.android.internal.R.attr.textViewStyleなどのattrで テーマで利用する要素一覧のようです

  • defStyleResはAPI21からのようで、デフォルトのスタイルのようです。

styleableやattrはここで定義されていたりします。

https://android.googlesource.com/platform/frameworks/base/+/HEAD/core/res/res/values/attrs.xml

TextViewStyleはこちら?

https://android.googlesource.com/platform/frameworks/base/+/HEAD/core/res/res/values/styles.xml

あとはa(TypedArray)から要素を取り出してViewの要素に反映させていくだけです。

        int n = a.getIndexCount();

for (int i = 0; i < n; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.TextView_text:
text = a.getText(attr);
break;


Resources.Theme#obtainStyledAttributes()内

そうなると、Resources.Theme#obtainStyledAttributes()の中が気になります。

        public TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) {

final int len = attrs.length;
final TypedArray array = TypedArray.obtain(Resources.this, len);
array.mTheme = this;
AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, array.mData, array.mIndices);
return array;
}

AssetManager.java

native static final boolean applyStyle(long theme,

int defStyleAttr, int defStyleRes, long xmlParser,
int[] inAttrs, int[] outValues, int[] outIndices);

JNIによってC++のソースにに突入します。

https://android.googlesource.com/platform/frameworks/base.git/+/android-4.3_r2.1/core/jni/android_util_AssetManager.cpp#987

styleやthemeから取得してTypedArrayに代入していっているような気がします(自信ないです)

AssetManagerがよしなにやってくれているようですがそこがちょっと追いきれていません、、