返回介绍

3.1 App 图片缓存设计

发布于 2024-08-17 23:46:12 字数 6850 浏览 0 评论 0 收藏 0

App缓存分为两部分,数据缓存和图片缓存。

我们在第2章的2.2节介绍了App数据缓存,从而把从MobileAPI获取到的数据缓存到本地,减少了调用MobileAPI的次数。

本节将介绍图片缓存策略。

3.1.1 ImageLoader设计原理

Android上最让人头疼的莫过于从网络获取图片、显示、回收,任何一个环节有问题都可能直接OOM。尤其是在列表页,会加载大量网络上的图片,每当快速划动列表的时候,都会很卡,甚至会因为内存溢出而崩溃。

这时就轮到ImageLoader上场表演了。ImageLoader的目的是为了实现异步的网络图片加载、缓存及显示,支持多线程异步加载。 [1]

ImageLoader的工作原理是这样的:在显示图片的时候,它会先在内存中查找;如果没有,就去本地查找;如果还没有,就开一个新的线程去下载这张图片,下载成功会把图片同时缓存到内存和本地。

基于这个原理,我们可以在每次退出一个页面的时候,把ImageLoader内存中的缓存全都清除,这样就节省了大量内存,反正下次再用到的时候从本地再取出来就是了。

此外,由于ImageLoader对图片是软引用的形式,所以内存中的图片会在内存不足的时候被系统回收(内存足够的时候不会对其进行垃圾回收)。 [2]

3.1.2 ImageLoader的使用

ImageLoader由三大组件组成:

·ImageLoaderConfiguration——对图片缓存进行总体配置,包括内存缓存的大小、本地缓存的大小和位置、日志、下载策略(FIFO还是LIFO)等等。

·ImageLoader——我们一般使用displayImage来把URL对应的图片显示在ImageView上。

·DisplayImageOptions——在每个页面需要显示图片的地方,控制如何显示的细节,比如指定下载时的默认图(包括下载中、下载失败、URL为空等),是否将缓存放到内存或者本地磁盘。

借用博客园上陈哈哈的博文 [3] 对三者关系的一个比喻,“他们有点像厨房规定、厨师、客户个人口味之间的关系。ImageLoaderConfiguration就像是厨房里面的规定,每一个厨师要怎么着装,要怎么保持厨房的干净,这是针对每一个厨师都适用的规定,而且不允许个性化改变。ImageLoader就像是具体做菜的厨师,负责具体菜谱的制作。DisplayImageOptions就像每个客户的偏好,根据客户是重口味还是清淡,每一个ImageLoader根据DisplayImageOptions的要求具体执行。”

下面我们介绍如何使用ImageView:

1)在YoungHeartApplication中总体配置ImageLoader:

public class YoungHeartApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    CacheManager.getInstance().initCacheDir();
    ImageLoaderConfiguration config = 
        new ImageLoaderConfiguration.Builder
        (getApplicationContext())
        .threadPriority(Thread.NORM_PRIORITY - 2)
        .memoryCacheExtraOptions(480, 480)
        .memoryCacheSize(2 * 1024 * 1024)
        .denyCacheImageMultipleSizesInMemory()
        .discCacheFileNameGenerator(new Md5FileNameGenerator())
        .tasksProcessingOrder(QueueProcessingType.LIFO)
        .memoryCache(new WeakMemoryCache()).build();
    ImageLoader.getInstance().init(config);
  }
}

2)在使用ImageView加载图片的地方,配置当前页面的ImageLoader选项。有可能是Activity,也有可能是Adapter:

public CinemaAdapter(ArrayList<CinemaBean> cinemaList,
    AppBaseActivity context) {
  this.cinemaList = cinemaList;
  this.context = context;
  options = new DisplayImageOptions.Builder()
      .showStubImage(R.drawable.ic_launcher)
      .showImageForEmptyUri(R.drawable.ic_launcher)
      .cacheInMemory()
      .cacheOnDisc()
      .build();
}

3)在使用ImageView加载图片的地方,使用ImageLoader,代码片段节选自CinemaAdapter:

CinemaBean cinema = cinemaList.get(position);
holder.tvCinemaName.setText(cinema.getCinemaName());
holder.tvCinemaId.setText(cinema.getCinemaId());
context.imageLoader.displayImage(cinemaList.get(position)
    .getCinemaPhotoUrl(), holder.imgPhoto);

其中displayImage方法的第一个参数是图片的URL,第二个参数是ImageView控件。

一般来说,ImageLoader性能如果有问题,就和这里的配置有关,尤其是ImageLoader-Configuration。我列举在上面的配置代码是目前比较通用的,请大家参考。

3.1.3 ImageLoader优化

尽管ImageLoader很强大,但一直把图片缓存在内存中,会导致内存占用过高。虽然对图片的引用是软引用,软引用在内存不够的时候会被GC,但我们还是希望减少GC的次数,所以要经常手动清理ImageLoader中的缓存。

我们在AppBaseActivity中的onDestroy方法中,执行ImageLoader的clearMemoryCache方法,以确保页面销毁时,把为了显示这个页面而增加的内存缓存清除。这样,即使到了下个页面要复用之前加载过的图片,虽然内存中没有了,根据ImageLoader的缓存策略,还是可以在本地磁盘上找到:

public abstract class AppBaseActivity extends BaseActivity {
  protected boolean needCallback;
  protected ProgressDialog dlg;
  public ImageLoader imageLoader = ImageLoader.getInstance();
  protected void onDestroy() {
    // 回收该页面缓存在内存的图片
    imageLoader.clearMemoryCache();
    super.onDestroy();
  }

本章没有过多讨论ImageLoader的代码实现,只是描述了它的实现原理。有兴趣的朋友可以参考下列文章,里面有很深入的研究:

1)简介ImageLoader。

地址:http://blog.csdn.net/yueqinglkong/article/details/27660107

2)Android-Universal-Image-Loader图片异步加载类库的使用(超详细配置)。

地址:http://blog.csdn.net/vipzjyno1/article/details/23206387

3)Android开源框架Universal-Image-Loader完全解析。

地址:http://blog.csdn.net/xiaanming/article/details/39057201

3.1.4 图片加载利器Fresco

就在本书写作期间,Facebook开源了它的Android图片加载组件Fresco。

我之所以关注这个Fresco组件,是因为我负责的App用一段时间后就占据了180M左右的内存,App会变得很卡。我们使用MAT分析内存,发现让内存居高不下的罪魁祸首就是图片。于是我们把目光转向Fresco,开始优化App占用的内存。

Fresco使用起来很简单,如下所示:

·在Application级别,对Fresco进行初始化,如下所示:

Fresco.initialize(getApplicationContext());

·与ImageLoader等传统第三方图片处理SDK不同,Fresco是基于控件级别的,所以我们把程序中显示网络图片的ImageView都替换为SimpleDraweeView即可,并在ImageView所在的布局文件中添加fresco命名空间,如下所示:

<LinearLayout 
  xmlns:android="http:// schemas.android.com/apk/res/android"
  xmlns:fresco="http:// schemas.android.com/apk/res-auto">
<com.facebook.drawee.view.SimpleDraweeView
  android:id="@+id/imgView"
  android:layout_width="10dp"
  android:layout_height="10dp"
  fresco:placeholderImage="@drawable/placeholder" />

·在Activity中为这个图片控件指定要显示的网络图片:

Uri uri = Uri.parse("http:// www.bb.com/a.png");
draweeView.setImageURI(uri);

Fresco的原理是,设计了一个Image Pipeline的概念,它负责先后检查内存、磁盘文件(Disk),如果都没有再老老实实从网络下载图片,如图3-1所示,箭头上标记了jpg或bmp格式的,表示Cache中有图片,直接取出;没有标记,则表示Cache中找不到。

图3-1 Image Pipeline的工作流

我们可以像配置ImageLoader那样配置Fresco中的Image Pipeline,使用ImagePipelineConfig来做这个事情。

Fresco有3个线程池,其中3个线程用于网络下载图片,2个线程用于磁盘文件的读写,还有2个线程用于CPU相关操作,比如图片解码、转换,以及放在后台执行的一些费时操作。

接下来介绍Fresco三层缓存的概念。这才是Fresco最核心的技术,它比其他图片SDK吃内存小,就在于这个全新的缓存设计。

第一层:Bitmap缓存

·在Android 5.0系统中,考虑到内存管理有了很大改进,所以Bitmap缓存位于Java的堆(heap)中。

·而在Android 4.x和更低的系统,Bitmap缓存位于ashmem中,而不是位于Java的堆(heap)中。这意味着图片的创建和回收不会引发过多的GC,从而让App运行得更快。

当App切换到后台时,Bitmap缓存会被清空。

第二层:内存缓存

内存缓存中存储了图片的原始压缩格式。从内存缓存中取出的图片,在显示前必须先解码。当App切换到后台时,内存缓存也会被清空。

第三层:磁盘缓存

磁盘缓存,又名本地存储。磁盘缓存中存储的也是图片的原始压缩格式。在使用前也要先解码。当App切换到后台时,磁盘缓存不会丢失,即使关机也不会。

Fresco有很多高级的应用,对于大部分App而言,基本还用不到。只要掌握上述简单的使用方法就能极大地节省内存了。我做的App原先占用180MB的内存,现在只会占据80MB左右的内存了。这也是我为什么要在本书中增加这一部分内容的原因。

关于Fresco的更多介绍请参见:

·Fresco在GitHub上的源码:https://github.com/mkottman/AndroLua

·Fresco官方文档:http://fresco-cn.org/docs/index.html

[1] ImageLoader在GitHub的下载地址:https://github.com/nostra13/Android-Universal-Image-Loader。

[2] 关于Java强引用、软引用、弱引用、虚引用的介绍,请参考这篇文章:http://www.cnblogs.com/blogof lee/archive/2012/03/22/2411124.html。

[3] 详细内容请参见http://www.cnblogs.com/kissazi2/p/3886563.html。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文