1 复现场景
1.1 先自定义Drawable
public class TestDrawable extends GradientDrawable {
}
1.2 xml中使用drawable
drawable_test.xml
<?xml version="1.0" encoding="utf-8"?>
<com.test.drawable.TestDrawable
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 设置圆角 -->
<!-- <corners android:radius="20dp"/>-->
</com.test.drawable.TestDrawable>
1.3 java/kotlin中使用
View v1 = findViewById(R.id.view1);
View v2 = findViewById(R.id.view2);
v1.setBackgroundResource(R.drawable.drawable_test);
Log.i("TAGTAG", "onCreate1: " + v1.getBackground());
v2.setBackgroundResource(R.drawable.drawable_test);
Log.i("TAGTAG", "onCreate2: " + v2.getBackground());
1.4 观察日志输出
同样的代码,v1使用TestDrawable,但v2却不是
2 原因分析
注意drawable的解析流程,ResourcesImpl#loadDrawable()
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws Resources.NotFoundException {
// If the drawable's XML lives in our current density qualifier,
// it's okay to use a scaled version from the cache. Otherwise, we
// need to actually load the drawable from XML.
final boolean useCache = density == 0 || value.density == mMetrics.densityDpi;
// ... 省略部分代码
try {
// ... 省略部分代码
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme. Skip the cache if
// we're currently preloading or we're not using the cache.
if (!mPreloading && useCache) {
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
cachedDrawable.setChangingConfigurations(value.changingConfigurations);
return cachedDrawable;
}
}
// Next, check preloaded drawables. Preloaded drawables may contain
// unresolved theme attributes.
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
boolean needsNewDrawableAfterCache = false;
if (cs != null) {
// ... 省略部分代码
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(wrapper, value, id, density);
}
// If we were able to obtain a drawable, store it in the appropriate
// cache: preload, not themed, null theme, or theme-specific. Don't
// pollute the cache with drawables loaded from a foreign density.
if (dr != null) {
dr.setChangingConfigurations(value.changingConfigurations);
if (useCache) {
cacheDrawable(value, isColorDrawable, caches, theme, canApplyTheme, key, dr);
// ... 省略部分代码
}
}
return dr;
} catch (Exception e) {
// ... 省略部分代码
}
}
上述大概流程是先调用DrawableCache#getInstance()从缓存中取,若取到直接返回,若未取到,正常加载并将drawable调用ResourcesImpl#cacheDrawable()进行缓存。
查看DrawableCache#getInstance()源码:
public Drawable getInstance(long key, Resources resources, Resources.Theme theme) {
final Drawable.ConstantState entry = get(key, theme);
if (entry != null) {
return entry.newDrawable(resources, theme);
}
return null;
}
由此可知,从缓存中取的并不是Drawable,而是Drawable.ConstantState: This abstract class is used by {@link Drawable}s to store shared constant state and data between Drawables, 再根据其newDrawable.
再看看缓存的地方:
private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
final Drawable.ConstantState cs = dr.getConstantState();
if (cs == null) {
return;
}
if (mPreloading) {
// ... 省略部分代码
} else {
synchronized (mAccessLock) {
caches.put(key, theme, cs, usesTheme);
}
}
}
故,重点排查对象就在Drawable.ConstantState上,实际上TestDrawable的处理在GradientDrawable中:
public class GradientDrawable extends Drawable {
@Override
public ConstantState getConstantState() {
mGradientState.mChangingConfigurations = getChangingConfigurations();
return mGradientState;
}
final static class GradientState extends Drawable.ConstantState {
// ... 省略部分代码
@Override
public Drawable newDrawable() {
return new GradientDrawable(this, null);
}
@Override
public Drawable newDrawable(@Nullable Resources res) {
// If this drawable is being created for a different density,
// just create a new constant state and call it a day.
final GradientState state;
final int density = Drawable.resolveDensity(res, mDensity);
if (density != mDensity) {
state = new GradientState(this, res);
} else {
state = this;
}
return new GradientDrawable(state, res);
}
// ... 省略部分代码
}
}
从上面逻辑可知,缓存生成的drawable是GradientDrawable而不是TestDrawable
3 解决办法
结合分析,对自定义Drawable不进行缓存即可,故使Drawable#getConstantState返回为null即可:
public class TestDrawable extends GradientDrawable {
@Override
public ConstantState getConstantState() {
return null;
}
}