文档章节

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

k
 kim366
发布于 2016/05/13 19:36
字数 2282
阅读 7
收藏 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

没有更多内容

加载失败,请刷新页面

加载更多

PHP生成CSV之内部换行

当我们使用PHP将采集到的文件内容保存到csv文件时,往往需要将采集内容进行二次过滤处理才能得到需要的内容。比如网页中的换行符,空格符等等。 对于空格等处理起来都比较简单,这里我们单独...

豆花饭烧土豆
47分钟前
1
0
使用 mjml 生成 thymeleaf 邮件框架模板

发邮件算是系统开发的一个基本需求了,不过搞邮件模板实在是件恶心事,估计搞过的同仁都有体会。 得支持多种客户端 支持响应式 疼彻心扉的 outlook 多数客户端只支持 inline 形式的 css 布局...

郁也风
50分钟前
4
0
让哲学照亮我们的人生——读《医务工作者需要学点哲学》有感2600字

让哲学照亮我们的人生——读《医务工作者需要学点哲学》有感2600字: 作者:孙冬梅;以前读韩国前总统朴槿惠的著作《绝望锻炼了我》时,里面有一句话令我印象深刻,她说“在我最困难的时期,...

原创小博客
今天
3
0
JAVA-四元数类

public class Quaternion { private final double x0, x1, x2, x3; // 四元数构造函数 public Quaternion(double x0, double x1, double x2, double x3) { this.x0 = ......

Pulsar-V
今天
17
0
Xshell利用Xftp传输文件,使用pure-ftpd搭建ftp服务

Xftp传输文件 如果已经通过Xshell登录到服务器,此时可以使用快捷键ctrl+alt+f 打开Xftp并展示Xshell当前的目录,之后直接拖拽传输文件即可。 pure-ftpd搭建ftp服务 pure-ftpd要比vsftp简单,...

野雪球
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部