大量图片优化

最近在练习中用GridView加入相册中图片发现加入大量的相片之后,GirdView会变得很卡,想到或许可以用异步加载的方式来解决,但是能力有限,想得到却无法实现。在读了一些大牛的博客和代码之后,终于实现了。

1  在异步加载之前的代码的和普通加载代码一样,只需要在GirdView的Adapter的public View getView(int position, View convertView, ViewGroupparent)方法使用异步加载的方式返回ImageView。

2  如果能把加载过的图片给缓存起来,而不用每次都从sd卡上读取,这样效率应该会提高不少。所以可以先建一个缓存类,MemoryCache,为了能尽可能缓存,又尽可能的不抛出OOM的异常,可以使用SoftReference<Bitmap>,软引用来缓冲图片。MemoryCache类的代码如下:

 /** 
  * 缓存类(图片的路径和图片) 
  */ 
 public class MemoryCache {  
  private HashMap<String, SoftReference<Bitmap>> cache=new HashMap<String, SoftReference<Bitmap>>();  
  //获取缓存图片 
  public Bitmap get(String path) {  
  if(cache.get(path)==null){  
  return null;  
         }else{  
  return cache.get(path).get();  
         }  
     }  
  
  //新增缓存 
  public void put(String url,Bitmap bitmap) {  
         cache.put(url, new SoftReference<Bitmap>(bitmap));  
     }  
  
  //清空缓存 
  public void clear() {  
         cache.clear();  
     }  
 }  

3  弄完了缓存类,接下来就可以弄异步加载的类,这个类主要在维护一个Stack,每次向这个类请求一个ImageView时,就要传入对应ImageView和图片的路径。异步类首先会根据图片的路径先去缓存中查找是否有缓存对应的BItMap,如果有就把他放到ImageView返回,如果没有就把这个ImageView和图片路径放到Stack中,并唤醒加载图片的线程。而加载图片的线程(线程优先权低于UI线程),会无限循环查看Stack大小,如果为0,就进入等待。如果不为0,就依次出栈Stack中的元素进行处理。(感觉像生产者-消费者模式)。

3.1 接下来,就写这个异步类的变量和构造函数了:

 private MemoryCache cache;  
  private PhotosStack photosStack;  
  private LoadImageThread loadImageThread;  
  //用来保存每个月ImageView和对应应加载的图片 
  private Map<ImageView, String> viewPath=Collections.synchronizedMap(new HashMap<ImageView, String>());  
  public LoadImage(Activity activity) {  
  super();  
         cache=new MemoryCache();  
         photosStack=new PhotosStack();  
         loadImageThread=new LoadImageThread(activity);  
  //设置异步加载线程优先权低于UI线程 
         loadImageThread.setPriority(Thread.NORM_PRIORITY);  
     }  

其中MemoryCache是缓存类,PhotosStack类中维护一个Stack,LoadImageThread类是负责异步加载图片的。而viewPath这个变量是为了要保证GridView对应Position放对应的ImageView,如果没有这个变量,GridView中排列就会无序,在处理GridView的点击事件时候,就不好处理。而对viewPath的操作的异步,所以就需要线程安全咯。

PhotosStack代码如下:

 //维护需要被填充的Image和对应图片路径的栈 
 class PhotosStack{  
     Stack<MyPhoto> stack=new Stack<LoadImage.MyPhoto>();  
  //移除指定ImageView 
  public void remove(ImageView iView) {  
  for(int i=0;i<stack.size();i++){  
  if(stack.get(i).imageView==iView){  
                 stack.remove(i);  
  break;  
             }  
         }  
     }  
 }  

其中MyPhoto是一个照片的实体类,有两个属性,图片的路径和对应的ImageView。

 /** 
      * 照片的实体类 
      */ 
  class MyPhoto{  
  public String path;  
  public ImageView imageView;  
  public MyPhoto(String path, ImageView imageView){  
  this.path=path;   
  this.imageView=imageView;  
             }  
     }  

3.2  接下来就可以实现给Adapter的调用的方法loadImage(Stringpath,ImageView iv ),接收两参数——图片路径和对应的ImageView。这个方法,首先会去缓存中查看是否有缓存对应的图片,如果有就把它设给ImageView。如果没有,就先为ImageView设个默认图片,然后以同步块(锁为PhotosStack中的stack)的方式加入PhotosStack中的stack中,并唤醒加载图片的线程。最后还要判断下加载图片的线程是否已经被启动了,如果没有,就启动。

 /** 
      * 填充ImageView 
      * @param activity  
      * @param path 图片的路径 
      * @param iv 被填充的ImageView 
      */ 
  public void loadImage(String path,ImageView iv ) {  
         viewPath.put(iv, path);  
         Bitmap momeryBitmap=cache.get(path);  
  if(momeryBitmap!=null){//有缓存这张图片 
             iv.setImageBitmap(momeryBitmap);  
         }else{//没有缓存 
             iv.setImageResource(R.drawable.ic_launcher);  
  //有可以这个ImageVIew还没出栈,所以需要先出栈 
             photosStack.remove(iv);  
  //由于photosStack中的stack,出栈入栈操作时不会在异步的,所以需要在同步块中完成出入栈,并以photosStack.stack作为锁 
  synchronized (photosStack.stack) {  
                 photosStack.stack.push(new MyPhoto(path, iv));  
  //唤醒持有锁的线程 
                 photosStack.stack.notifyAll();  
             }  
         }  
  if(loadImageThread.getState()==Thread.State.NEW){  
             loadImageThread.start();  
         }  
  
  
     }  

3.3 最后可以实现异步加载图片的方法了,主要是循环判断stack中元素的数量,如果为0 ,说明所有的图片已经被加载完毕了,可以进入等待状态。如果不为0,说明还有图片等待加载,就依次出栈这些元素,依次加载图片,并放到缓存中。代码如下:

 //异步加载图片的线程 
  class LoadImageThread extends Thread{  
         Activity activity;  
  
  public LoadImageThread(Activity activity) {  
  this.activity = activity;  
         }  
  
  @Override 
  public void run() {  
  while(true){  
  try {  
  if(photosStack.stack.size()>0){  
  final MyPhoto photo;  
  //得到栈顶的MyPhoto 
  synchronized (photosStack.stack) {  
                              photo= photosStack.stack.pop();  
                         }   
                         cache.put(photo.path,getSmallBitmap(photo.path) );  
                         String ivPathString=viewPath.get(photo.imageView);  
  if(ivPathString!=null&& ivPathString.equalsIgnoreCase(photo.path)){  
                             Runnable runableRunnable=new Runnable() {  
  @Override 
  public void run() {  
                                     photo.imageView.setImageBitmap(getSmallBitmap(photo.path));  
                                 }  
                             };  
                             activity.runOnUiThread(runableRunnable);  
                         }  
                     }  
  if(photosStack.stack.size()==0){  
  synchronized (photosStack.stack) {  
                             photosStack.stack.wait();  
                         }  
                     }  
                 } catch (InterruptedException e) {  
  // TODO 自动生成的 catch 块 
                     e.printStackTrace();  
                 }  
             }  
         }  
     }  
  

其中缩小图片方法getSmallBitmap(String path),代码如下:

 //缩小图片 
  public Bitmap getSmallBitmap(String path) {  
         Options options=new Options();  
         options.inJustDecodeBounds=true;  
         BitmapFactory.decodeFile(path,options);  
  int REQUIRE_SIZE=80;  
  int scare=1;  
  
  while(true){  
  if(options.outWidth<=REQUIRE_SIZE|| options.outHeight<=REQUIRE_SIZE){  
  break;  
             }else{  
                 options.outWidth=options.outWidth/2;  
                 options.outHeight=options.outHeight/2;  
                 scare++;  
             }   
         }  
         Options newoptions=new Options();  
         newoptions.inSampleSize=scare;  
  return BitmapFactory.decodeFile(path, newoptions);  
     }  

其实这个也适合ListView加载大量图片,可以下载代码

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Android开发指南

Android优化指南

53070
来自专栏林冠宏的技术文章

浅谈 Glide - BitmapPool 的存储时机 & 解答 ViewTarget 在同一View显示不同的图片时,总用同一个 Bitmap 引用的原因

作者:林冠宏 / 指尖下的幽灵 掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8 博客:htt...

425100
来自专栏向治洪

系统捕获异常并发送到服务器

大家都知道,现在安装Android系统的手机版本和设备千差万别,在模拟器上运行良好的程序安装到某款手机上说不定就出现崩溃的现象,开发者个人不可能购买所有设备逐个...

21070
来自专栏MelonTeam专栏

Viewpager循环滑动的实现

导语 本文讲述实现ViewPager循环滑动效果的两种方案: 方案1: 复写ViewPager或者Adapter,扩展dataList,左右各加1...

23760
来自专栏cloudskyme

设计模式(2)-策略模式之多用组合少用继承

首先看一下策略模式的意图 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。 结构 ? 适用性 许多相关...

32270
来自专栏潇涧技术专栏

Head First Android ActionBar

最近在Android Studio中新建项目时发现Activity还是和以前一样,默认继承自ActionBarActivity,但是ActionBarActiv...

9510
来自专栏向治洪

Volley解析之表单提交篇

要实现表单的提交,就要知道表单提交的数据格式是怎么样,这里我从某知名网站抓了一条数据,先来分析别人提交表单的数据格式。  数据包: Connection: ...

23450
来自专栏飞雪无情的博客

Android Intents and Intent Filters(三)

每个data定义一个URI和数据类型(MIME),URI由4个属性来定义,分别是android:scheme,android:host,android:port...

8730
来自专栏木宛城主

Unity应用架构设计(3)——构建View和ViewModel的生命周期

对于一个View而言,本质上是一个MonoBehaviour。它本身就具备生命周期这个概念,比如,Awake,Start,Update,OnDestory等。...

24850
来自专栏Android干货园

Android高仿微信照片选择器+预览+显示照片

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/49...

23320

扫码关注云+社区

领取腾讯云代金券