原创整理不易,转载请注明出处:Android异步加载图片中UI是否被阻塞的测试

代码下载地址:http://www.zuidaima.com/share/1724477350611968.htm

在学习"Android异步加载图像小结"这篇文章时, 发现有些地方没写清楚,我就根据我的理解,把这篇文章的代码重写整理了一遍,下面就是我的整理。


下面测试使用的layout文件:
简单来说就是 LinearLayout 布局,其下放了5个ImageView。

01 <?xml version="1.0"encoding="utf-8"?>
02 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
03 android:orientation="vertical"android:layout_width="fill_parent"
04 android:layout_height="fill_parent">
05 <TextView android:text="图片区域开始"android:id="@+id/textView2"
06 android:layout_width="wrap_content"android:layout_height="wrap_content"></TextView>
07 <ImageView android:id="@+id/imageView1"
08 android:layout_height="wrap_content"android:src="@drawable/icon"
09 android:layout_width="wrap_content"></ImageView>
10 <ImageView android:id="@+id/imageView2"
11 android:layout_height="wrap_content"android:src="@drawable/icon"
12 android:layout_width="wrap_content"></ImageView>
13 <ImageView android:id="@+id/imageView3"
14 android:layout_height="wrap_content"android:src="@drawable/icon"
15 android:layout_width="wrap_content"></ImageView>
16 <ImageView android:id="@+id/imageView4"
17 android:layout_height="wrap_content"android:src="@drawable/icon"
18 android:layout_width="wrap_content"></ImageView>
19 <ImageView android:id="@+id/imageView5"
20 android:layout_height="wrap_content"android:src="@drawable/icon"
21 android:layout_width="wrap_content"></ImageView>
22 <TextView android:text="图片区域结束"android:id="@+id/textView1"
23 android:layout_width="wrap_content"android:layout_height="wrap_content"></TextView>
24 </LinearLayout>


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

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

Handler+Runnable模式

我们先看一个并不是异步线程加载的例子,使用 Handler+Runnable模式。
这里为何不是新开线程的原因请参看这篇文章:Android Runnable 运行在那个线程 这里的代码其实是在UI 主线程中下载图片的,而不是新开线程。

我们运行下面代码时,会发现他其实是阻塞了整个界面的显示,需要所有图片都加载完成后,才能显示界面。

01 packagecom.zuidaima.AndroidTest;
02
03 importjava.io.IOException;
04 importjava.net.URL;importandroid.app.Activity;
05 importandroid.graphics.drawable.Drawable;
06 importandroid.os.Bundle;
07 importandroid.os.Handler;importandroid.os.SystemClock;
08 importandroid.util.Log;
09 importandroid.widget.ImageView;
10
11 publicclassMainActivityextendsActivity {
12 @Override
13 publicvoidonCreate(Bundle savedInstanceState) {
14 super.onCreate(savedInstanceState);
15 setContentView(R.layout.main);
16 loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
17 loadImage(http://www.chinatelecom.com.cn/images/logo_new.gif",
18 R.id.imageView2);
19 loadImage("http://cache.soso.com/30d/img/web/logo.gif, R.id.imageView3);
20 loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif",
21 R.id.imageView4);
22 loadImage("http://images.cnblogs.com/logo_small.gif",
23 R.id.imageView5);
24 }
25
26 privateHandler handler=newHandler();
27
28 privatevoidloadImage(finalString url,finalintid) {
29 handler.post(newRunnable() {
30 publicvoidrun() {
31 Drawable drawable=null;
32 try{
33 drawable=Drawable.createFromStream(
34 newURL(url).openStream(),"image.gif");
35 }catch(IOException e) {
36 Log.d("test", e.getMessage());
37 }
38 if(drawable==null) {
39 Log.d("test","null drawable");
40 }else{
41 Log.d("test","not null drawable");
42 }
43 // 为了测试缓存而模拟的网络延时
44 SystemClock.sleep(2000);
45
46 (ImageView) MainActivity.this.findViewById(id))
47 .setImageDrawable(drawable);
48 }
49 });
50 }
51 }

Handler+Thread+Message模式

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

01 packagecom.zuidaima.AndroidTest;
02
03 importjava.io.IOException;
04 importjava.net.URL;
05 importandroid.app.Activity;
06 importandroid.graphics.drawable.Drawable;
07 importandroid.os.Bundle;
08 importandroid.os.Handler;
09 importandroid.os.Message;
10 importandroid.os.SystemClock;
11 importandroid.util.Log;
12 importandroid.widget.ImageView;
13
14 publicclassMainActivityextendsActivity {
15 @Override
16 publicvoidonCreate(Bundle savedInstanceState) {
17 super.onCreate(savedInstanceState);
18 setContentView(R.layout.main);
19 loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
20 loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif",
21 R.id.imageView2);
22 loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
23 loadImage2("http://csdnimg.cn/www/images/csdnindex_logo.gif",
24 R.id.imageView4);
25 loadImage2("http://images.cnblogs.com/logo_small.gif",
26 R.id.imageView5);
27 }
28
29 finalHandler handler2=newHandler() {
30 @Override
31 publicvoidhandleMessage(Message msg) {
32 ((ImageView) MainActivity.this.findViewById(msg.arg1))
33 .setImageDrawable((Drawable) msg.obj);
34 }
35 };
36
37 // 采用handler+Thread模式实现多线程异步加载
38 privatevoidloadImage2(finalString url,finalintid) {
39 Thread thread=newThread() {
40 @Override
41 publicvoidrun() {
42 Drawable drawable=null;
43 try{
44 drawable=Drawable.createFromStream(
45 newURL(url).openStream(),"image.png");
46 }catch(IOException e) {
47 Log.d("test", e.getMessage());
48 }
49
50 // 模拟网络延时
51 SystemClock.sleep(2000);
52
53 Message message=handler2.obtainMessage();
54 message.arg1=id;
55 message.obj=drawable;
56 handler2.sendMessage(message);
57 }
58 };
59 thread.start();
60 thread=null;
61 }
62
63 }


这时候我们可以看到实现了异步加载, 界面打开时,五个ImageView都是没有图的,然后在各自线程下载完后才把图自动更新上去。

Handler+ExecutorService(线程池)+MessageQueue模式

能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。

这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就来用它。

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

线程池的信息可以参看这篇文章:Java&Android的线程池-ExecutorService

下面的演示例子是创建一个可重用固定线程数的线程池。
核心代码

01 packagecom.zuidaima.AndroidTest;
02
03 importjava.io.IOException;
04 importjava.net.URL;
05 importjava.util.concurrent.ExecutorService;
06 importjava.util.concurrent.Executors;
07
08 importandroid.app.Activity;
09 importandroid.graphics.drawable.Drawable;
10 importandroid.os.Bundle;
11 importandroid.os.Handler;
12 importandroid.os.Message;
13 importandroid.os.SystemClock;
14 importandroid.util.Log;
15 importandroid.widget.ImageView;
16
17 publicclassMainActivityextendsActivity );
18
19 // 引入线程池来管理多线程
20 privatevoidloadImage3(finalString url,finalintid) {
21 executorService.submit(newRunnable() {
22 publicvoidrun() {
23 try{
24 finalDrawable drawable=Drawable.createFromStream(
25 newURL(url).openStream(),"image.png");
26
27 // 模拟网络延时
28 SystemClock.sleep(2000);
29
30 handler.post(newRunnable() {
31 publicvoidrun() {
32 ((ImageView) MainActivity.this.findViewById(id))
33 .setImageDrawable(drawable);
34 }
35 });
36 }catch(Exception e) {
37 thrownewRuntimeException(e);
38 }
39 }
40 });
41 }
42 }


这里我们象第一步一样使用了 handler.post(new Runnable() { 更新前段显示当然是在UI主线程,我们还有 executorService.submit(new Runnable() { 来确保下载是在线程池的线程中。

Handler+ExecutorService(线程池)+MessageQueue+缓存模式

下面比起前一个做了几个改造:

把整个代码封装在一个类中
为了避免出现同时多次下载同一幅图的问题,使用了本地缓存 封装的类:

01 packagecom.zuidaima.AndroidTest;
02
03 importjava.lang.ref.SoftReference;
04 importjava.net.URL;importjava.util.HashMap;
05 importjava.util.Map;
06 importjava.util.concurrent.ExecutorService;
07 importjava.util.concurrent.Executors;
08 importandroid.graphics.drawable.Drawable;
09 importandroid.os.Handler;
10 importandroid.os.SystemClock;
11
12 publicclassAsyncImageLoader3 );
13
14 // 固定五个线程来执行任务
15 privatefinalHandler handler=newHandler();
16
17 /** *//** *//***//**
18 *
19 * @param imageUrl
20 * 图像url地址
21 * @param callback
22 * 回调接口
23 * @return 返回内存中缓存的图像,第一次加载返回null
24 */
25 publicDrawable loadDrawable(finalString imageUrl,
26 finalImageCallback callback) {
27
28 // 如果缓存过就从缓存中取出数据
29 if(imageCache.containsKey(imageUrl)) {
30 SoftReference<Drawable> softReference=imageCache.get(imageUrl);
31 if(softReference.get() !=null) {
32 returnsoftReference.get();
33 }
34 }
35 // 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
36 executorService.submit(newRunnable() {
37 publicvoidrun() {
38 try{
39 finalDrawable drawable=loadImageFromUrl(imageUrl);
40
41 imageCache.put(imageUrl,newSoftReference<Drawable>(
42 drawable));
43
44 handler.post(newRunnable() {
45 publicvoidrun() {
46 callback.imageLoaded(drawable);
47 }
48 });
49 }catch(Exception e) {
50 thrownewRuntimeException(e);
51 }
52 }
53 });
54 returnnull;
55 }
56
57 // 从网络上取数据方法
58 protectedDrawable loadImageFromUrl(String imageUrl) {
59 try{
60
61 // 测试时,模拟网络延时,实际时这行代码不能有
62 SystemClock.sleep(2000);
63
64 returnDrawable.createFromStream(newURL(imageUrl).openStream(),
65 "image.png");
66
67 }catch(Exception e) {
68 thrownewRuntimeException(e);
69 }
70 }
71
72 // 对外界开放的回调接口
73 publicinterfaceImageCallback {
74
75 // 注意 此方法是用来设置目标对象的图像资源
76 publicvoidimageLoaded(Drawable imageDrawable);
77 }
78 }


说明:
final参数是指当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
参看:Java关键字final、static使用总结
这里使用SoftReference 是为了解决内存不足的错误(OutOfMemoryError)的,更详细的可以参看:
内存优化的两个类:SoftReference 和 WeakReference

前端调用:

01 packagecom.zuidaima.AndroidTest;
02
03 importandroid.app.Activity;
04

更多相关文章

  1. Android(安卓)四种加载方式详解(standard singleTop singleTask s
  2. 带你走进Android(安卓)Afinal框架的世界
  3. android中的常见类(二)
  4. Android动态加载之ClassLoader —热修复、插件化
  5. Android最大方法数和解决方案
  6. android 多线程同时下载多个较大文件
  7. 关于Android中非UI线程中操作UI线程中的控件的疑问
  8. 开源弹幕引擎·烈焰弹幕使(DanmakuFlameMaster)使用解析
  9. 深入理解android消息处理机制

随机推荐

  1. Android(安卓)Studio系列-Activity单元测
  2. 自学Android(安卓)坑1
  3. Android(安卓)RxJava与Retrofit与Recycle
  4. 解决 Android(安卓)N 7.0 上 报错:android
  5. 【Android】21.3 动画
  6. android 关于listview scrollview 底部
  7. android 组件,xml布局中属性详解
  8. Android(安卓)进程内存、CPU使用查看
  9. Android(安卓)----蓝牙架构
  10. Android单个模块编译