当前位置: 移动技术网 > IT编程>移动开发>Android > 设计简单的Android图片加载框架

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

2019年07月24日  | 移动技术网IT编程  | 我要评论

科隆家具展,色女二代,北海二手房出售

目前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 开发者来说一直是比较困扰的问题。本文只是提到了最基础的一种解决方法,用于学习还是不错的。

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网