通常在Activity的onCreate回调方法中调用setContentView方法来设置界面要显示的layout,setContentView方法会对layout文件进行解析和资源的加载。这里以常用的View、ImageView、TextView为例看下加载过程。
从上面的步骤能看出,View的初始化过程中资源的加载主要是通过TypeArray类来实现的,而TypeArray类内部又是通过Resources来加载资源。
Step 1. View初始化
public View(Context context, AttributeSet attrs, int defStyleAttr) {
this(context);
TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,
defStyleAttr, 0);
Drawable background = null;
....
final int N = a.getIndexCount();
for (int i = 0; i < N; i++) {
int attr = a.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
....
}
}
}
构造方法里面首先调用Context的obtainStyledAttributes取得TypeArray对象
Step 2. Context.obtainStyledAttributes
public final TypedArray obtainStyledAttributes(
AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) {
return getTheme().obtainStyledAttributes(
set, attrs, defStyleAttr, defStyleRes);
}
obtainStyledAttributes方法里面通过调用getTheme获取当前上下文的主题,这里的context实际上就是Activity对象,而Activity继承了ContextThemeWrapper,getTheme的具体操作就是在ContextThemeWrapper 中完成的。
public Resources.Theme getTheme() {
if (mTheme != null) {
return mTheme;
}
mThemeResource = Resources.selectDefaultTheme(mThemeResource,
getApplicationInfo().targetSdkVersion);
initializeTheme();
return mTheme;
}
private void initializeTheme() {
final boolean first = mTheme == null;
if (first) {
mTheme = getResources().newTheme();
Resources.Theme theme = mBase.getTheme();
if (theme != null) {
mTheme.setTo(theme);
}
}
onApplyThemeResource(mTheme, mThemeResource, first);
}
最终Theme对象的生成是通过getResources().newTheme()这句代码实现的,这里的getResources()的实现在4.2的版本中Android做了修改:
4.2版本
private Resources mResources;
public Resources getResources() {
if (mResources != null) {
return mResources;
}
if (mOverrideConfiguration == null) {
mResources = super.getResources();
return mResources;
} else {
Context resc = createConfigurationContext(mOverrideConfiguration);
mResources = resc.getResources();
return mResources;
}
}
4.2以前的版本是继承至ContextWrapper
public Resources getResources()
{
return mBase.getResources();
}
在4.2的版本中添加了mResources变量,用于支持OverrideConfiguration。这样当我们在换肤中替换Resources对象的时候不仅要替换Context对象的mResrouces,对应4.2及其以后的版本Activity对象内的mResrouces对象也要替换。Theme是定义在Resources中的内部类,每一个Theme实例内部都包含一个Resources对象的引用,因此这里的Resources对象也是需要替换的。
Step 3. TypeArray.getDrawable
public Drawable getDrawable(int index) {
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
return mResources.loadDrawable(value, value.resourceId);
}
return null;
}
在TypeArray类的getDrawable方法,首先通过索引index获得资源项的信息TypedValue,接着再调用Resources类的loadDrawable方法进行资源的加载。
TextView的textColor属性的加载过程
Step 1.TextView构造方法
public TextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final Resources.Theme theme = context.getTheme();
TypedArray a = theme.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.TextViewAppearance, defStyle, 0);
TypedArray appearance = null;
int ap = a.getResourceId(
com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
a.recycle();
if (ap != -1) {
appearance = theme.obtainStyledAttributes(
ap, com.android.internal.R.styleable.TextAppearance);
}
if (appearance != null) {
int n = appearance.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = appearance.getIndex(i);
switch (attr) {
case com.android.internal.R.styleable.TextAppearance_textColor:
textColor = appearance.getColorStateList(attr);
break;
....
}
}
}
}
TextView在初始化过程中加载颜色资源的过程和View加载背景资源是相似的,TextView调用TypeArray类的getColorStateList方法来获取TextView文本的颜色,getColorStateList中再调用Resources的loadColorStateList加载colorState资源。
ImageView的src属性的加载过程
Step 1.ImageView构造方法
public More ...ImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initImageView();
TypedArray a = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.ImageView, defStyle, 0);
Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
if (d != null) {
setImageDrawable(d);
}
....
}
ImageView的src资源的加载和View的background资源的加载是一样的。
通过上面3种资源的加载,可以看出在View及其子类在初始化的过程中,对于常用的颜色资源、图片资源、selector资源的加载主要是通过Resources的loadDrawable和loadColorStateList两个方法来实现的。那么对于换肤功能可以采用自定义ProxyResources类继承Resources,来实现对资源加载的拦截。首先检查皮肤包SkinResouces中是否存在要当前要查找的资源,如果皮肤包存在时,通过SkinResources加载资源;否则通过默认的defaultResouces中加载资源。对于layout资源和xml文件资源,则默认从defaultResources资源中加载,一般代码开发和布局layout的耦合性比较大,换肤需求中更改布局的情况也比较少。