欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

设计简单的Android图片加载框架

程序员文章站 2024-03-05 11:02:12
目前android 发展至今优秀的图片加载框架太多,例如: volley ,picasso,imageloader,glide等等。但是作为程序猿,懂得其中的实现原理还是相...

目前android 发展至今优秀的图片加载框架太多,例如: volley ,picasso,imageloader,glide等等。但是作为程序猿,懂得其中的实现原理还是相当重要的,只有懂得才能更好地使用。于是乎,今天我就简单设计一个网络加载图片框架。主要就是熟悉图片的网络加载机制。

一般来说,一个优秀的 图片加载框架(imageloader) 应该具备如下功能:

图片压缩

内存缓存

磁盘缓存

图片的同步加载

图片的异步加载

网络拉取

那我们就从以上几个方面进行介绍:

1.图片压缩(有效的降低oom的发生概率)

图片压缩功能我在bitmap 的高效加载中已经做了介绍这里不多说直接上代码。这里直接抽象一个类用于完成图片压缩功能。

public class imageresizer {
 private static final string tag = "imageresizer";

 public imageresizer() {
  super();
  // todo auto-generated constructor stub
 }

 public bitmap decodesampledbitmapfromresource(resources res, int resid,
   int reqwidth, int reqheight) {
  final bitmapfactory.options options = new bitmapfactory.options();
  options.injustdecodebounds = true;
  bitmapfactory.decoderesource(res, resid, options);

  options.insamplesize = calculateinsamplesize(options, reqwidth,
    reqheight);

  options.injustdecodebounds = false;
  return bitmapfactory.decoderesource(res, resid, options);
 }

 public bitmap decodesampledbitmapfrombitmapfiledescriptor(filedescriptor fd,
   int reqwidth,int reqheight){
  final bitmapfactory.options options = new bitmapfactory.options();
  options.injustdecodebounds = true;

  bitmapfactory.decodefiledescriptor(fd, null, options);

  options.insamplesize = calculateinsamplesize(options, reqwidth,
    reqheight);

  options.injustdecodebounds = false;
  return bitmapfactory.decodefiledescriptor(fd, null, options);
 }

 public int calculateinsamplesize(bitmapfactory.options options,
   int reqwidth, int reqheight) {

  final int width = options.outwidth;
  final int height = options.outheight;

  int insamplesize = 1;
  if (height > reqheight || width > reqwidth) {
   final int halfheight = height / 2;
   final int halfwidth = width / 2;
   while ((halfheight / insamplesize) > reqheight
     && (halfwidth / insamplesize) > halfwidth) {
    insamplesize *= 2;
   }
  }
  return insamplesize;

 }

}

2.内存缓存和磁盘缓存

缓存直接选择 lrucache 和 disklrucache 来完成内存缓存和磁盘缓存工作。

首先对其初始化:

private lrucache<string, bitmap> mmemorycache;
private disklrucache mdisklrucache;

public imageloader(context context) {
  mcontext = context.getapplicationcontext();
  //分配内存缓存为当前进程的1/8,磁盘缓存容量为50m
  int maxmemory = (int) (runtime.getruntime().maxmemory() * 1024);
  int cachesize = maxmemory / 8;
  mmemorycache = new lrucache<string, bitmap>(cachesize) {

   @override
   protected int sizeof(string key, bitmap value) {
    return value.getrowbytes() * value.getheight() / 1024;
   }

  };

  file diskcachedir = getdiskchahedir(mcontext, "bitmap");
  if (!diskcachedir.exists()) {
   diskcachedir.mkdirs();
  }
  if (getusablespace(diskcachedir) > disk_cache_size) {
   try {
    mdisklrucache = disklrucache.open(diskcachedir, 1, 1,
      disk_cache_size);
    misdisklrucachecreated = true;
   } catch (ioexception e) {
    e.printstacktrace();
   }
  }
 }

创建完毕后,接下来则需要提供方法来视线添加以及获取的功能。首先来看内存缓存。

private void addbitmaptomemorycache(string key, bitmap bitmap) {
  if (getbitmapfrommemcache(key) == null) {
   mmemorycache.put(key, bitmap);
  }
 }

 private bitmap getbitmapfrommemcache(string key) {
  return mmemorycache.get(key);
 }

相对来说内存缓存比较简单,而磁盘缓存则复杂的多。磁盘缓存(lrudiskcache)并没有直接提供方法来实现,而是要通过editor以及snapshot 来实现对于文件系统的添加以及读取的操作。

首先看一下,editor,它提供了commit 和 abort 方法来提交和撤销对文件系统的写操作。

//将下载的图片写入文件系统,实现磁盘缓存
 private bitmap loadbitmapfromhttp(string url, int reqwidth, int reqheight)
   throws ioexception {
  if (looper.mylooper() == looper.getmainlooper()) {
   throw new runtimeexception("can not visit network from ui thread.");
  }
  if (mdisklrucache == null)
   return null;
  string key = hashkeyformurl(url);
  disklrucache.editor editor = mdisklrucache.edit(key);
  if (editor != null) {
   outputstream outputstream = editor
     .newoutputstream(disk_cache_index);
   if (downloadurltostream(url, outputstream)) {
    editor.commit();
   } else {
    editor.abort();
   }

  }
  mdisklrucache.flush();
  return loadbitmapfordiskcache(url, reqwidth, reqheight);
 }

snapshot, 通过它可以获取磁盘缓存对象对应的 fileinputstream,但是fileinputstream 无法便捷的进行压缩,所以通过filedescriptor 来加载压缩后的图片,最后将加载后的bitmap添加到内存缓存中。

public bitmap loadbitmapfordiskcache(string url, int reqwidth, int reqheight)
   throws ioexception {
  if (looper.mylooper() == looper.getmainlooper()) {
   log.w(tag, "load bitmap from ui thread , it's not recommended");
  }
  if (mdisklrucache == null)
   return null;
  bitmap bitmap = null;
  string key = hashkeyformurl(url);
  disklrucache.snapshot snapshot = mdisklrucache.get(key);
  if (snapshot != null) {
   fileinputstream fileinputstream = (fileinputstream) snapshot
     .getinputstream(disk_cache_index);
   filedescriptor filedescriptor = fileinputstream.getfd();
   bitmap = mimageresizer.decodesampledbitmapfrombitmapfiledescriptor(
     filedescriptor, reqwidth, reqheight);
   if (bitmap != null) {
    addbitmaptomemorycache(key, bitmap);
   }
  }
  return bitmap;
 }

3.同步加载

同步加载的方法需要外部在子线程中调用。

//同步加载
 public bitmap loadbitmap(string uri, int reqwidth, int reqheight) {
  bitmap bitmap = loadbitmpafrommemcache(uri);
  if (bitmap != null) {
   return bitmap;
  }
  try {
   bitmap = loadbitmapfordiskcache(uri, reqwidth, reqheight);
   if (bitmap != null) {
    return bitmap;
   }
   bitmap = loadbitmapfromhttp(uri, reqwidth, reqheight);

  } catch (ioexception e) {
   e.printstacktrace();
  }
  if (bitmap == null && !misdisklrucachecreated) {
   bitmap = downloadbitmapfromurl(uri);
  }
  return bitmap;
 }

从方法中可以看出工作过程遵循如下几步:

首先尝试从内存缓存中读取图片,接着尝试从磁盘缓存中读取图片,最后才会从网络中拉取。此方法不能再主线程中执行,执行环境的检测是在loadbitmapfromhttp中实现的。

if (looper.mylooper() == looper.getmainlooper()) {
   throw new runtimeexception("can not visit network from ui thread.");
  }

4.异步加载

//异步加载
 public void bindbitmap(final string uri, final imageview imageview,
   final int reqwidth, final int reqheight) {

  imageview.settag(tag_key_uri, uri);
  bitmap bitmap = loadbitmpafrommemcache(uri);
  if (bitmap != null) {
   imageview.setimagebitmap(bitmap);
   return;
  }
  runnable loadbitmaptask = new runnable() {

   @override
   public void run() {
    bitmap bitmap = loadbitmap(uri, reqwidth, reqheight);
    if (bitmap != null) {
     loaderresult result = new loaderresult(imageview, uri,
       bitmap);
     mmainhandler.obtainmessage(message_post_result, result)
       .sendtotarget();

    }
   }
  };
  thread_pool_executor.execute(loadbitmaptask);
 }

从bindbitmap的实现来看,bindbitmap 方法会尝试从内存缓存中读取图片,如果读取成功就直接返回结果,否则会在线程池中去调用loadbitmap方法,当图片加载成功后再将图片、图片的地址以及需要绑定的imageview封装成一个loaderresult对象,然后再通过mmainhandler向主线程发送一个消息,这样就可以在主线程中给imageview设置图片了。

下面来看一下,bindbitmap这个方法中用到的线程池和handler,首先看一下线程池 thread_pool_executor 的实现。

private static final int cpu_count = runtime.getruntime()
   .availableprocessors();
private static final int core_pool_size = cpu_count + 1;
private static final int maximum_pool_size = cpu_count * 2 + 1;
private static final long keep_alive = 10l;


private static final threadfactory sthreadfactory = new threadfactory() {
  private final atomicinteger mcount = new atomicinteger();

  @override
  public thread newthread(runnable r) {
   // todo auto-generated method stub
   return new thread(r, "imageloader#" + mcount.getandincrement());
  }
 };


public static final executor thread_pool_executor = new threadpoolexecutor(
   core_pool_size, maximum_pool_size, keep_alive, timeunit.seconds,
   new linkedblockingdeque<runnable>(), sthreadfactory);


1.使用线程池和handler的原因。

首先不能用普通线程去实现,如果采用普通线程去加载图片,随着列表的滑动可能会产生大量的线程,这样不利于效率的提升。 handler 的实现 ,直接采用了 主线程的looper来构造handler 对象,这就使得 imageloader 可以在非主线程构造。另外为了解决由于view复用所导致的列表错位这一问题再给imageview 设置图片之前会检查他的url有没有发生改变,如果发生改变就不再给它设置图片,这样就解决了列表错位问题。

private handler mmainhandler = new handler(looper.getmainlooper()) {

  @override
  public void handlemessage(message msg) {
   loaderresult result = (loaderresult) msg.obj;
   imageview imageview = result.imageview;
   imageview.setimagebitmap(result.bitmap);
   string uri = (string) imageview.gettag(tag_key_uri);
   if (uri.equals(result.uri)) {
    imageview.setimagebitmap(result.bitmap);
   } else {
    log.w(tag, "set image bitmap,but url has changed , ignored!");
   }
  }

 };

总结:

图片加载的问题 ,尤其是大量图片的加载,对于android 开发者来说一直是比较困扰的问题。本文只是提到了最基础的一种解决方法,用于学习还是不错的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。