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池中。

Android的Drawable缓存机制源码分析的更多相关文章

  1. Android线程间异步通信机制源码分析

    本文首先从整体架构分析了Android整个线程间消息传递机制,然后从源码角度介绍了各个组件的作用和完成的任务.文中并未对基础概念进行介绍,关于threadLacal和垃圾回收等等机制请自行研究. 基础 ...

  2. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  3. Android Small插件化框架源码分析

    Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...

  4. ApplicationEvent事件机制源码分析

    <spring扩展点之三:Spring 的监听事件 ApplicationListener 和 ApplicationEvent 用法,在spring启动后做些事情> <服务网关zu ...

  5. Springboot学习04-默认错误页面加载机制源码分析

    Springboot学习04-默认错误页面加载机制源码分析 前沿 希望通过本文的学习,对错误页面的加载机制有这更神的理解 正文 1-Springboot错误页面展示 2-Springboot默认错误处 ...

  6. Android查缺补漏(View篇)--事件分发机制源码分析

    在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 <Android查缺补漏(View篇)-- ...

  7. Android异步消息传递机制源码分析

    1.Android异步消息传递机制有以下两个方式:(异步消息传递来解决线程通信问题) handler 和 AsyncTask 2.handler官方解释的用途: 1).定时任务:通过handler.p ...

  8. 【Android】Handler、Looper源码分析

    一.前言 源码分析使用的版本是 4.4.2_r1. Handler和Looper的入门知识以及讲解可以参考我的另外一篇博客:Android Handler机制 简单而言:Handler和Looper是 ...

  9. hadoop的RPC机制 -源码分析

    这些天一直奔波于长沙和武汉之间,忙着腾讯的笔试.面试,以至于对hadoop RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上 ...

随机推荐

  1. 2015年第5本(英文第4本):Death on the Nile尼罗河上的惨案

    书名:Death on the Nile 作者: Agatha Christie 单词数:7.9万(读完后发现网上还有一个版本,总共2.7万单词,孩子都能读懂,看来是简写版) 词汇量:6700 首万词 ...

  2. android assets文件夹资源的访问

    1.assets文件夹里面的文件都是保持原始的文件格式 . 2.assets中的文件只可以读取而不能进行写的操作. 3.assets目录下的资源文件不会在R.java自动生成ID,所以读取assets ...

  3. [linux]CentOS无法使用epel源

    [linux]CentOS无法使用epel源 问题的产生与解决 作者作为Android应用开发者,对linux的接触一直是ubuntu为主,但是有一个用于科学上网的vps,由于内存只有64M,所以使用 ...

  4. 用thinkPHP实现验证码的功能

    许多系统的登录都有验证码,而如果使用thinkPHP框架搭建网站的话,验证码的生成和验证就比较容易了 1.生成验证码 thinkPHP有对应生成验证码的方法 要使用验证码,需要导入扩展类库中的ORG. ...

  5. IOS之UIImageView--小实例项目--带音效的拳皇动画

    内容大纲: 1.初步工作 2.开始敲代码 3.注意 4.可能遇到的错误 5.设置音频速率在代码顺序上的注意点 带音效的拳皇动画实例项目 初步工作 1.新建一Objective-C工程之后,将需要的拳皇 ...

  6. jhljx跑跑跑(找规律)

    题目来源:https://biancheng.love/contest/41/problem/D/index jhljx跑跑跑 题目描述 数学不好的jhljx又在和别人打牌,他们一共m人每人n张牌,牌 ...

  7. CentOS下安装实时检测网络带宽的小工具bmon

    首先下载rpmforge-release扩展的rpm包 32位操作系统:wget http://www.sudu.us/Tools/bmon/rpmforge-release-0.3.6-1.el5. ...

  8. bat自动执行telnet

    @del c:\temp.vbs @echo on error resume next >>c:\temp.vbs @echo dim WshShell>>c:\temp.vb ...

  9. A Popup Progress Window

    一个包含bar和取消而且不需要资源弹出窗口 1.构造函数 CProgressWnd(); CProgressWnd(CWnd* pParent, LPCTSTR strTitle, BOOL bSmo ...

  10. apt-cache, apt-get

    apt是debian系的软件包的管理工具,他们可以通过搜索在/var/lib/apt/list里的索引文件搜做根据/etc/apt/sources.list里的软件源来在线安装软件,安装的过程还可以自 ...