Android換膚系列 Activity資源攔截與替換

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章