1. ホーム
  2. アンドロイド

getDrawable()キャッシュの問題点

2022-03-18 04:24:23
<パス

我々はしばしばgetContext().getResources().getDrawable()によってDrawable()を取得し、ときにAndroidのメカニズムはキャッシュ機構を使用しているので、直接色を変更するにはsetColorFilter()、他のgetDrawable()の色も変えることができます。我々はしばしば私たち自身の画像は不可解に他の色に変更されていることを我々のプロジェクトで発生します。

getDrawable()のソースコードを見てみましょう。

  public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
       ......
       Load the drawable
        final Drawable res = loadDrawable(value, id, theme);
        synchronized (mAccessLock) {
            if (mTmpValue == null) {
                mTmpValue = value;
            }
        }
        return res;
    }


    /*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException {
        if (TRACE_FOR_PRELOAD) {
            // Log only framework resources
            if ((id >>> > 24) == 0x1) {
                final String name = getResourceName(id);
                if (name ! = null) {
                    Log.d("PreloadDrawable", name);
                }
            }
        }

        final boolean isColorDrawable;
        final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches;
        final long key;
        //Drawable is divided into 2 types, colorDrawable (such as R.color.xxx) and other Drawable, first calculate the cache index key
        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 get from the cache where you want to set the theme
        if (!mPreloading) {
            final Drawable cachedDrawable = getCachedDrawable(caches, key, theme);
            if (cachedDrawable ! = null) {
                return cachedDrawable;
            }
        }

        // Get ConstantState from cache, subclass BitmapState mainly caches Bitmap and Paint, so use the same ConstantState to affect each other
        final ConstantState cs;
        if (isColorDrawable) {
            cs = sPreloadedColorDrawables.get(key);
        } else {
            cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
        }

        //new out drawable
        final Drawable dr;
        if (cs ! = null) {
            final Drawable clonedDr = cs.newDrawable(this);
            if (theme ! = null) {
                dr = clonedDr.mutate();// notice here, this will recreate a ConstantState
                dr.applyTheme(theme);
                dr.clearMutated();
            } else {
                dr = clonedDr;
            }
        } else if (isColorDrawable) {
            // no cached color, create directly
            dr = new ColorDrawable(value.data);
        } else {
            //go back and create the corresponding drawable type
            dr = loadDrawableForCookie(value, id, theme);
        }

        //Cache the ConstantState
        if (dr ! = null) {
            dr.setChangingConfigurations(value.changingConfigurations);
            cacheDrawable(value, theme, isColorDrawable, caches, key, dr);
        }

        return dr;
    }

インターフェイス setColorFilter メソッドを見てみましょう。

BitmapDrawableの場合

    @Override
    public void setColorFilter(ColorFilter cf) {
        // modifies the Oaint property of the State, which is naturally affected when the State is fetched through the cache
        mBitmapState.mPaint.setColorFilter(cf);
        invalidateSelf();
    }


解決策は、上で述べたmutateメソッドで

   @Override
    public Drawable mutate() {
        if (!mMutated && super.mutate() == this) {
            // Create BitmapState
            mBitmapState = new BitmapState(mBitmapState);
            mMutated = true;
        }
        return this;
    }

    //
     BitmapState(BitmapState bitmapState) {
            mBitmap = bitmapState.mBitmap;
            mTint = bitmapState.mTint;
            mTintMode = bitmapState.mTintMode. mTintMode = bitmapState.mTintMode;
            mThemeAttrs = bitmapState.mThemeAttrs;
            mChangingConfigurations = bitmapState.mChangingConfigurations. mChangingConfigurations = bitmapState;
            mGravity = bitmapState.mGravity. mTileModeX = bitmapState;
            mTileModeX = bitmapState.mTileModeX. mTileModeY = bitmapState;
            mTileModeY = bitmapState.mTileModeY. mTileModeY = bitmapState.mTileModeY;
            mTargetDensity = bitmapState. mTargetDensity. mBaseAlpha = bitmapState;
            mBaseAlpha = bitmapState.mBaseAlpha;
            //Paint will be newly created, thus solving the problems caused by Paint
            mPaint = new Paint(bitmapState.mPaint);
            mRebuildShader = bitmapState.mRebuildShader;
            mAutoMirrored = bitmapState.mAutoMirrored;
        }


まとめ

BitmapDrawableはgetDrawable()で取得しますが、色を変更するなどの落とし穴があり、再度mutateを呼び出した方が良いです。もちろん、直接new BitmapDrawableでも良いですが、ビットマップが再利用されず、メモリ消費量が増えます。