Android异步加载图像(含线程池,缓存方法)

简介:

研究了android从网络上异步加载图像:

(1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法。

     在主线程中new 一个Handler对象,加载图像方法如下所示

[java]  view plain copy print ?
  1. private void loadImage(final String url, final int id) {  
  2.         handler.post(new Runnable() {  
  3.                public void run() {  
  4.                    Drawable drawable = null;  
  5.                    try {  
  6.                        drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");  
  7.                    } catch (IOException e) {  
  8.                    }  
  9.                    ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);  
  10.                }  
  11.            });  
  12.    }  

上面这个方法缺点很显然,经测试,如果要加载多个图片,这并不能实现异步加载,而是等到所有的图片都加载完才一起显示,因为它们都运行在一个线程中。

然后,我们可以简单改进下,将Handler+Runnable模式改为Handler+Thread+Message模式不就能实现同时开启多个线程吗?

(2)在主线程中new 一个Handler对象,代码如下:

[java]  view plain copy print ?
  1. final Handler handler2=new Handler(){  
  2.          @Override  
  3.          public void handleMessage(Message msg) {  
  4.             ((ImageView) LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj);  
  5.          }  
  6.      };  


对应加载图像代码如下:对应加载图像代码如下:对应加载图像代码如下:

[java]  view plain copy print ?
  1. // 引入线程池来管理多线程  
  2.    private void loadImage3(final String url, final int id) {  
  3.        executorService.submit(new Runnable() {  
  4.            public void run() {  
  5.                try {  
  6.                    final Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");  
  7.                    handler.post(new Runnable() {  
  8.   
  9.                        public void run() {  
  10.                            ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);  
  11.                        }  
  12.                    });  
  13.                } catch (Exception e) {  
  14.                    throw new RuntimeException(e);  
  15.                }  
  16.            }  
  17.        });  
  18.    }  

(4)为了更方便使用我们可以将异步加载图像方法封装一个类,对外界只暴露一个方法即可,考虑到效率问题我们可以引入内存缓存机制,做法是

建立一个HashMap,其键(key)为加载图像url,其值(value)是图像对象Drawable。先看一下我们封装的类

[java]  view plain copy print ?
  1. public class AsyncImageLoader3 {  
  2.    //为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)  
  3.     public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();  
  4.     private ExecutorService executorService = Executors.newFixedThreadPool(5);    //固定五个线程来执行任务  
  5.     private final Handler handler=new Handler();  
  6.   
  7.      /** 
  8.      * 
  9.      * @param imageUrl     图像url地址 
  10.      * @param callback     回调接口 
  11.      * @return     返回内存中缓存的图像,第一次加载返回null 
  12.      */  
  13.     public Drawable loadDrawable(final String imageUrl, final ImageCallback callback) {  
  14.         //如果缓存过就从缓存中取出数据  
  15.         if (imageCache.containsKey(imageUrl)) {  
  16.             SoftReference<Drawable> softReference = imageCache.get(imageUrl);  
  17.             if (softReference.get() != null) {  
  18.                 return softReference.get();  
  19.             }  
  20.         }  
  21.         //缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中  
  22.          executorService.submit(new Runnable() {  
  23.             public void run() {  
  24.                 try {  
  25.                     final Drawable drawable = Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png");  
  26.   
  27.                     imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));  
  28.   
  29.                     handler.post(new Runnable() {  
  30.                         public void run() {  
  31.                            callback.imageLoaded(drawable);  
  32.                         }  
  33.                     });  
  34.                 } catch (Exception e) {  
  35.                     throw new RuntimeException(e);  
  36.                 }  
  37.             }  
  38.         });  
  39.         return null;  
  40.     }  
  41.      //从网络上取数据方法  
  42.     protected Drawable loadImageFromUrl(String imageUrl) {  
  43.         try {  
  44.             return Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png");  
  45.         } catch (Exception e) {  
  46.             throw new RuntimeException(e);  
  47.         }  
  48.     }  
  49.     //对外界开放的回调接口  
  50.     public interface ImageCallback {  
  51.         //注意 此方法是用来设置目标对象的图像资源  
  52.         public void imageLoaded(Drawable imageDrawable);  
  53.     }  
  54. }  

这样封装好后使用起来就方便多了。在主线程中首先要引入AsyncImageLoader3 对象,然后直接调用其loadDrawable方法即可,需要注意的是ImageCallback接口的imageLoaded方法是唯一可以把加载的图像设置到目标ImageView或其相关的组件上。

在主线程调用代码:

  先实例化对象 private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();

  调用异步加载方法:

 

[java]  view plain copy print ?
  1. //引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程  
  2.     private void loadImage4(final String url, final int id) {  
  3.           //如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行  
  4.          Drawable cacheImage = asyncImageLoader.loadDrawable(url,new AsyncImageLoader.ImageCallback() {  
  5.              //请参见实现:如果第一次加载url时下面方法会执行  
  6.              public void imageLoaded(Drawable imageDrawable) {  
  7.                ((ImageView) findViewById(id)).setImageDrawable(imageDrawable);  
  8.              }  
  9.          });  
  10.         if(cacheImage!=null){  
  11.           ((ImageView) findViewById(id)).setImageDrawable(cacheImage);  
  12.         }  
  13.     }  


5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下:

[java]  view plain copy print ?
  1. public class AsyncImageLoader {  
  2.    //为了加快速度,加入了缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)  
  3.     private Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();  
  4.   
  5.      /** 
  6.      * 
  7.      * @param imageUrl     图像url地址 
  8.      * @param callback     回调接口 
  9.      * @return     返回内存中缓存的图像,第一次加载返回null 
  10.      */  
  11.     public Drawable loadDrawable(final String imageUrl, final ImageCallback callback) {  
  12.         //如果缓存过就从缓存中取出数据  
  13.         if (imageCache.containsKey(imageUrl)) {  
  14.             SoftReference<Drawable> softReference = imageCache.get(imageUrl);  
  15.             if (softReference.get() != null) {  
  16.                 return softReference.get();  
  17.             }  
  18.         }  
  19.   
  20.         final Handler handler = new Handler() {  
  21.             @Override  
  22.             public void handleMessage(Message msg) {  
  23.                 callback.imageLoaded((Drawable) msg.obj);  
  24.             }  
  25.         };  
  26.         new Thread() {  
  27.             public void run() {  
  28.                 Drawable drawable = loadImageFromUrl(imageUrl);  
  29.                 imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));  
  30.                 handler.sendMessage(handler.obtainMessage(0, drawable));  
  31.   
  32.             }  
  33.   
  34.         }.start();  
  35.         /* 
  36.         下面注释的这段代码是Handler的一种代替方法 
  37.          */  
  38. //        new AsyncTask() {  
  39. //            @Override  
  40. //            protected Drawable doInBackground(Object... objects) {  
  41. //                  Drawable drawable = loadImageFromUrl(imageUrl);  
  42. //                imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));  
  43. //                return  drawable;  
  44. //            }  
  45. //  
  46. //            @Override  
  47. //            protected void onPostExecute(Object o) {  
  48. //                  callback.imageLoaded((Drawable) o);  
  49. //            }  
  50. //        }.execute();  
  51.         return null;  
  52.     }  
  53.   
  54.     protected Drawable loadImageFromUrl(String imageUrl) {  
  55.         try {  
  56.             return Drawable.createFromStream(new URL(imageUrl).openStream(), "src");  
  57.         } catch (Exception e) {  
  58.             throw new RuntimeException(e);  
  59.         }  
  60.     }  
  61.     //对外界开放的回调接口  
  62.     public interface ImageCallback {  
  63.         public void imageLoaded(Drawable imageDrawable);  
  64.     }  
  65. }  


至此,异步加载就介绍完了,下面给出的代码为测试用的完整代码:

[java]  view plain copy print ?
  1. package com.bshark.supertelphone.activity;  
  2.   
  3. import android.app.Activity;  
  4. import android.graphics.drawable.Drawable;  
  5. import android.os.Bundle;  
  6. import android.os.Handler;  
  7. import android.os.Message;  
  8. import android.widget.ImageView;  
  9. import com.bshark.supertelphone.R;  
  10. import com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader;  
  11. import com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader3;  
  12.   
  13. import java.io.IOException;  
  14. import java.net.URL;  
  15. import java.util.concurrent.ExecutorService;  
  16. import java.util.concurrent.Executors;  
  17.   
  18. public class LazyLoadImageActivity extends Activity {  
  19.        final Handler handler=new Handler();  
  20.       final Handler handler2=new Handler(){  
  21.           @Override  
  22.           public void handleMessage(Message msg) {  
  23.              ((ImageView) LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj);  
  24.           }  
  25.       };  
  26. private ExecutorService executorService = Executors.newFixedThreadPool(5);    //固定五个线程来执行任务  
  27.     private AsyncImageLoader asyncImageLoader = new AsyncImageLoader();  
  28.     private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();  
  29.   
  30.   
  31. @Override  
  32. public void onCreate(Bundle savedInstanceState) {  
  33.   super.onCreate(savedInstanceState);  
  34.   setContentView(R.layout.main);  
  35.     
  36. //  loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);  
  37. //  loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);  
  38. //  loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);  
  39. //        loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);  
  40. //  loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);  
  41.   
  42.         loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);  
  43.   loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);  
  44.   loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);  
  45.         loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);  
  46.   loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);  
  47. //        loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);  
  48. //  loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);  
  49. //  loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);  
  50. //        loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);  
  51. //  loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);  
  52.   
  53. //        loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);  
  54. //  loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);  
  55. //  loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);  
  56. //        loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);  
  57. //  loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);  
  58.   
  59. //        loadImage5("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1);  
  60. //        //为了测试缓存而模拟的网络延时  
  61. //        SystemClock.sleep(2000);  
  62. //  loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image2);  
  63. //        SystemClock.sleep(2000);  
  64. //  loadImage5("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3);  
  65. //        SystemClock.sleep(2000);  
  66. //        loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);  
  67. //        SystemClock.sleep(2000);  
  68. //  loadImage5("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5);  
  69. //        SystemClock.sleep(2000);  
  70. //         loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image4);  
  71. }  
  72.   
  73. @Override  
  74. protected void onDestroy() {  
  75.   executorService.shutdown();  
  76.   super.onDestroy();  
  77. }  
  78.     //线程加载图像基本原理  
  79.     private void loadImage(final String url, final int id) {  
  80.          handler.post(new Runnable() {  
  81.                 public void run() {  
  82.                     Drawable drawable = null;  
  83.                     try {  
  84.                         drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");  
  85.                     } catch (IOException e) {  
  86.                     }  
  87.                     ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);  
  88.                 }  
  89.             });  
  90.     }  
  91.      //采用handler+Thread模式实现多线程异步加载  
  92.      private void loadImage2(final String url, final int id) {  
  93.          Thread thread = new Thread(){  
  94.              @Override  
  95.              public void run() {  
  96.                Drawable drawable = null;  
  97.                     try {  
  98.                         drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");  
  99.                     } catch (IOException e) {  
  100.                     }  
  101.   
  102.                 Message message= handler2.obtainMessage() ;  
  103.                  message.arg1 = id;  
  104.                  message.obj = drawable;  
  105.                  handler2.sendMessage(message);  
  106.              }  
  107.          };  
  108.          thread.start();  
  109.          thread = null;  
  110.     }  
  111.     // 引入线程池来管理多线程  
  112.     private void loadImage3(final String url, final int id) {  
  113.         executorService.submit(new Runnable() {  
  114.             public void run() {  
  115.                 try {  
  116.                     final Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");  
  117.                     handler.post(new Runnable() {  
  118.   
  119.                         public void run() {  
  120.                             ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);  
  121.                         }  
  122.                     });  
  123.                 } catch (Exception e) {  
  124.                     throw new RuntimeException(e);  
  125.                 }  
  126.             }  
  127.         });  
  128.     }  
  129.     //引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程  
  130.     private void loadImage4(final String url, final int id) {  
  131.           //如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行  
  132.          Drawable cacheImage = asyncImageLoader.loadDrawable(url,new AsyncImageLoader.ImageCallback() {  
  133.              //请参见实现:如果第一次加载url时下面方法会执行  
  134.              public void imageLoaded(Drawable imageDrawable) {  
  135.                ((ImageView) findViewById(id)).setImageDrawable(imageDrawable);  
  136.              }  
  137.          });  
  138.         if(cacheImage!=null){  
  139.           ((ImageView) findViewById(id)).setImageDrawable(cacheImage);  
  140.         }  
  141.     }  
  142.   
  143.     //采用Handler+Thread+封装外部接口  
  144.     private void loadImage5(final String url, final int id) {  
  145.           //如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行  
  146.          Drawable cacheImage = asyncImageLoader3.loadDrawable(url,new AsyncImageLoader3.ImageCallback() {  
  147.              //请参见实现:如果第一次加载url时下面方法会执行  
  148.              public void imageLoaded(Drawable imageDrawable) {  
  149.                ((ImageView) findViewById(id)).setImageDrawable(imageDrawable);  
  150.              }  
  151.          });  
  152.         if(cacheImage!=null){  
  153.                     ((ImageView) findViewById(id)).setImageDrawable(cacheImage);  
  154.         }  
  155.     }  
  156.   
  157.   
  158. }  



xml文件大致如下:

[html]  view plain copy print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2.   
  3. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  4.               android:layout_width="fill_parent"  
  5.               android:orientation="vertical"  
  6.               android:layout_height="fill_parent" >  
  7.   <ImageView android:id="@+id/image1" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView>  
  8.    <ImageView android:id="@+id/image2" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView>  
  9.     <ImageView android:id="@+id/image3" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView>  
  10.     <ImageView android:id="@+id/image5" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView>  
  11.     <ImageView android:id="@+id/image4" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView>  
  12. </LinearLayout>  
相关文章
|
1月前
|
缓存 监控 前端开发
在资源加载优化中,如何利用浏览器缓存提升性能?
通过以上这些方法,可以有效地利用浏览器缓存来提升资源加载的性能,减少网络请求次数,提高用户体验和应用的响应速度。同时,需要根据具体的应用场景和资源特点进行灵活调整和优化,以达到最佳的效果。此外,随着技术的不断发展和变化,还需要持续关注和学习新的缓存优化方法和策略。
93 53
|
19小时前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
19小时前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
2月前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
81 15
Android 系统缓存扫描与清理方法分析
|
1月前
|
存储 缓存 监控
利用 Redis 缓存特性避免缓存穿透的策略与方法
【10月更文挑战第23天】通过以上对利用 Redis 缓存特性避免缓存穿透的详细阐述,我们对这一策略有了更深入的理解。在实际应用中,我们需要根据具体情况灵活运用这些方法,并结合其他技术手段,共同保障系统的稳定和高效运行。同时,要不断关注 Redis 缓存特性的发展和变化,及时调整策略,以应对不断出现的新挑战。
71 10
|
1月前
|
缓存 监控 NoSQL
Redis 缓存穿透的检测方法与分析
【10月更文挑战第23天】通过以上对 Redis 缓存穿透检测方法的深入探讨,我们对如何及时发现和处理这一问题有了更全面的认识。在实际应用中,我们需要综合运用多种检测手段,并结合业务场景和实际情况进行分析,以确保能够准确、及时地检测到缓存穿透现象,并采取有效的措施加以解决。同时,要不断优化和改进检测方法,提高检测的准确性和效率,为系统的稳定运行提供有力保障。
57 5
|
2月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
32 3
|
2月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
25 2
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
25 1
|
2月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
47 1