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 内容

浙公网安备 33010602011771号