要實現對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註冊的代碼,過程如下圖: