实例演示Android异步加载图片

简介: 本文给大家演示异步加载图片的分析过程。让大家了解异步加载图片的好处,以及如何更新UI。首先给出main.xml布局文件:简单来说就是 LinearLayout 布局,其下放了2个TextView和5个ImageView。

本文给大家演示异步加载图片的分析过程。让大家了解异步加载图片的好处,以及如何更新UI。
首先给出main.xml布局文件:
简单来说就是 LinearLayout 布局,其下放了2个TextView和5个ImageView。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     android:orientation="vertical"
 5     android:layout_width="fill_parent"
 6     android:layout_height="fill_parent">
 7     <TextView
 8         android:text="图片区域开始"
 9         android:id="@+id/textView2"
10         android:layout_width="wrap_content"
11         android:layout_height="wrap_content" />
12     <ImageView
13         android:id="@+id/imageView1"
14         android:layout_height="wrap_content"
15         android:src="@drawable/icon"
16         android:layout_width="wrap_content" />
17     <ImageView
18         android:id="@+id/imageView2"
19         android:layout_height="wrap_content"
20         android:src="@drawable/icon"
21         android:layout_width="wrap_content" />
22     <ImageView
23         android:id="@+id/imageView3"
24         android:layout_height="wrap_content"
25         android:src="@drawable/icon"
26         android:layout_width="wrap_content" />
27     <ImageView
28         android:id="@+id/imageView4"
29         android:layout_height="wrap_content"
30         android:src="@drawable/icon"
31         android:layout_width="wrap_content" />
32     <ImageView
33         android:id="@+id/imageView5"
34         android:layout_height="wrap_content"
35         android:src="@drawable/icon"
36         android:layout_width="wrap_content" />
37     <TextView
38         android:text="图片区域结束"
39         android:id="@+id/textView1"
40         android:layout_width="wrap_content"
41         android:layout_height="wrap_content" />
42 </LinearLayout>

我们将演示的过程是异步从服务器上下载5张不同图片,依次放入这5个ImageView。上下2个TextView 是为了方便我们看是否阻塞了UI的显示。
当然 AndroidManifest.xml 文件中要配置好网络访问权限。

1 <uses-permission android:name="android.permission.INTERNET" />

1)Handler+Runnable模式
我们先看一个并不是异步线程加载的例子,而是使用 Handler+Runnable模式。
注意这里不是新开的线程,这里的代码其实是在UI主线程中下载图片的。
我们运行下面代码时,会发现它其实是阻塞了整个界面的显示,需要所有图片都加载完成后,才能显示界面。

 1 package com.szy.textviewimagedemo;
 2 
 3 import java.io.IOException;
 4 import java.net.URL;
 5 
 6 import android.app.Activity;
 7 import android.graphics.drawable.Drawable;
 8 import android.os.Bundle;
 9 import android.os.Handler;
10 import android.os.SystemClock;
11 import android.util.Log;
12 import android.widget.ImageView;
13 
14 /**
15  *@author coolszy
16  *@date 2012-2-13
17  *@blog http://blog.92coding.com
18  *
19  */
20 public class MainActivity extends Activity
21 {
22     @Override
23     public void onCreate(Bundle savedInstanceState)
24     {
25         super.onCreate(savedInstanceState);
26         setContentView(R.layout.main);
27         loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
28         loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
29         loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
30         loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
31         loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5);
32     }
33 
34     private Handler handler = new Handler();
35 
36     private void loadImage(final String url, final int id)
37     {
38         handler.post(new Runnable()
39         {
40             public void run()
41             {
42                 Drawable drawable = null;
43                 try
44                 {
45                     drawable = Drawable.createFromStream(new URL(url).openStream(), "image.gif");
46                 } catch (IOException e)
47                 {
48                     Log.i("MainActivity", e.getMessage());
49                 }
50                 if (drawable == null)
51                 {
52                     Log.i("MainActivity", "null drawable");
53                 } else
54                 {
55                     Log.i("MainActivity", "not null drawable");
56                 }
57                 // 为了测试缓存而模拟的网络延时
58                 SystemClock.sleep(2000);
59                 ((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);
60             }
61         });
62     }
63 }

2)Handler+Thread+Message模式
这种模式使用了线程,所以可以看到异步加载的效果。
核心代码:

 1 package com.szy.textviewimagedemo;
 2 
 3 import java.io.IOException;
 4 import java.net.URL;
 5 
 6 import android.app.Activity;
 7 import android.graphics.drawable.Drawable;
 8 import android.os.Bundle;
 9 import android.os.Handler;
10 import android.os.Message;
11 import android.os.SystemClock;
12 import android.util.Log;
13 import android.widget.ImageView;
14 
15 /**
16  *@author coolszy
17  *@date 2012-2-13
18  *@blog http://blog.92coding.com
19  *
20  */
21 public class MainActivity extends Activity
22 {
23     @Override
24     public void onCreate(Bundle savedInstanceState)
25     {
26         super.onCreate(savedInstanceState);
27         setContentView(R.layout.main);
28         Log.i("MainActivity", "MainThread ID:"+Thread.currentThread().getId());
29         loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
30         loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
31         loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
32         loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
33         loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5);
34     }
35 
36     final Handler handler = new Handler()
37     {
38         @Override
39         public void handleMessage(Message msg)
40         {
41             Log.i("MainActivity", "UpdateUIThread ID:"+Thread.currentThread().getId());
42             ((ImageView) MainActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable) msg.obj);
43         }
44     };
45 
46     // 采用handler+Thread模式实现多线程异步加载
47     private void loadImage(final String url, final int id)
48     {
49         Thread thread = new Thread()
50         {
51             @Override
52             public void run()
53             {
54                 Drawable drawable = null;
55                 try
56                 {
57                     Log.i("MainActivity", "Thread ID:"+Thread.currentThread().getId());
58                     drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");
59                 } catch (IOException e)
60                 {
61                     Log.i("MainActivity", e.getMessage());
62                 }
63 
64                 // 模拟网络延时
65                 SystemClock.sleep(2000);
66 
67                 Message message = handler.obtainMessage();
68                 message.arg1 = id;
69                 message.obj = drawable;
70                 handler.sendMessage(message);
71             }
72         };
73         thread.start();
74         thread = null;
75     }
76 }

这时候我们可以看到实现了异步加载, 界面打开时,五个ImageView都是没有图的,然后在各自线程下载完后才把图自动更新上去。
3)Handler+ExecutorService(线程池)+MessageQueue模式
能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。
这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就使用它。
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
下面的演示例子是创建一个可重用固定线程数的线程池。
核心代码

 1 package com.szy.textviewimagedemo;
 2 
 3 import java.net.URL;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 
 7 import android.app.Activity;
 8 import android.graphics.drawable.Drawable;
 9 import android.os.Bundle;
10 import android.os.Handler;
11 import android.os.SystemClock;
12 import android.util.Log;
13 import android.widget.ImageView;
14 
15 /**
16  *@author coolszy
17  *@date 2012-2-13
18  *@blog http://blog.92coding.com
19  *
20  */
21 public class MainActivity extends Activity
22 {
23     @Override
24     public void onCreate(Bundle savedInstanceState)
25     {
26         super.onCreate(savedInstanceState);
27         setContentView(R.layout.main);
28         Log.i("MainActivity", "MainThread ID:"+Thread.currentThread().getId());
29         loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
30         loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
31         loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
32         loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
33         loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5);
34     }
35 
36     private Handler handler = new Handler();
37 
38     private ExecutorService executorService = Executors.newFixedThreadPool(5);
39 
40     // 引入线程池来管理多线程
41     private void loadImage(final String url, final int id)
42     {
43         executorService.submit(new Runnable()
44         {
45             public void run()
46             {
47                 try
48                 {
49                     Log.i("MainActivity", "Thread ID:"+Thread.currentThread().getId());
50                     final Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");
51                     // 模拟网络延时
52                     SystemClock.sleep(2000);
53                     handler.post(new Runnable()
54                     {
55                         public void run()
56                         {
57                             Log.i("MainActivity", "UpdateUIThread ID:"+Thread.currentThread().getId());
58                             ((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);
59                         }
60                     });
61                 } catch (Exception e)
62                 {
63                     throw new RuntimeException(e);
64                 }
65             }
66         });
67     }
68 }

这里我们象第一步一样使用了
handler.post(new Runnable() { }) 更新前段显示当然是在UI主线程,我们还有 executorService.submit(new Runnable() { }) 来确保下载是在线程池的线程中。
4)Handler+ExecutorService(线程池)+MessageQueue+缓存模式
下面比起前一个做了几个改造:
把整个代码封装在一个类中,同时为了避免出现同时多次下载同一幅图的问题,使用了本地缓存封装的类:

 1 package com.szy.textviewimagedemo;
 2 
 3 import java.lang.ref.SoftReference;
 4 import java.net.URL;
 5 import java.util.HashMap;
 6 import java.util.Map;
 7 import java.util.concurrent.ExecutorService;
 8 import java.util.concurrent.Executors;
 9 
10 import android.graphics.drawable.Drawable;
11 import android.os.Handler;
12 import android.os.SystemClock;
13 import android.util.Log;
14 
15 /**
16  *@author coolszy
17  *@date 2012-2-13
18  *@blog http://blog.92coding.com
19  */
20 
21 public class AsyncImageLoader
22 {
23     // 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
24     public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
25 
26     private ExecutorService executorService = Executors.newFixedThreadPool(5); // 固定五个线程来执行任务
27     private final Handler handler = new Handler();
28 
29     /**
30      *
31      * @param imageUrl
32      *            图像url地址
33      * @param callback
34      *            回调接口
35      * @return 返回内存中缓存的图像,第一次加载返回null
36      */
37     public Drawable loadDrawable(final String imageUrl, final ImageCallback callback)
38     {
39         // 如果缓存过就从缓存中取出数据
40         if (imageCache.containsKey(imageUrl))
41         {
42             SoftReference<Drawable> softReference = imageCache.get(imageUrl);
43             if (softReference.get() != null)
44             {
45                 Log.i("MainActivity", "图片存在缓存中.");
46                 return softReference.get();
47             }
48         }
49         // 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
50         executorService.submit(new Runnable()
51         {
52             public void run()
53             {
54                 try
55                 {
56                     Log.i("MainActivity", "下载图片...");
57                     final Drawable drawable = loadImageFromUrl(imageUrl);
58                     imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
59                     handler.post(new Runnable()
60                     {
61                         public void run()
62                         {
63                             callback.imageLoaded(drawable);
64                         }
65                     });
66                 } catch (Exception e)
67                 {
68                     throw new RuntimeException(e);
69                 }
70             }
71         });
72         return null;
73     }
74 
75     // 从网络上取数据方法
76     protected Drawable loadImageFromUrl(String imageUrl)
77     {
78         try
79         {
80             // 测试时,模拟网络延时,实际时这行代码不能有
81             SystemClock.sleep(2000);
82             return Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png");
83 
84         } catch (Exception e)
85         {
86             throw new RuntimeException(e);
87         }
88     }
89 
90     // 对外界开放的回调接口
91     public interface ImageCallback
92     {
93         // 注意 此方法是用来设置目标对象的图像资源
94         public void imageLoaded(Drawable imageDrawable);
95     }
96 }

说明:
final参数是指当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
这里使用SoftReference 是为了解决内存不足的错误(OutOfMemoryError)的。
前端调用:

 1 package com.szy.textviewimagedemo;
 2 
 3 import android.app.Activity;
 4 import android.graphics.drawable.Drawable;
 5 import android.os.Bundle;
 6 import android.widget.ImageView;
 7 
 8 /**
 9  *@author coolszy
10  *@date 2012-2-13
11  *@blog http://blog.92coding.com
12  *
13  */
14 public class MainActivity extends Activity
15 {
16     @Override
17     public void onCreate(Bundle savedInstanceState)
18     {
19         super.onCreate(savedInstanceState);
20         setContentView(R.layout.main);
21         loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
22         loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2);
23         loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
24         loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4);
25         loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5);
26     }
27 
28     private AsyncImageLoader asyncImageLoader = new AsyncImageLoader();
29 
30     // 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
31     private void loadImage(final String url, final int id)
32     {
33         // 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
34         Drawable cacheImage = asyncImageLoader.loadDrawable(url, new AsyncImageLoader.ImageCallback()
35         {
36             // 请参见实现:如果第一次加载url时下面方法会执行
37             public void imageLoaded(Drawable imageDrawable)
38             {
39                 ((ImageView) findViewById(id)).setImageDrawable(imageDrawable);
40             }
41         });
42         if (cacheImage != null)
43         {
44             ((ImageView) findViewById(id)).setImageDrawable(cacheImage);
45         }
46     }
47 }

若水工作室

目录
相关文章
|
7月前
|
XML Java Android开发
Android Studio App开发之对图片进行简单加工(包括放缩,旋转等等 附源码)
Android Studio App开发之对图片进行简单加工(包括放缩,旋转等等 附源码)
156 0
|
7月前
|
XML Java Android开发
Android Studio App开发之使用相机拍摄照片和从相册中选取图片(附源码 超详细必看)
Android Studio App开发之使用相机拍摄照片和从相册中选取图片(附源码 超详细必看)
978 0
|
3月前
|
存储 缓存 编解码
Android经典面试题之图片Bitmap怎么做优化
本文介绍了图片相关的内存优化方法,包括分辨率适配、图片压缩与缓存。文中详细讲解了如何根据不同分辨率放置图片资源,避免图片拉伸变形;并通过示例代码展示了使用`BitmapFactory.Options`进行图片压缩的具体步骤。此外,还介绍了Glide等第三方库如何利用LRU算法实现高效图片缓存。
75 20
Android经典面试题之图片Bitmap怎么做优化
|
7月前
|
Android开发
Android通过手势(多点)缩放和拖拽图片
Android通过手势(多点)缩放和拖拽图片
61 4
|
7月前
|
Java Android开发
android 下载图片的问题
android 下载图片的问题
51 3
|
4月前
|
数据处理 开发工具 数据安全/隐私保护
Android平台RTMP推送|轻量级RTSP服务|GB28181接入之文字、png图片水印的精进之路
本文探讨了Android平台上推流模块中添加文字与PNG水印的技术演进。自2015年起,为了满足应急指挥及安防领域的需求,逐步发展出三代水印技术:第一代为静态文字与图像水印;第二代实现了动态更新水印内容的能力,例如实时位置与时间信息;至第三代,则优化了数据传输效率,直接使用Bitmap对象传递水印数据至JNI层,减少了内存拷贝次数。这些迭代不仅提升了用户体验和技术效率,也体现了开发者追求极致与不断创新的精神。
|
4月前
|
自然语言处理 定位技术 API
Android经典实战之如何获取图片的经纬度以及如何根据经纬度获取对应的地点名称
本文介绍如何在Android中从图片提取地理位置信息并转换为地址。首先利用`ExifInterface`获取图片内的经纬度,然后通过`Geocoder`将经纬度转为地址。注意操作需在子线程进行且考虑多语言支持。
276 4
|
4月前
|
XML 前端开发 Android开发
Android经典实战之Kotlin中实现圆角图片和圆形图片
本文介绍两种实现圆角图像视图的方法。第一种是通过自定义Kotlin `AppCompatImageView`,重写`onDraw`方法使用`Canvas`和`Path`进行圆角剪裁。第二种利用Android Material库中的`ShapeableImageView`,简单配置即可实现圆角效果。两种方法均易于实现且提供动态调整圆角半径的功能。
95 0
|
6月前
|
JSON 编解码 Apache
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
Android中使用HttpURLConnection实现GET POST JSON数据与下载图片
70 1
|
6月前
|
Java Android开发
18. 【Android教程】图片控件 ImageView
18. 【Android教程】图片控件 ImageView
103 4