要实现对Android资源加载的拦截和替换,4.4 以下的版本可通过自定义Resources子类重写父类的loadDrawable和loadColorStateList两个方法,在方法中将请求资源替换成皮肤包中的资源。在4.4的系统中重写这两个方法在运行时会收到警告,但并不影响正常运行,但这种方式在Android 5.0 以后就不在适用了。
另外一种实现资源加载拦截的方式是通过替换Resources内的静态缓存变量为自定义对象来实现资源的拦截。通过前面的Android换肤系列 Resources 可以知道Resources包含以下三个静态变量:sPreloadedDrawables, sPreloadedColorDrawables, sPreloadedColorStateLists。Resources类的loadDrawable和loadColorStateList方法在加载资源前会判断资源是否存在,当sPreloadedDrawables,sPreloadedColorDrawables或者sPreloadedColorStateLists存在缓存资源时,就直接返回缓存内容。
Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
....
Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
....
}
ColorStateList loadColorStateList(TypedValue value, int id)
throws NotFoundException {
ColorStateList csl;
if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
csl = mPreloadedColorStateLists.get(key);
if (csl != null) {
return csl;
}
}
csl = mPreloadedColorStateLists.get(key);
....
}
因此可以通过替换sPreloadedColorDrawables,sPreloadedDrawables,mPreloadedColorStateLists为重写了get方法的自定义类,来加载自定义的资源。由于缓存使用key来作为缓存资源的唯一标识,即在重写的get方法里面需要把key转化成资源id再通过自定义的resources加载资源。
不同android版本间Resources的静态缓存变量的差异:
api >= 18
private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists;
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables;
api 16-17
private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables;
private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists;
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables;
api 14-15
private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables;
private static final SparseArray<ColorStateList> mPreloadedColorStateLists;
private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables;
api < 14
private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables;
private static final SparseArray<ColorStateList> mPreloadedColorStateLists
不同android版本间Resources的key生成的差异:
drawable key
api >= 17
long key = isColorDrawable ? value.data :(((long) value.assetCookie) << 32) | value.data;
api < 17
long key = (((long) value.assetCookie) << 32) | value.data;
colorStateList key
api >= 16
long key = (((long) value.assetCookie) << 32) | value.data;
api < 16
int key = (value.assetCookie << 24) | value.data;
自定义DrawableLongSparseArray继承LongSparseArray, 重写父类的get方法, 在方法内部将缓存key转换成资源id, 在通过资源id加载皮肤包中的资源, 实现资源拦截替换。
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public class DrawableLongSpareArray extends LongSparseArray<Drawable.ConstantState> {
private LongSparseArray<Integer> resourceIdKeyMap;
private LongSparseArray<Drawable.ConstantState> originalCache;
private ProxyResources resources;
public DrawableLongSpareArray(ProxyResources resources, LongSparseArray<Drawable.ConstantState> originalCache,
LongSparseArray<Integer> resourceIdKeyMap) {
this.resources = resources;
this.originalCache = originalCache;
this.resourceIdKeyMap = resourceIdKeyMap;
}
@Override
public Drawable.ConstantState get(long key) {
Integer id;
if (resources != null && (id = resourceIdKeyMap.get(key)) != null) {
Drawable dr = resources.loadDrawable(id);
if (dr != null) {
return dr.getConstantState();
} else {
return null;
}
} else {
return originalCache.get(key);
}
}
}
接下来就是实现id到key之间映射的生成。 可通过重写Resources类的getLayout方法,在inflate布局的时候将界面用到的所有资源注册到resourceIdKeyMap中去, 也可通过自定义LayoutInflater.Factory,注册到LayoutInflater当中以减少资源id注册的代码,过程如下图: