关于Activity被染色的原因以及解决方案

来源:互联网 发布:微信站街软件免费下载 编辑:程序博客网 时间:2024/04/20 08:24

  在公司的Android项目中遇到过一些非常匪夷所思的bug,有一些bug就算找到了解决方案但是根本的原因也不太清楚。其中有一个Activity被染色的bug,具体体现是打开一个新的Activity的时候,新Activity的背景色是在xml里头通过background属性设置的,但这个新Activity的背景色显示的居然不是xml里头设置的,而是上一个Activity的背景色,看起来就像这个新的Activity被上一个Activity染色了。这个问题当时我们也不清楚具体的原因,最终是通过使用.9图来设置背景解决的。最近在网上看到一个帖子说到了一个通知栏一会儿白一会儿灰的问题,看起来和我遇到的这个问题类似,于是分析了一下源码,终于找到了原因。
  先给出一句话总结,再具体分析。此bug的原因在于Resources会将最近使用过的色值当做Drawable缓存起来,在API 17以下,如果在某个Activity调用了View#setBackgroundColor(int)方法,会导致Drawable对应的色值改变。因此当新Activity使用色值被修改过Drawable当做背景时,就会出现Activity被染色的情况。
  下面具体解释此bug的原因。首先简单介绍一下ColorDrawable这个类,顾名思义,这个类就是将一个色值变成一个Drawable对象。从ColorDrawable#getColor()方法可以看到,用于保存色值的是ColorDrawable里的内部类ColorState的mUseColor这个值。也就是说mUseColor这个值会影响这个Drawable对象的颜色。
  当系统渲染一个View的时候,View的构造函数通过调用TypedArray#getDrawable(int)并最终调用Resources#loadDrawable(TypedValue, int)来获得一个Drawable对象来设置该View的背景。下面来看看Resources#loadDrawable(TypedValue, int)的具体实现。

/*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {        //省略部分        final boolean isColorDrawable;        final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches;        final long key;        //通过value.type是否属于一个色值        //而出现此bug的情景就是在xml文件里background设置一个色值,符合if条件判断        if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT                && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {            isColorDrawable = true;            caches = mColorDrawableCache;            key = value.data;        } else {            isColorDrawable = false;            caches = mDrawableCache;            key = (((long) value.assetCookie) << 32) | value.data;        }        // First, check whether we have a cached version of this drawable        // that was inflated against the specified theme.        if (!mPreloading) {            //顾名思义,从缓存中获得Drawable对象            final Drawable cachedDrawable = getCachedDrawable(caches, key, theme);            if (cachedDrawable != null) {                return cachedDrawable;            }        }        //省略部分,从注释可以看到,如果代码运行至此        //Resources会将调用casheDrawable()将新创建的Drawable对象缓存起来        // If we were able to obtain a drawable, store it in the appropriate        // cache (either preload or themed).        if (dr != null) {            dr.setChangingConfigurations(value.changingConfigurations);            cacheDrawable(value, theme, isColorDrawable, caches, key, dr);        }        return dr;    }

  以上源码省略掉了一部分,总结起来,当系统渲染一个View兵调用loadDrawable来获得一个Drawable设置背景时,会先从缓存的Drawable对象中获得,如果没有则创建新的并缓存起来。再结合我们遇到的bug,我们可以将矛头指向这个被缓存起来的Drawable对象,是不是因为Drawable对象的色值改变了导致的问题?下面看一下API 17以下View#setBackgroundColor(int)的源码。

    public void setBackgroundColor(int color) {        if (mBackground instanceof ColorDrawable) {            //如果是ColorDrawable,直接调用setColor()方法            ((ColorDrawable) mBackground).setColor(color);            computeOpaqueFlags();            mBackgroundResource = 0;        } else {            setBackground(new ColorDrawable(color));        }    }

  下面是setColor(int)方法源码。

 public void setColor(int color) {        if (mColorState.mBaseColor != color || mColorState.mUseColor != color) {            mColorState.mBaseColor = mColorState.mUseColor = color;            invalidateSelf();        }    }

  可以看到,此方法把ColorDrawable用于保存色值mColorState.mUseColor给修改了!!这就是出现Activity被染色的根本原因。这个bug在API17以后已经被修复,View#setBackgroundColor(int)变成了下面这样。

public void setBackgroundColor(int color) {        if (mBackground instanceof ColorDrawable) {            ((ColorDrawable) mBackground.mutate()).setColor(color);            computeOpaqueFlags();            mBackgroundResource = 0;        } else {            setBackground(new ColorDrawable(color));        }    }

  在调用setColor(int)之前先调用了以下mutate()方法,mutate()方法官方给出的解释是这样的: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.大致的意思是一个mutable的Drawable将不再与其他的Drawable分享ColorState,通常所有通过相同资源加载的Drawable会分享相同的ColorState,如果你修改一个实例的ColorState,其他的实例也会被影响。这也就是出现Activity被染色的bug的根本原因。那么既然这个bug是在API 17之后才修复的,那为了兼容性,在低版本有什么workaround的呢?
  (1)xml的background使用.9图而不使用色值,简单粗暴、奏效。
  (2)使用setBackgroundDrawable(Drawable)(API16 之后使用setBackground(Drawable))代替setBackgroundColor(int)。
  (3)每次打开Activity的时候使用setBackgroundColor(int)去重新赋值,这也是可行的。

0 0
原创粉丝点击