Android Drawable的ConstantState共享问题解决,并从源码层分析其原因

在平时开发中,我们有时候会用到设置图片的透明的功能,不假思索的情况下,我们自然就会通过设置getBackground().setAlpha(XXX)来去达到我们的目的,但是我们后续会发现白色背景变成透明的状况,并且引发很多界面出现颜色错乱的问题,是不是很无奈呢?

这个问题,我们网上一搜也都就知道怎么解决了?通用解决方式:
getBackground().setAlpha(XXX)改成getBackground().mutate().setAlpha(XXX);
这样就解决了。
我们在往后面看其他的解释,也就知道原因了,这里我们看一下mutate代码的google注释:

Drawable#
/**
     * Make this drawable mutable. This operation cannot be reversed. A mutable
     * drawable is guaranteed to not share its state with any other drawable.
     * This is especially useful when you need to modify properties of drawables
     * loaded from resources. By default, all drawables instances loaded from
     * the same resource share a common state; if you modify the state of one
     * instance, all the other instances will receive the same modification.
     *
     * Calling this method on a mutable Drawable will have no effect.
     *
     * @return This drawable.
     * @see ConstantState
     * @see #getConstantState()
     */
    public @NonNull Drawable mutate() {
        return this;
    }

直接在线翻译:使这个可抽变。此操作无法反转。易变的可支取保证不与任何其他可支取共享其状态。当您需要修改drawables的属性时,这特别有用从资源加载。默认情况下,从同一资源共享一个公共状态;如果修改实例,所有其他实例将收到相同的修改。在可变Drawable上调用此方法将没有效果。

从上面的翻译可以反向推论得出,同一资源共享一个公共状态ConstantState,当然不同的Drawable的实现类有不同的ConstantState(比如ColorDrawable有内部类ColorState),但总的来说,还是因为公共状态的共享导致我们直接使用getBackground().setAlpha(XXX)时出现问题,而我们使用getBackground().mutate().setAlpha(XXX)时,这里以ColorDrawable的mutate()实现举例(其他Drawable实现类也大差不差的):

ColorDrawable#
/**
     * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
     * that comes from the same resource.
     *
     * @return This drawable.
     */
    @Override
    public Drawable mutate() {
        if (!mMutated && super.mutate() == this) {
            mColorState = new ColorState(mColorState);
            mMutated = true;
        }
        return this;
    }

我们可以看出,里面是重新创建了一个ColorState来实现自己的单独状态,而不影响共享状态的情况下来实现自己的状态需求的。到这里我们就有下面这张网上很流行的图的结论了:
在这里插入图片描述

然后我们就到这里了吗?NONONO,到这里我们还是不知道怎么实现公共状态ConstantState的共享的呢?我们一定要一探究竟,而非人云亦云。

上面从使用的角度和源码层了解了共享的机制,下面我们换个方向看一下共享的实现:
首先我们都知道,加载一张资源图片一般是通过getResources().getDrawable(iconId)来获得,然后我们继续看getDrawable里面实现,最终到这里:

Resources#
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValueForDensity(id, density, value, true);
            return impl.loadDrawable(this, value, id, density, theme);
        } finally {
            releaseTempTypedValue(value);
        }
    }

我们看到这里最终拿到了真正的Drawable对象,下面我们继续往里看,就到了共享状态的真正实现了,这里只看关键片段:

ResourcesImpl#loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id, int density, @Nullable Resources.Theme theme)

		// 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);
		}

这里我们就可以明显的看到类似key-value的意味了,然后我们继续看sPreloadedColorDrawables和sPreloadedColorDrawables,代码如下:

ResourcesImpl#
// Information about preloaded resources.  Note that they are not
    // protected by a lock, because while preloading in zygote we are all
    // single-threaded, and after that these are immutable.
    private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables;
    private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables
            = new LongSparseArray<>();

这里我们最终才真正看到了,此处是两个静态变量的LongSparseArray,我们可以得出:一旦加载到同一个图片资源Drawable时,LongSparseArray通过本地存储了同一key对应的Drawable.ConstantState,当下次加载同一资源,会使用共享一个公共状态ConstantState对象,因此才达到了同一资源的公共状态ConstantState的共享机制。到这里,我们就探究结束了,哈哈!

看完了到这里,是不是眼前一亮,不再人云亦云,恍然大悟的感觉呢,其实本猿也是经常一眼概论,但看过比别人的结论,总感觉自己只认识很浅的层面,因此想通过文章总结和看源码,来深刻认识一些东西,加深一些印象和思维,到这里,该说拜拜了,因个人认识有限,希望大家观看并评论指导!谢谢!

悟已往之不谏,知来者之可追。实迷途其未远,觉今是而昨非。纸上得来终觉浅,绝知此事要躬行,加油!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值