Android异步加载小结

异步加载图片

第一种   Handler+Thread+post,加载图像方法如下所示:

  使用post方法将Runnable对象放到Handler的线程队列中,该Runnable的执行其实并未单独开启线程,而是仍然在当前Activity的UI线程中执行,Handler只是调用了Runnable对象的run方法。

 private void loadImage(final String url, final int id) {

    new Thread(){

     public void run(){
         handler.post(new Runnable() {
                public void run() {
                    Drawable drawable = null;
                    try {
                        drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");
                    } catch (IOException e) {
                    }
                    ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);
                }
            });

    }

}.start();
}

第二种:Handler+Thread+Message:
handler简介 Handler为Android提供了一种异步消息处理机制,它包含两个队列,一个是线程列队,另一个是消息列队。使用post方法将
线 程对象添加到线程队列中,使用sendMessage(Message message)将消息放入消息队列中。当向消息队列中发送消息后就立 即返回,
而从消息队列中读取消息对象时会阻塞,继而回调Handler中public void handleMessage(Message msg)方法。因此 在创建
Handler时应该使用匿名内部类重写该方法。如果想要这个流程一直执行的话,可以再run方法内部执行postDelay或者 post方法,
再将该线程对象添加到消息队列中重复执行。想要停止线程,调用Handler对象的removeCallbacks(Runnable r)从 线程队列中移除线
程对象,使线程停止执行。
final Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { ((ImageView) LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj); } }; 对应加载图像代码如下: //采用handler+Thread模式实现多线程异步加载 private void loadImage2(final String url, final int id) { Thread thread = new Thread(){ @Override public void run() { Drawable drawable = null; try { drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png"); } catch (IOException e) { } Message message= handler.obtainMessage() ; message.arg1 = id; message.obj = drawable; handler.sendMessage(message); } }; thread.start(); thread = null; }

接下来进行优化:

(3)引入ExecutorService接口

       在主线程中加入:private ExecutorService executorService = Executors.newFixedThreadPool(5);  

       对应加载图像方法更改如下:

      // 引入线程池来管理多线程
    private void loadImage3(final String url, final int id) {
        executorService.submit(new Runnable() {
            public void run() {
                try {
                    final Drawable drawable = Drawable.createFromStream(new URL(url).openStream(), "image.png");
                    handler.post(new Runnable() {

                        public void run() {
                            ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable);
                        }
                    });
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

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

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

//public class AsyncImageLoader3 {
   //为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
  //  public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
   // private ExecutorService executorService = Executors.newFixedThreadPool(5);    //固定五个线程来执行任务
   

  1 package com.tarena.bll;
  2 
  3 import java.io.File;
  4 import java.io.IOException;
  5 import java.lang.ref.SoftReference;
  6 import java.util.ArrayList;
  7 import java.util.HashMap;
  8 
  9 import org.apache.http.HttpEntity;
 10 import org.apache.http.util.EntityUtils;
 11 
 12 import com.tarena.utils.BitmapUtils;
 13 import com.tarena.utils.GlobalConsts;
 14 import com.tarena.utils.HttpUtils;
 15 
 16 import android.content.Context;
 17 import android.graphics.Bitmap;
 18 import android.os.Handler;
 19 import android.os.Message;
 20 import android.util.Log;
 21 
 22 /**
 23  * 执行批量的图片加载任务
 24  * 
 25  * @author zsw
 26  * 
 27  */
 28 public class AsyncImageLoader {
 29     private ArrayList<ImageLoadTask> tasks;// 任务集合
 30     private Thread workThread;// 工作线程 用于遍历任务集合
 31     private boolean isLoop;// 线程中循环的 控制变量
 32     private Handler handler;// 线程通信对象
 33     private HashMap<String, SoftReference<Bitmap>> caches;// 图片缓存集合
 34     private Context context;
 35     //使用构造函数进行初始化操作
 36     public AsyncImageLoader(Context context, final Callback callback) {
 37         this.context = context;
 38         this.tasks = new ArrayList<AsyncImageLoader.ImageLoadTask>();
 39         this.caches = new HashMap<String, SoftReference<Bitmap>>();
 40         this.isLoop = true;
 41         this.handler = new Handler() {
 42              //处理消息
 43             public void handleMessage(android.os.Message msg) {
 44                 ImageLoadTask task = (ImageLoadTask) msg.obj;
 45                 //调用回调接口的方法,将图片路径和所对应的图片位图回传
 46                 callback.imageLoaded(task.path, task.bitmap);
 47             };
 48         };
 49         this.workThread = new Thread() {
 50             public void run() {
 51                 Log.i("info", "工作线程开始运行");
 52                 while (isLoop) {
 53                     // 轮询(如果控制循环变量为true而且任务又不为空则继续循环,否则跳出循环)
 54                     while (isLoop == true && !tasks.isEmpty()) {
 55                         //借助集合的remove()方法得到第一个任务
 56                         ImageLoadTask task = tasks.remove(0);
 57 
 58                         try {
 59                             //利用HttpUtils的getEntity方法获得响应实体
 60                             HttpEntity entity = HttpUtils.getEntity(
 61                                     GlobalConsts.BASE_URL + task.path, null,
 62                                     HttpUtils.METHOD_GET);
 63                             //将响应实体类转成字节数组
 64                             byte[] data = EntityUtils.toByteArray(entity);
 65                             //利用BitmapUtil图片处理类的loadBitmap将字节数组压缩成位图
 66                             task.bitmap = BitmapUtils
 67                                     .loadBitmap(data, 100, 100);
 68 
 69                             // 发消息
 70                             Message msg = Message.obtain(handler, 0, task);
 71                             msg.sendToTarget();
 72                             // 保存到内存和文件缓存
 73                             caches.put(task.path, new SoftReference<Bitmap>(
 74                                     task.bitmap));
 75                             BitmapUtils
 76                                     .save(task.bitmap, createFile(task.path));
 77                         } catch (IOException e) {
 78                             // TODO Auto-generated catch block
 79                             e.printStackTrace();
 80                         }
 81 
 82                     }
 83                     //若无任务是设置isLoop为false
 84                     if (isLoop == false)
 85                         break;
 86 
 87                     // 等待
 88                     synchronized (this) {
 89                         try {
 90                             this.wait();
 91                         } catch (InterruptedException e) {
 92                             e.printStackTrace();
 93                         }
 94                     }
 95                 }
 96                 Log.i("info", "工作线程结束");
 97             };
 98         };
 99         this.workThread.start();
100     };
101 
102     public void quit() {
103         isLoop = false;
104         synchronized (workThread) {
105             try {
106                 workThread.notify();
107             } catch (Exception e) {
108                 // TODO Auto-generated catch block
109                 e.printStackTrace();
110             }
111         }
112     }
113 
114     /**
115      * 图片加载方法
116      * 
117      * @param path
118      * @return
119      */
120     public Bitmap loadImage(String path) {
121         Bitmap bm = null;
122         // 如果内存缓存中存在图片 则直接返回图片
123         if (caches.containsKey(path)) {
124             bm = caches.get(path).get();
125             if (bm != null) {
126                 return bm;
127             } else {
128                 caches.remove(path);
129             }
130         }
131         // 如果文件缓存中存在图片 则直接返回图片
132         bm = BitmapUtils.loadBitmap(createFile(path).getAbsolutePath());
133         if (bm != null) {
134             return bm;
135         }
136         // 如果不存在缓存图片,返回null.向图片加载任务集合添加新的加载任务,并通知线程继续进行
137         ImageLoadTask task = new ImageLoadTask(path);
138         if (!tasks.contains(task)) {
139             tasks.add(task);
140             synchronized (workThread) {
141                 try {
142                     workThread.notify();
143                 } catch (Exception e) {
144                     // TODO Auto-generated catch block
145                     e.printStackTrace();
146                 }
147             }
148         }
149         return null;
150     }
151     /**
152      * 创建缓存文件
153      * @param path
154      * @return
155      */
156     private File createFile(String path) {
157         return new File(context.getExternalCacheDir(), path);
158     }
159 //    声明一个回调接口
160     public interface Callback {
161         void imageLoaded(String path, Bitmap bm);
162     }
163 //    图片加载任务类
164     private class ImageLoadTask {
165         private String path;
166         private Bitmap bitmap;
167 
168         public ImageLoadTask() {
169             super();
170         }
171 
172         public ImageLoadTask(String path) {
173             super();
174             this.path = path;
175         }
176 
177         @Override
178         public boolean equals(Object o) {
179             ImageLoadTask task = (ImageLoadTask) o;
180             return path.equals(task.path);
181         }
182     }
183 }

   此处用到两个自定义工具类:

  BitmapUtisl.java

 1 package com.tarena.utils;
 2 
 3 import java.io.File;
 4 import java.io.FileOutputStream;
 5 import java.io.IOException;
 6 
 7 import android.graphics.Bitmap;
 8 import android.graphics.Bitmap.CompressFormat;
 9 import android.graphics.BitmapFactory;
10 import android.graphics.BitmapFactory.Options;
11 
12 public class BitmapUtils {
13     /**
14      * 从指定文件路径 加载位图对象
15      * 
16      * @param path
17      * @return
18      */
19     public static Bitmap loadBitmap(String path) {
20         return BitmapFactory.decodeFile(path);
21     }
22 
23     /**
24      * 从图片的字节数组中 加载位图对象 并对加载图片的宽高进行限制
25      * 
26      * @param data
27      * @param width
28      * @param height
29      * @return
30      */
31     public static Bitmap loadBitmap(byte[] data, int width, int height) {
32         Bitmap bm = null;
33         // 创建加载选项对象
34         Options opts = new Options();
35         // 设置仅加载边界信息
36         opts.inJustDecodeBounds = true;
37         // 加载为位图尺寸信息
38         BitmapFactory.decodeByteArray(data, 0, data.length, opts);
39         // 计算并设置收缩比例
40         int x = opts.outWidth / width;
41         int y = opts.outHeight / height;
42         opts.inSampleSize = x > y ? x : y;
43         // 取消仅加载边界信息的设置
44         opts.inJustDecodeBounds = false;
45         // 加载位图
46         bm = BitmapFactory.decodeByteArray(data, 0, data.length, opts);
47         return bm;
48     }
49 
50     /**
51      * 将位图对象保存到指定文件目录
52      * 
53      * @param bm
54      * @param file
55      * @throws IOException
56      */
57     public static void save(Bitmap bm, File file) throws IOException {
58         if (bm != null && file != null) {
59             // 如果父目录不存在 则创建目录
60             if (!file.getParentFile().exists()) {
61                 file.getParentFile().mkdirs();
62             }
63             // 如果文件不存在则创建文件
64             if (!file.exists()) {
65                 file.createNewFile();
66             }
67             // 保存
68             bm.compress(CompressFormat.JPEG, 100, new FileOutputStream(file));
69         }
70     }
71 }

HttpUtils.java

  1 package com.tarena.utils;
  2 
  3 import java.io.IOException;
  4 import java.io.InputStream;
  5 import java.util.List;
  6 
  7 import org.apache.http.HttpEntity;
  8 import org.apache.http.HttpResponse;
  9 import org.apache.http.HttpStatus;
 10 import org.apache.http.NameValuePair;
 11 import org.apache.http.client.ClientProtocolException;
 12 import org.apache.http.client.HttpClient;
 13 import org.apache.http.client.entity.UrlEncodedFormEntity;
 14 import org.apache.http.client.methods.HttpGet;
 15 import org.apache.http.client.methods.HttpPost;
 16 import org.apache.http.client.methods.HttpUriRequest;
 17 import org.apache.http.impl.client.DefaultHttpClient;
 18 import org.apache.http.params.CoreConnectionPNames;
 19 
 20 public class HttpUtils {
 21     public static final int METHOD_GET = 1;
 22     public static final int METHOD_POST = 2;
 23 
 24     /**
 25      * 连接服务端指定的资源路径获取响应实体对象
 26      * 
 27      * @param uri
 28      * @param params
 29      * @param method
 30      * @return
 31      * @throws IOException
 32      */
 33     public static HttpEntity getEntity(String uri, List<NameValuePair> params,
 34             int method) throws IOException {
 35         HttpEntity entity = null;
 36         // 创建客户端对象
 37         HttpClient client = new DefaultHttpClient();
 38         client.getParams().setParameter(
 39                 CoreConnectionPNames.CONNECTION_TIMEOUT, 3000);
 40         // 创建请求对象
 41         HttpUriRequest request = null;
 42         switch (method) {
 43         case METHOD_GET:// get请求
 44             StringBuilder sb = new StringBuilder(uri);
 45             if (params != null && !params.isEmpty()) {
 46                 sb.append('?');
 47                 for (NameValuePair pair : params) {
 48                     sb.append(pair.getName()).append('=')
 49                             .append(pair.getValue()).append('&');
 50                 }
 51                 sb.deleteCharAt(sb.length() - 1);
 52             }
 53             request = new HttpGet(sb.toString());
 54             break;
 55         case METHOD_POST:// post请求
 56             request = new HttpPost(uri);
 57             if (params != null && !params.isEmpty()) {
 58                 UrlEncodedFormEntity reqEntity = new UrlEncodedFormEntity(
 59                         params);
 60                 ((HttpPost) request).setEntity(reqEntity);
 61             }
 62             break;
 63         }
 64         // 执行请求获得实体对象
 65         HttpResponse response = client.execute(request);
 66         if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
 67             entity = response.getEntity();
 68         }
 69         // 返回响应实体
 70         return entity;
 71     }
 72 
 73     /**
 74      * 获取指定实体对象的 长度信息
 75      * 
 76      * @param entity
 77      * @return
 78      */
 79     public static long getLength(HttpEntity entity) {
 80         if (entity != null)
 81             return entity.getContentLength();
 82         return 0;
 83     }
 84 
 85     /**
 86      * 获取实体输入流
 87      * 
 88      * @param entity
 89      * @return
 90      * @throws IOException
 91      * @throws IllegalStateException
 92      */
 93     public static InputStream getStream(HttpEntity entity)
 94             throws IllegalStateException, IOException {
 95         if (entity != null) {
 96             return entity.getContent();
 97         }
 98         return null;
 99     }
100 }

 

 第三种:AsyncTask

AsyncTask简介 AsyncTask的特点是任务在主线程之外运行,而回调方法是在主线程中执行,这就有效地避免了使用Handler带来的麻烦。阅读 AsyncTask的源码可知,AsyncTask是使用java.util.concurrent 框架来管理线程以及任务的执行的,concurrent框架是一个非常 成熟,高效的框架,经过了严格的测试。这说明AsyncTask的设计很好的解决了匿名线程存在的问题。 AsyncTask是抽象类,其结构图如下图所示: AsyncTask定义了三种泛型类型 Params,Progress和Result。 Params 启动任务执行的输入参数,比如HTTP请求的URL。 Progress 后台任务执行的百分比。 Result 后台执行任务最终返回的结果,比如String。 子类必须实现抽象方法doInBackground(Params… p) ,在此方法中实现任务的执行工作,比如连接网络获取数据等。通常还应 该实现onPostExecute(Result r)方法,因为应用程序关心的结果在此方法中返回。需要注意的是AsyncTask一定要在主线程中创 建实例。 AsyncTask的执行分为四个步骤,每一步都对应一个回调方法,需要注意的是这些方法不应该由应用程序调用,开发者需要做的 就是实现这些方法。在任务的执行过程中,这些方法被自动调用,运行过程,如下图所示: onPreExecute() 当任务执行之前开始调用此方法,可以在这里显示进度对话框。 doInBackground(Params…) 此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用 publicProgress(Progress…)来更新任务的进度。 onProgressUpdate(Progress…) 此方法在主线程执行,用于显示任务执行的进度。 onPostExecute(Result) 此方法在主线程执行,任务执行的结果作为此方法的参数返回

 1 public class ThreadHandlerActivity extends Activity {
 2 
 3     private List<String> urlList;
 4     private ImageAdapter listItemAdapter;
 5     private LinkedList<HashMap<String, Object>> listItem;
 6     private Handler handler;
 7     private ExecutorService executorService = Executors.newFixedThreadPool(10);
 8 
 9     @Override
10     public void onCreate(Bundle savedInstanceState) {
11         super.onCreate(savedInstanceState);
12         setContentView(R.layout.activity_main);
13         urlList = new ArrayList<String>();
14         urlList.add("http://www.baidu.com/img/baidu_sylogo1.gif");
15         urlList.add("http://y2.ifengimg.com/2012/06/24/23063562.gif");
16         urlList.add("http://himg2.huanqiu.com/statics/images/index/logo.png");
17 
18         listItem = new LinkedList<HashMap<String, Object>>();
19 
20         listItemAdapter = new ImageAdapter(this, listItem);
21         ListView listView = (ListView) this.findViewById(R.id.listView1);
22         listView.setAdapter(listItemAdapter);
23         
24         handler = new Handler(){
25             @Override
26             public void handleMessage(Message msg) {
27                 HashMap<String, Object> map = (HashMap<String, Object>) msg.obj;
28                 listItem.add(map);
29                 listItemAdapter.notifyDataSetChanged();
30             }
31         };
32         for (final String urlStr : urlList) {
33             executorService.submit(new Runnable() {
34                 @Override
35                 public void run() {
36                     try {
37                         URL url = new URL(urlStr);
38                         Drawable drawable = Drawable.createFromStream(
39                                 url.openStream(), "src");
40                         HashMap<String, Object> table = new HashMap<String, Object>();
41                         table.put("ItemImage", drawable);
42                         Message msg = new Message();
43                         msg.obj = table;
44                         msg.setTarget(handler);
45                         handler.sendMessage(msg);
46                     } catch (Exception e) {
47                         e.printStackTrace();
48                     }
49                 }
50             });
51         }
52     }
53 
54     @Override
55     public boolean onCreateOptionsMenu(Menu menu) {
56         getMenuInflater().inflate(R.menu.activity_main, menu);
57         return true;
58     }
59 }

但是AsyncTask对于异步处理不是万能的,对于需要循环、多次的任务处理,我们任然需要采用传统的Thread线程机制。

注:本文除了自己写的还有来自于http://www.open-open.com/lib/view/open1345017746897.html 内容

posted @ 2014-04-12 19:11  不忘初心,方得始终  阅读(1055)  评论(0)    收藏  举报