Android Drawable 资源缓存问题

起源 今天开发过程中遇到一个问题,定位到问题代码如下: public static Drawable getColorFilteredDrawable(@DrawableRes int d

起源

今天开发过程中遇到一个问题,定位到问题代码如下:

public static Drawable getColorFilteredDrawable(@DrawableRes int drawableRes, @ColorRes int colorRes){ Context context = App.getContext(); Drawable drawable = ContextCompat.getDrawable(context, drawableRes); drawable.setColorFilter(getColor(colorRes), PorterDuff.Mode.SRC_ATOP); return drawable; }

这段代码的期望是通过resId产生不同的Drawable,并改变颜色,产生不同颜色的Drawable对象。但是最后发现该方法返回相同resId的Drawable都是同一个颜色,所以颜色只与最后调用的setColorFilter方法有关。初步断定可能是Android系统缓存了Drawable对象。为了进一步确认这个问题,决定进到Android Sdk源码中看一下。

源码分析

跟踪代码到Resources类的loadDrawable方法。通过分析代码,发现其中有一段就是判断是否缓存了该resId和Theme所对应的Drawable,如果缓存了,就返回了缓存这的对象,这也是getResources.getDrawable()方法返回同一个Drawable对象的原因。

@Nullable Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException { ... // First, check whether we have a cached version of this drawable // that was inflated against the specified theme. if (!mPreloading) { final Drawable cachedDrawable = caches.getInstance(key, theme); if (cachedDrawable != null) { return cachedDrawable; } } ... }

解决方案

既然知道了原因,那么怎么做到获得相同resId的不同Drawable对象呢。通过查找代码,我们发现了Drawable有一个工厂类ConstantState。它保存了一些共享的常量,并且也可以作为工厂类来产生新的Drawable。但是这样产生的Drawable还是共享了这个ConstantState对象,所以为了让Drawable完全独立,还需要调用mutate()方法同时拷贝里面的ConstantState对象,可以理解为DeepCopy。

/** * This abstract class is used by {@link Drawable}s to store shared constant state and data * between Drawables. {@link BitmapDrawable}s created from the same resource will for instance * share a unique bitmap stored in their ConstantState. * * <p> * {@link #newDrawable(Resources)} can be used as a factory to create new Drawable instances * from this ConstantState. * </p> * * Use {@link Drawable#getConstantState()} to retrieve the ConstantState of a Drawable. Calling * {@link Drawable#mutate()} on a Drawable should typically create a new ConstantState for that * Drawable. */ public static abstract class ConstantState { ... public abstract Drawable newDrawable(); public Drawable newDrawable(Resources res) { return newDrawable(); } public Drawable newDrawable(Resources res, Theme theme) { return newDrawable(null); } ... }

所以最后通过下面这句代码就可以获得完全独立的Drawable对象,随便修改也不会影响其他地方了。

Drawable drawable = ContextCompat.getDrawable(context, drawableRes).getConstantState().newDrawable().mutate();

扩展

与Drawable类似的,系统还缓存了ColorStateList,Animation,StateListAnimator,如果通过Resources取得这些资源的对象,又想对其进行修改的话就要注意了。

未登录用户
全部评论0
到底啦