• Android的Drawable缓存机制源码分析


         Android获取Drawable的方式一般是Resources.getDrawable(int),Framework会返回给你一个顶层抽象的Drawable对象。而在Framework中,系统使用了享元的方式来节省内存。为了证明这一点,我们来写一个小demo:

            我们在我们的Android项目中引入一个简单的图片test.png。由于我们只是为了享元的结论,我们定义一个简单的Activity,并复写它的onCreate方法:

           

    List<Bitmap> list = new ArrayList<Bitmap>();
    
        Bitmap bitmap = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            for (int i = 0; i < 10; i ++) {
                bitmap  = BitmapFactory.decodeResource(getResources(), R.drawable.test);
                list.add(bitmap);
            }
            ImageView iv = new ImageView(this);
            iv.setImageBitmap(bitmap);
            this.setContentView(iv);
        }

    可能你这里有疑惑为何要需要一个list把Bitmap存储起来,这重要是为了避免GC引起的内存释放。好了我们将我们的内存打印出来会发现我们加入了10个Bitmap占用的实际内存是:26364K。我们在转化成为Drawable的方式:

     List<Drawable> list = new ArrayList<Drawable>();
    
        Drawable bitmap = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            for (int i = 0; i < 10; i ++) {
                bitmap  = this.getResources().getDrawable(R.drawable.test);
                list.add(bitmap);
            }
            ImageView iv = new ImageView(this);
            iv.setImageDrawable(bitmap);
            this.setContentView(iv);
        }


    我们再打印内存,发现内存已经降到了:7844K,这部分数据基本就证明了我们的结论。那么有没有可能是Resources缓存了相同的drawable。当然不是,你可以写一个简单代码测试一下:

    Drawable d1 = this.getResources().getDrawable(R.drawable.test);
            Drawable d2 = this.getResources().getDrawable(R.drawable.test);
            System.out.println(">>>d1 == d2 ? = "+(d1 == d2));

    你会发现输出的是false。实际上,享元这点我们基本达成了共识,关键Framwork来包装Drawable的时候还引入了组合模式,Framework本身缓存的是你这个Drawable的核心元数据。

    Resources.java
     Drawable loadDrawable(TypedValue value, int id)
                throws NotFoundException {
    ...
    Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
    ...
    }


    从代码可以看出,系统对Drawable主要分成两大类,实际上还有一类熟悉预加载类的Drawable,不过不作为我们讨论的重点,由于我们load的并不属于color类型的Drawable,因此我们对应的享元池由mDrawableCache对象实现。

    Resources.java
    private Drawable getCachedDrawable(
                LongSparseArray<WeakReference<ConstantState>> drawableCache,
                long key) {
            synchronized (mAccessLock) {
                WeakReference<Drawable.ConstantState> wr = drawableCache.get(key);
                if (wr != null) {   // we have the key
                    Drawable.ConstantState entry = wr.get();
                    if (entry != null) {
                        //Log.i(TAG, "Returning cached drawable @ #" +
                        //        Integer.toHexString(((Integer)key).intValue())
                        //        + " in " + this + ": " + entry);
                        return entry.newDrawable(this);
                    }
                    else {  // our entry has been purged
                        drawableCache.delete(key);
                    }
                }
            }
            return null;
        }

    我们通过调用代码,会发现我们存储在数据池中的根本不是我们的Drawable对象,而是一个叫做Drawable.ConstantState类型的对象,而且用了弱引用包装起来。ConstantState是一个抽象类,有多个子类的实现

    public static abstract class ConstantState {
            /**
             * Create a new drawable without supplying resources the caller
             * is running in.  Note that using this means the density-dependent
             * drawables (like bitmaps) will not be able to update their target
             * density correctly. One should use {@link #newDrawable(Resources)}
             * instead to provide a resource.
             */
            public abstract Drawable newDrawable();
            /**
             * Create a new Drawable instance from its constant state.  This
             * must be implemented for drawables that change based on the target
             * density of their caller (that is depending on whether it is
             * in compatibility mode).
             */
            public Drawable newDrawable(Resources res) {
                return newDrawable();
            }
            /**
             * Return a bit mask of configuration changes that will impact
             * this drawable (and thus require completely reloading it).
             */
            public abstract int getChangingConfigurations();
    
            /**
             * @hide
             */
            public Bitmap getBitmap() {
                return null;
            }
        }

    由于我们使用的是BitmapDrawable,而BitmapDrawable对应的ConstantState是BitmapState

    final static class BitmapState extends ConstantState {
            Bitmap mBitmap;
            int mChangingConfigurations;
            int mGravity = Gravity.FILL;
            Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
            Shader.TileMode mTileModeX = null;
            Shader.TileMode mTileModeY = null;
            int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
            boolean mRebuildShader;
            boolean mAutoMirrored;
    
            BitmapState(Bitmap bitmap) {
                mBitmap = bitmap;
            }
    
            BitmapState(BitmapState bitmapState) {
                this(bitmapState.mBitmap);
                mChangingConfigurations = bitmapState.mChangingConfigurations;
                mGravity = bitmapState.mGravity;
                mTileModeX = bitmapState.mTileModeX;
                mTileModeY = bitmapState.mTileModeY;
                mTargetDensity = bitmapState.mTargetDensity;
                mPaint = new Paint(bitmapState.mPaint);
                mRebuildShader = bitmapState.mRebuildShader;
                mAutoMirrored = bitmapState.mAutoMirrored;
            }
    
            @Override
            public Bitmap getBitmap() {
                return mBitmap;
            }
    
            @Override
            public Drawable newDrawable() {
                return new BitmapDrawable(this, null);
            }
    
            @Override
            public Drawable newDrawable(Resources res) {
                return new BitmapDrawable(this, res);
            }
    
            @Override
            public int getChangingConfigurations() {
                return mChangingConfigurations;
            }
        }

    我们可以看到BitmapState对应的newDrawable方法,它将自己作为参数传递给BitmapDrawable对象,也就是说BitmapDrawble组合了同一个的BitmapState。这样就实现了同一个Bitmap资源的复用。

    跟到这,相信大家都跟我一样了解了Bitmap是如何从cache中取出,我们接下来看一下ConstantState是如何存入的。

    Resources.loadDrawable()
    {
    ...
                            InputStream is = mAssets.openNonAsset(
                                    value.assetCookie, file, AssetManager.ACCESS_STREAMING);
            //                System.out.println("Opened file " + file + ": " + is);
                            // MIUI MOD:
                            // dr = Drawable.createFromResourceStream(this, value, is, file, null);
                            dr = createFromResourceStream(this, value, is, file, id);
                            is.close();
    ...
     if (dr != null) {
                dr.setChangingConfigurations(value.changingConfigurations);
                cs = dr.getConstantState();
                if (cs != null) {
                    if (mPreloading) {
                        final int changingConfigs = cs.getChangingConfigurations();
                        if (isColorDrawable) {
                            if (verifyPreloadConfig(changingConfigs, 0, value.resourceId,
                                    "drawable")) {
                                sPreloadedColorDrawables.put(key, cs);
                            }
                        } else {
                            if (verifyPreloadConfig(changingConfigs,
                                    LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) {
                                if ((changingConfigs&LAYOUT_DIR_CONFIG) == 0) {
                                    // If this resource does not vary based on layout direction,
                                    // we can put it in all of the preload maps.
                                    sPreloadedDrawables[0].put(key, cs);
                                    sPreloadedDrawables[1].put(key, cs);
                                } else {
                                    // Otherwise, only in the layout dir we loaded it for.
                                    final LongSparseArray<Drawable.ConstantState> preloads
                                            = sPreloadedDrawables[mConfiguration.getLayoutDirection()];
                                    preloads.put(key, cs);
                                }
                            }
                        }
                    } else {
                        synchronized (mAccessLock) {
                            //Log.i(TAG, "Saving cached drawable @ #" +
                            //        Integer.toHexString(key.intValue())
                            //        + " in " + this + ": " + cs);
                            if (isColorDrawable) {
                                mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
                            } else {
                                mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs));
                            }
                        }
                    }
                }
    ...
    
    }

    可以看出,当你新生成一个Drawable的时候,就会将Drawable的ConstantState从Drawable中取出,然后放入你Cache池中。

  • 相关阅读:
    基于springboot1.5.9整合shiro时出现静态文件找不到的问题
    基于springboot多模块项目使用maven命令打成war包放到服务器上运行的问题
    关于使用map存放数据乱序”问题“
    springboot1.5.9整合websocket实现实时显示的小demo
    SpringBoot1.5.10.RELEASE配置mybatis的逆向工程
    SpringBoot1.5.10.RELEASE整合druid时,在druid monitor界面出现(*) property for user to setup
    SpringBoot1.5.10.RELEASE整合druid
    SpringBoot1.5.10.RELEASE项目如何在tomcat容器中运行
    JS学习-01
    List,Set,Map在java.util包下都是接口 List有两个实现类:ArrayList和LinkedList Set有两个实现类:HashSet和LinkedHashSet AbstractSet实现了Set
  • 原文地址:https://www.cnblogs.com/lianghe01/p/5507990.html
Copyright © 2020-2023  润新知