- 资源 Resource
旧版本的Android Studio,可以直接找到这个R.java类,它是由AAPT工具在编译打包过程中,自动生成的。 该类主要是一堆的常量。但是新版的Android Studio它已经隐藏起来了。通过打开apk,可以看到带有arsc后缀的文件。打开它,可以看到所有的资源ID。 右边表格第一列就是资源ID,它是用一个16进制表示,0x表示16进制,7f对比多个应用都一样,08是资源类型, drawable(08),color(06),后面4位代表每组类型的资源顺序,后面4位是会变化的,每次打包都可能会变化。 这个ID就是整个资源表的一个主键,你可以通过它就找到关于这个资源的其他信息,包括资源类型,名称,文件对应目录等。还可以通过另外一种方式,找到所有的资源ID。
/app/build/intermediates/runtime_symbol_list/debug/R.txt
int anim abc_tooltip_enter 0x7f01000a
int anim abc_tooltip_exit 0x7f01000b
- 资源内容类型 TypeValue
文件资源查找是在Native层实现的,解析好后会存放在TypeValue中
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
...
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
...
}
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
...
final int cookie = nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
...
return true;
}
如果把Resource理解成是一个文件,文件类型也有很多种,而TypeValue 是存储解析后的文件内容。而内容的类型也有很多种,下面分析几种常见的。先来看一下常见的资源具体内容有哪些,怎么配置的。
颜色
<color name="purple_500">#FF6200EE</color>
大小
<dimen name="pull_up_drawer_height">64.0000dp</dimen>
文字
<string name="app_name">My Application</string>
整数
<integer name="item2">11</integer>
数值
<bool name="item3">true</bool>
图形
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/Blue_10"/>
</shape>
布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="button"
android:id="@+id/button"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
Native
解析后,把逻辑存储到TypeValue
中,针对每一种内容,有不同的逻辑表示。其中它的type
属性用来表示数据类型,data
用来存储数据。
- 如果是
dimen
,那它的属性type
用TYPE_DIMENSION
表示;
public float getDimension(@DimenRes int id) throws NotFoundException {
...
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(value.data, impl.getDisplayMetrics());
}
...
}
public static float complexToDimension(int data, DisplayMetrics metrics)
{
return applyDimension(
(data>>COMPLEX_UNIT_SHIFT)&COMPLEX_UNIT_MASK,
complexToFloat(data),
metrics);
}
Dimen
也是用一个整数表示,但是它包括了单位,小数,整数部分,其中单位是低4位表示,通过位运算(data>>
COMPLEX_UNIT_SHIFT
)&
COMPLEX_UNIT_MASK
,可以取出单位。
- 如果是
color
,那它的属性type
用一个整数表示,整数在TYPE_FIRST_INT
到TYPE_LAST_INT
之前。
@ColorInt
public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
...
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
return value.data;
}
...
}
配置中的颜色值是通过16进制表示,但是Native层解析后的颜色是一个10进制,可以通过转换得到16进制
String hexColor = "0x" Integer.
toHexString
(color)
- 如果是
string
,那它的属性type
用TYPE_STRING
表示;
字符串的加载跟color
,dimen
差不多,但是它多了一层缓存逻辑,每个应用都会有一个ApkAssets去管理,使用StringBlock
字符串块去缓存了应用中的字符串资源。前面讲到Native
层解析完后,会把数据存放到TypeValue
,字符类型的TypeValue,它的data
属性,代表着字符资源索引id
,Java
层会继续通过这个资源索引id
去请求字符串。
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
...
synchronized (this) {
final int cookie = nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
...
if (outValue.type == TypedValue.TYPE_STRING) {
if ((outValue.string = getPooledStringForCookie(cookie, outValue.data)) == null) {
return false;
}
}
return true;
}
}
//字符串块
CharSequence getStringFromPool(int idx) {
if (mStringBlock == null) {
return null;
}
synchronized (this) {
return mStringBlock.getSequence(idx);
}
}
//读取缓存
public CharSequence getSequence(int idx) {
synchronized (this) {
if (mStrings != null) {
CharSequence res = mStrings[idx];
if (res != null) {
return res;
}
} else if (mSparseStrings != null) {
CharSequence res = mSparseStrings.get(idx);//如果缓存中有就拿缓存的
if (res != null) {
return res;
}
} else {
final int num = nativeGetSize(mNative);
if (mUseSparse && num > 250) {
mSparseStrings = new SparseArray<CharSequence>();
} else {
mStrings = new CharSequence[num];
}
}
String str = nativeGetString(mNative, idx);//通过id请求字符串
if (str == null) {
return null;
}
CharSequence res = str;
...
//style
...
if (res != null) {
if (mStrings != null) mStrings[idx] = res;
else mSparseStrings.put(idx, res);//缓存数据
}
return res;
}
}
字符串是支持Html style
,它的逻辑实现也是在StringBlock
for (int styleIndex = 0; styleIndex < style.length; styleIndex += 3) {
int styleId = style[styleIndex];
...
if (styleTag.equals("b")) {
mStyleIDs.boldId = styleId;
}
...
} else if (styleTag.equals("marquee")) {
mStyleIDs.marqueeId = styleId;
}
}
res = applyStyles(str, style, mStyleIDs);
虽然做了缓存,但是框架并不会一直持有这些资源,在应用退出之后,会释放掉的。
- 如果是
drawable
它会先找到文件的对应类型,文件类型有很多,前面有讲到,然后再根据不同文件类型,去解析文件内容。
ResourcesImpl.java
private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
int id, int density) {
...
final String typeName = getResourceTypeName(id);
if (typeName != null && typeName.equals("color")) {
dr = loadColorOrXmlDrawable(wrapper, value, id, density, file);
} else {
dr = loadXmlDrawable(wrapper, value, id, density, file);
}
...
}
之所以有颜色判断区分,是因为getDrawable
方法可以传入colorId
最后得到Drawable
对象,只不过它是一个颜色相关的ColorDrawable
对象
Drawable drawable = getResources().getDrawable(R.drawable.
bg
,null);
Drawable drawable1 = getDrawable(R.color.
purple_500
);//color
但是drawable
还有很多种类可以配置在xml
中,也是通过inflateFromTag
文件内容的标签生成。比如标签shape
,代表的是我们常用的图形。
DrawableInflater.java
@NonNull Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, int density, @Nullable Theme theme)
throws XmlPullParserException, IOException {
...
Drawable drawable = inflateFromTag(name);
if (drawable == null) {
drawable = inflateFromClass(name);
}
drawable.setSrcDensityOverride(density);
drawable.inflate(mRes, parser, attrs, theme);
...
return drawable;
}
private @Nullable Drawable inflateFromTag(@NonNull String name) {
switch (name) {
case "selector":
return new StateListDrawable();
case "layer-list":
return new LayerDrawable();
...
case "color":
return new ColorDrawable();
case "shape":
return new GradientDrawable();
...
default:
return null;
}
}
类型很多,为了方便看代码,我删除掉一些类型。inflateFromTag
方法就是根据不同配置标签,实例化不同的对象。
但是实例化好对象之后,如何根据具体的对象,去初始化不同的属性?因为每一种Drawable
他们对象属性都是不一样的。通过drawable.inflate(mRes, parser, attrs, theme);
会继续初始化配置中的属性参数。
比如常用的图形shape
GradientDrawable.java
private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
TypedArray a;
int type;
final int innerDepth = parser.getDepth() + 1;
int depth;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth=parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth) {
continue;
}
String name = parser.getName();
if (name.equals("size")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize);
updateGradientDrawableSize(a);
a.recycle();
} else if (name.equals("gradient")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient);
updateGradientDrawableGradient(r, a);
a.recycle();
} else if (name.equals("solid")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid);
updateGradientDrawableSolid(a);
a.recycle();
} else if (name.equals("stroke")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke);
updateGradientDrawableStroke(a);
a.recycle();
} else if (name.equals("corners")) {
a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners);
updateDrawableCorners(a);
a.recycle();
} else if (name.equals("padding")) {
a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding);
updateGradientDrawablePadding(a);
a.recycle();
} else {
Log.w("drawable", "Bad element under <shape>: " + name);
}
}
}
但是如果它是一张图片资源,比如getDrawable(R.drawable.
img
)
,它是不需要配置的。所以它并不需要走解析文件内容的流程,直接通过读取文件流解析成BitmapDrawable
,通过文件前后缀名称判断是否需要走解析流程。
ResourcesImpl.java
if (file.endsWith(".xml")) {
final String typeName = getResourceTypeName(id);
if (typeName != null && typeName.equals("color")) {
dr = loadColorOrXmlDrawable(wrapper, value, id, density, file);
} else {
dr = loadXmlDrawable(wrapper, value, id, density, file);
}
} else if (file.startsWith("frro://")) {
...
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
final AssetInputStream ais = (AssetInputStream) is;
dr = decodeImageDrawable(ais, wrapper, value);
}
- 如果是
layout
LayoutInflater layoutInflater = LayoutInflater.from(getApplicationContext());
View view = layoutInflater.inflate(R.layout.activity1,null);
一般通过LayoutInflater
加载layout
先来看一下他主要的解析代码
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
...
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
...
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
createViewFromTag
方法主要是通过标签生成对象,比如,RelativeLayout
,然后继续循环子标签rInflateChildren
,把所有的子标签也加载完成。循环结束之后,调用parent.onFinishInflate();
createViewFromTag
方法最终会调用createView
方法,通过反射机制创建实例对象。
try {
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}