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

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

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

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

[java] view plain copy print ?
  1. privatevoidloadImage(finalStringurl,finalintid){
  2. handler.post(newRunnable(){
  3. publicvoidrun(){
  4. Drawabledrawable=null;
  5. try{
  6. drawable=Drawable.createFromStream(newURL(url).openStream(),"image.png");
  7. }catch(IOExceptione){
  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. finalHandlerhandler2=newHandler(){
  2. @Override
  3. publicvoidhandleMessage(Messagemsg){
  4. ((ImageView)LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj);
  5. }
  6. };


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

[java] view plain copy print ?
  1. //引入线程池来管理多线程
  2. privatevoidloadImage3(finalStringurl,finalintid){
  3. executorService.submit(newRunnable(){
  4. publicvoidrun(){
  5. try{
  6. finalDrawabledrawable=Drawable.createFromStream(newURL(url).openStream(),"image.png");
  7. handler.post(newRunnable(){
  8. publicvoidrun(){
  9. ((ImageView)LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);
  10. }
  11. });
  12. }catch(Exceptione){
  13. thrownewRuntimeException(e);
  14. }
  15. }
  16. });
  17. }

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

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

[java] view plain copy print ?
  1. publicclassAsyncImageLoader3{
  2. //为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
  3. publicMap<String,SoftReference<Drawable>>imageCache=newHashMap<String,SoftReference<Drawable>>();
  4. privateExecutorServiceexecutorService=Executors.newFixedThreadPool(5);//固定五个线程来执行任务
  5. privatefinalHandlerhandler=newHandler();
  6. /**
  7. *
  8. *@paramimageUrl图像url地址
  9. *@paramcallback回调接口
  10. *@return返回内存中缓存的图像,第一次加载返回null
  11. */
  12. publicDrawableloadDrawable(finalStringimageUrl,finalImageCallbackcallback){
  13. //如果缓存过就从缓存中取出数据
  14. if(imageCache.containsKey(imageUrl)){
  15. SoftReference<Drawable>softReference=imageCache.get(imageUrl);
  16. if(softReference.get()!=null){
  17. returnsoftReference.get();
  18. }
  19. }
  20. //缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
  21. executorService.submit(newRunnable(){
  22. publicvoidrun(){
  23. try{
  24. finalDrawabledrawable=Drawable.createFromStream(newURL(imageUrl).openStream(),"image.png");
  25. imageCache.put(imageUrl,newSoftReference<Drawable>(drawable));
  26. handler.post(newRunnable(){
  27. publicvoidrun(){
  28. callback.imageLoaded(drawable);
  29. }
  30. });
  31. }catch(Exceptione){
  32. thrownewRuntimeException(e);
  33. }
  34. }
  35. });
  36. returnnull;
  37. }
  38. //从网络上取数据方法
  39. protectedDrawableloadImageFromUrl(StringimageUrl){
  40. try{
  41. returnDrawable.createFromStream(newURL(imageUrl).openStream(),"image.png");
  42. }catch(Exceptione){
  43. thrownewRuntimeException(e);
  44. }
  45. }
  46. //对外界开放的回调接口
  47. publicinterfaceImageCallback{
  48. //注意此方法是用来设置目标对象的图像资源
  49. publicvoidimageLoaded(DrawableimageDrawable);
  50. }
  51. }

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

在主线程调用代码:

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

调用异步加载方法:

[java] view plain copy print ?
  1. //引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
  2. privatevoidloadImage4(finalStringurl,finalintid){
  3. //如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
  4. DrawablecacheImage=asyncImageLoader.loadDrawable(url,newAsyncImageLoader.ImageCallback(){
  5. //请参见实现:如果第一次加载url时下面方法会执行
  6. publicvoidimageLoaded(DrawableimageDrawable){
  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. publicclassAsyncImageLoader{
  2. //为了加快速度,加入了缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
  3. privateMap<String,SoftReference<Drawable>>imageCache=newHashMap<String,SoftReference<Drawable>>();
  4. /**
  5. *
  6. *@paramimageUrl图像url地址
  7. *@paramcallback回调接口
  8. *@return返回内存中缓存的图像,第一次加载返回null
  9. */
  10. publicDrawableloadDrawable(finalStringimageUrl,finalImageCallbackcallback){
  11. //如果缓存过就从缓存中取出数据
  12. if(imageCache.containsKey(imageUrl)){
  13. SoftReference<Drawable>softReference=imageCache.get(imageUrl);
  14. if(softReference.get()!=null){
  15. returnsoftReference.get();
  16. }
  17. }
  18. finalHandlerhandler=newHandler(){
  19. @Override
  20. publicvoidhandleMessage(Messagemsg){
  21. callback.imageLoaded((Drawable)msg.obj);
  22. }
  23. };
  24. newThread(){
  25. publicvoidrun(){
  26. Drawabledrawable=loadImageFromUrl(imageUrl);
  27. imageCache.put(imageUrl,newSoftReference<Drawable>(drawable));
  28. handler.sendMessage(handler.obtainMessage(0,drawable));
  29. }
  30. }.start();
  31. /*
  32. 下面注释的这段代码是Handler的一种代替方法
  33. */
  34. //newAsyncTask(){
  35. //@Override
  36. //protectedDrawabledoInBackground(Object...objects){
  37. //Drawabledrawable=loadImageFromUrl(imageUrl);
  38. //imageCache.put(imageUrl,newSoftReference<Drawable>(drawable));
  39. //returndrawable;
  40. //}
  41. //
  42. //@Override
  43. //protectedvoidonPostExecute(Objecto){
  44. //callback.imageLoaded((Drawable)o);
  45. //}
  46. //}.execute();
  47. returnnull;
  48. }
  49. protectedDrawableloadImageFromUrl(StringimageUrl){
  50. try{
  51. returnDrawable.createFromStream(newURL(imageUrl).openStream(),"src");
  52. }catch(Exceptione){
  53. thrownewRuntimeException(e);
  54. }
  55. }
  56. //对外界开放的回调接口
  57. publicinterfaceImageCallback{
  58. publicvoidimageLoaded(DrawableimageDrawable);
  59. }
  60. }


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

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



xml文件大致如下:

[html] view plain copy print ?
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:orientation="vertical"
  5. android:layout_height="fill_parent">
  6. <ImageViewandroid:id="@+id/image1"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
  7. <ImageViewandroid:id="@+id/image2"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
  8. <ImageViewandroid:id="@+id/image3"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
  9. <ImageViewandroid:id="@+id/image5"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
  10. <ImageViewandroid:id="@+id/image4"android:layout_height="wrap_content"android:layout_width="fill_parent"></ImageView>
  11. </LinearLayout>


更多相关文章

  1. Android热修复三部曲之动态加载补丁.dex文件
  2. Android练手小项目(KTReader)基于mvp架构(三)
  3. 初窥图像处理利器RenderScript
  4. Android(安卓)最火的快速开发框架XUtils
  5. Android开发 Handler+ExecutorService(线程池)+MessageQueue模式
  6. android横竖屏 用法总结
  7. webView 的深入研究
  8. Android下利用zbar类库实现扫一扫
  9. 【异步加载】Android(安卓)网络数据异步加载解决方案

随机推荐

  1. 【译文】通过 android:ssp 高效过滤 Andr
  2. Android访问服务器出现W/System.err(9302
  3. Android(安卓)& Chrome
  4. Android(安卓)ANR问题分析
  5. Android七大布局
  6. listview使用小技巧
  7. ROS与Android的通信
  8. java/android 统计文件夹大小及删除文件
  9. Android 登录界面Demo源码
  10. android在代码中为new出的控件设置ID及se