文档章节

位图管理、图片下载缓存、管理图片内存 (四) 缓存位图

k
 kim366
发布于 2016/05/13 19:36
字数 2282
阅读 6
收藏 0
点赞 2
评论 0
        缓存位图
        下载单个位图对象到UI组件中是很直接的,然而 ,如果你需要同时加载一系列的图片,则会显得比较复杂。许多情况下(如 ListiView,GridView,ViewPager中), 屏幕上的图片总数可能由于组件滚动的看似无限量的。
        当图片被滑出屏幕时,为了节省内存,这类组件会循环使用子视图。假如你没有长时间地持有这些引用,垃圾回收器也会释放你下载的位图。 这些都是好的,不过,为了保持UI流畅而且快速,你可能想要避免他们每次回到屏幕时都持续处理这些图片。 可以通过内存缓存和硬盘文件缓存快速重复加载这些图片,很好的达到这个目的。

        这一节将学习如何在下载多个位图时通过磁盘文件缓存和内存缓存提高UI的响应速度和流畅度。


       使用内存缓存

       内存缓存提供了一种快速获取位图的方法,这种方法以消耗应用内存为代价。LruCache类是非常合适的,他可以缓存位图,通过强类型引用LinkedHashMap保持最近引用的对象,并在内存缓存容量达到极限时清除最长时间没有引用的对象。
注意:在此之前,通过实现SoftReference or WeakReference缓存位图是很受欢迎的方式,不过不建议使用。从android2.3开始,垃圾回收机制对导致引用无响应的强弱类型引用更具侵略性。并且,在android3.0之前,位图的后台数据被保存在本地内存中,通过可预知的方式释放,很可能暂时性地导致应用耗光内存是程序崩溃。

       为了选择合适的LruCache容量,下列因素应该被考虑在内。
       1. 应用或activity的剩余内存是如何加强的?
       2. 屏幕同时可能存在多少图片,有多少图片应该准备在屏幕上显示?
       3. 设备的屏幕尺寸和分辨率是多少?高分辨率的的设备需要更大的缓存容量用于持有相同数量的图片。
       4. 图片会被多么频繁地被访问?其中一些比另外一些会被更频繁的访问么?如果是,那么你可能想要持有比较大数量的对象在内存中,甚至可以用多个LruCache对象用于存储不同群组的位图。
       5. 你能平衡数量与质量的问题么?有时候,存储数量较大质量较低的位图,而在后台任务中加载另外一个高质量的版本,可能更加有利。

      

       没有适合所有应用的容量值和计算公式,这些都依赖于你怎样分析你的使用,并找出一种合适的解决方案。缓存太小会导致额外的不利开销,缓存过大可能导致内存泄露。

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    //获取应用的最大内存
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

注意:在这个例子中,八分之一的内存会被分配为缓存,在一台常用的高分辨率设备上,这个最小值大约是4M, 在800*400分辨率的设备上,通过GridView全屏显示图片大约需要1.5M内存(800*400*4byte),所以,这可以在内存中缓存大约2.5页图片。


         当把位图加载到ImageView中时,LruCache会首先被检查。如果找到入口,则立即更新ImageView,否则,开启后台线程处理图片。

public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}
         BitmapWorkerTask也需要更新用于添加内存缓存的入口。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}
     

        使用硬盘缓存
        要获取最近浏览的位图,使用内存缓存方式是非常有利的,不过你不能依靠这种内存方式获取图片。像GridView这样具有大数据集的组件很容易填满内存缓存。此外,应用可能被另一个任务(如电话)打断,如果运行在后台,那么这个任务可能被杀死内存缓存会被销毁。
        在内存缓存中图片无法再获得的情况下,硬盘缓存可以用于辅助处理位图,降低加载时间。当然,从硬盘加载图片比从内存加载图片稍慢,而且加载过程应该在后台线程进行,因为硬盘的读取时间是不可预知的。

注: 如果图片被很频繁地访问,那么ContentProvider对象可能更适合放置缓存的图片,比如说在图片浏览器应用中。
        

        下列样例代码使用DiskLruCache实现,下列代码在内存缓存的基础上添加了硬盘缓存。

private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Initialize memory cache
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (mDiskCacheLock) {
            File cacheDir = params[0];
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting = false; // Finished initialization
            mDiskCacheLock.notifyAll(); // Wake any waiting threads
        }
        return null;
    }
}

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);

        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }

        // Add final bitmap to caches
        addBitmapToCache(imageKey, bitmap);

        return bitmap;
    }
    ...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }

    // Also add to disk cache
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}

public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // Wait while disk cache is started from background thread
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
    final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}
注: 因为初始化硬盘缓存要求对硬盘进行操作,所以这个过程不应该在主线程中发生。不过,这就意味着,有机会在初始化之前获得缓存。为了解决这个问题,在上述实现中,锁对象可以确保应用在初始化之前不从硬盘缓存中读取数据。
        因为内存缓存是在主线程中检查的,而硬盘缓存是在后台线程中检查的,所以银盘操作不应该在UI线程中发生。当图片处理完成时,最终的位图会被添加到内存缓存和硬盘缓存中已备使用。


        处理配置改变
        运行时配置改变,如屏幕方向改变,会导致应用正在运行的activity销毁并根据新的配置信息重新启动(要获取更多相关信息,可以查看Handling Runtime Changes部分)。你可能想在配置改变时避免持续重复处理图片,从而获得更平稳和更快捷的用户体验。
        幸运的是,在“使用内存缓存”中,你创建了很好的位图内存缓存。 这个缓存会在调用setRetainInstance(true)时通过Fragment传递到新的Activity实例中。当新的activity重建完成后,这个保留的fragment会被附着到该activity中,并且可通过现存的缓存对象获得, 从而使图片可以被快速获取并被重新注入到ImageView对象中。

        下面是一个在配置放生改变时通过Fragment重新获得内存缓存对象的实例。

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment retainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = retainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            ... // Initialize cache here as usual
        }
        retainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}

class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> mRetainedCache;

    public RetainFragment() {}

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
            fm.beginTransaction().add(fragment, TAG).commit();
        }
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}
        要想检验这个实例,可以尝试在不获得Fragment和获得Fragment的情况下旋转设备。你会注意到当你通过内存缓存实例化图片并将图片附着到activity上时,几乎没有延迟。任何在内存缓存中找不到的图像都有望在硬盘缓存中获得,否则将做一般处理,既重新下载再缓存。

本文转载自:http://blog.csdn.net/oyangyujun/article/details/41039575

共有 人打赏支持
k
粉丝 1
博文 129
码字总数 0
作品 0
朝阳
Android图片加载库Glide和Fresco是如何工作的

原文地址:https://blog.mindorks.com/how-the-android-image-loading-library-glide-and-fresco-works-962bc9d1cc40 通常,我们在加载图片的时候经常会遇到如下的问题: 内存溢出错误 图片加...

尺锤
2017/09/16
0
0
Android中的缓存处理

一、缓存介绍 (一)、Android中缓存的必要性: 1、没有缓存的弊端: 流量开销:对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量。 ...

墨宇hz
2015/04/25
0
0
【Google官方教程】前言:高效的Bitmap显示

转载声明:Ryan的博客文章欢迎您的转载,但在转载的同时,请注明文章的来源出处,不胜感激! :-) http://my.oschina.net/ryanhoo/blog/88153 译者:Ryan Hoo 来源:https://developer.andro...

RyanHoo
2012/11/09
0
11
FastImageCache 架构分析

原文 文章介绍 本文章注重分析 FastImageCache 这个 Github 第三方图片IO库的架构和部分分析等等。 对于 FastImageCache 很多同学或多或少都会听过,但是网上很多人说这是一个网络图片库,我...

葱神大大
06/22
0
0
【腾讯优测干货分享】使用多张图片做帧动画的性能优化

本文来自于Dev Club 开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57fc8cea302e4725036142f6 使用多张图片做帧动画的性能优化 背景 QQ群的送礼物功能需要加载几十...

腾讯Bugly
2016/10/11
613
2
ImageView异步加载图片--ImageLoaderSample

这个可以实现ImageView异步加载图片,内存缓存,文件缓存,imageview显示图片时增加淡入淡出动画。 解决了: 1. listview加载oom问题 listview加载时卡顿的现象 listview加载时item中图片重复...

咕噜不爱猫
2014/02/28
5K
0
如何将 HTML5 性能发挥到极致

HTML5作为新兴领域越来越热。然而在移动设备硬件性能弱于PC的背景下,对性能的需求显得更为重要,而HTML5性能优化前与优化后有着极大的差别,如何优化才能提高性能,对此熟知的人很少。本文以...

html5VR
2016/05/28
10K
8
instrument 之Core-Animation 性能调优(Color Hits Green and Misses Red)

备注:有很多很好的文章说把shouldRasterize(光栅化)个人认为应该按照PS里面专业术语来讲,个人理解。 不知道有没有小伙伴不知道这个字“栅”怎么读了,我特地百度了一下,有四种读法。 栅...

夜空下最亮的亮点
2017/12/29
0
0
VG.net矢量图和矢量动画开发平台拓扑图软件免费下载

VG.net拓扑图软件是一个基于.net平台的矢量图开发工具,可广泛应用于包括:电力、军工、煤炭、化工、科研、能源等各种监控软件、工作流设计器、电力、化工、煤炭、工控组态软件、仿真、地理信...

yidongkaifa
2014/10/20
0
0
YYImage 源码剖析:图片处理技巧

引言 首先问一个问题:你会用图片么? 图片是现代化 APP 界面设计里应用广泛的东西,精美的图片可以带来视觉上的享受,提高用户体验。由此给技术上带来了一些挑战,比如动图的处理、图片显示...

indulge_in
前天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

ndarray花式索引

花式索引 花式索引(Fancy indexing)是一个NumPy术语,它指的是利用整数数组进行索引。假设我们有一个8×4数组: In [117]: arr = np.empty((8, 4))In [118]: for i in range(8): ....

火力全開
1分钟前
0
0
Mybaties报错Error querying database

Mybaties我们经常用到动态SQL,如下我们利用动态去做判断,这样写当然没问题,但是当我们不是去判断orgCode(本文中orgCode一直为String类型)是否为空而是判断orgCode是否是一个值的时候该怎...

王子城
3分钟前
0
0
Android 调用手机自带的下载器下载

亲测有用,原文下载地址: 原文地址:https://blog.csdn.net/weixin_36554045/article/details/79108796 下面是原文: 创建一个广播类 public class UpdataBroadcastReceiver extends Broad...

她叫我小渝
7分钟前
0
0
idea工具debug断点红色变成灰色,断点无效

来自:idea工具debug断点红色变成灰色 没事别瞎点,禁用了断点当然不走了 看这篇博客底下的评论笑死我了 真香警告!

不开心的时候不要学习
9分钟前
0
0
知识点总结

jq如何拿到data-info的自定义属性 1.1 原生可以获取到所有属性el.attrbutes 1.2 jq的$(el).attr('属性名称') 继承的几种方式,原型链 2.1 扩展原型对象实现继承 2.2 替换原型对象实现继承 2....

litCabbage
12分钟前
0
0
python语言规范

http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_style_rules/...

ghou-靠墙哭
16分钟前
0
0
istio 监控,遥测 (理论)

Istio提供了一种灵活的模型来强制执行授权策略并收集网格中服务的遥测。 基础架构后端旨在提供用于构建服务的支持功能。它们包括诸如访问控制系统,遥测捕获系统,配额执行系统,计费系统等之...

xiaomin0322
18分钟前
0
0
阿里资深专家面试问题收集

corejava hashcode相等的两个对象一定相等吗?equals呢?反过来相等吗? 介绍一下集合框架? hashtable,hashmap底层实现是什么?hashtable和concurrenthashmap底层实现的区别? hashmap和treemap的...

undefine
19分钟前
8
0
alpine安装软件指定安装源

linux-alpine安装软件指定安装源 一、永久修改apk下载源地址 vi etc/apk/repositories 替换成阿里源 http://mirrors.aliyun.com/alpine/v3.8/main/http://mirrors.aliyun.com/alpine/v3...

我心中有猛狗
19分钟前
0
0
Centos7通过yum安装nginx

添加源地址(直接install可能不是最新版本的) sudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm 安装 sudo yum install -y ng......

iplusx
21分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部