handler添线程模式实现android应用的异步加载
handler加线程模式实现android应用的异步加载
源码下载
android开发经常需要访问加载网络图片,今天我做了一个笔记,一步一步由浅入深,来用handler+线程的模式来加载网络的图片,下面看看第一种情况:"每次创建一个线程来的模式"
首先创建一个handler用来接收消息,为指定的imageView设置图片:
Handler handler = new Handler(){ public void handleMessage(Message msg) { ImageView iv = (ImageView) findViewById(msg.arg1); iv.setImageDrawable((Drawable) msg.obj); }; };下面这个方法是加载指定url的图片,并且发送消息给handler
private void loadImage(final String string, final int id) { // TODO Auto-generated method stub new Thread(){ public void run() { try { Drawable drawable = Drawable.createFromStream(new URL(string).openStream(),"image.png"); SystemClock.sleep(2000); //模拟访问网络 Message msg = handler.obtainMessage(); msg.arg1 = id; //将imageView的id传送 msg.obj = drawable; //加载出的drawable实体对象 handler.sendMessage(msg); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }; }.start(); }在onCreate方法中,这样调用,为imageView来加载图片:
loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/one.png",R.id.imageOne); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/two.jpg",R.id.imageTwo); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/three.jpg",R.id.imageThree); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/four.png",R.id.imageFour);
可以看到,我们每次调用一次loadImage方法都会新开启一个线程,这样做有一点坏处,就是当我们有很多图片需要加载的时候,是需要开启大量的线程来访问网络的,这样性能开销很大,所以我们接下来看看,利用线程池+handler的模式来访问网络图片,这样可以改善不断的开线程造成的性能开销很大的问题。
线程池+handler
这次,我们的loadImage方法会创建一个拥有四个线程的线程池,来加载网络图片:
//新建一个拥有4个线程的线程池 ExecutorService service = Executors.newFixedThreadPool(4); private void loadImage(final String string,final int id) { // TODO Auto-generated method stub // serivce.submit(new Runnable() { }) 来确保下载是在线程池的线程中。 service.submit(new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { final Drawable drawable = Drawable.createFromStream(new URL(string).openStream(),"haha.png"); //SystemClock.sleep(3000);//模拟网络访问网络的时间 handler.post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub ImageView iv = (ImageView) findViewById(id); iv.setImageDrawable(drawable); } }); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } });onCreate中的代码是不变的
//加载四张网络图片,将图片的url和索要显示图片的iamgeviewd的的id当做参数传入方法 loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/one.png",R.id.imageOne); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/two.jpg",R.id.imageTwo); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/three.jpg",R.id.imageThree); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/four.png",R.id.imageFour);可能,目前看不出和第一种的区别,这是因为我们现在加载的图片只有四张,如果我们加载的图片很多的话,区别还是比较明显的,但是仔细想想,这种方式还是有一点不足之处,那就是我们每次都需要访问网络来加载图片,如果我们能够将已经加载的图片来缓存起来,那是多么的美好啊,那么我们的第三种模式就应运而生了。
线程池和内存缓存的模式:
首先新建一个AsyncImageLoader类,专门用来加载网络的图片的:
AsyncImageLoader.java
package com.example.sysnthreadpoolcache; import java.io.IOException; import java.lang.ref.SoftReference; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import android.graphics.drawable.Drawable; import android.os.Handler; import android.widget.ImageView; public class AsyncImageLoader { // 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动) private Map<String,SoftReference<Drawable>>imageCache = new HashMap<String, SoftReference<Drawable>>();//定义缓存图片的map对象 private ExecutorService service = Executors.newFixedThreadPool(4);//新建一个线程池,包含四个线程 private Handler handler = new Handler(); //对外部公开的接口 public interface ImageCallback{ // 注意 此方法是用来设置目标对象的图像资源 public void imageLoaded(Drawable imageDrawable); } public Drawable loadDrawable(final String url,final ImageCallback callback){ if (imageCache.containsKey(url)) {//如果缓存里边存在 SoftReference<Drawable> drawableReference = imageCache.get(url); Drawable drawable = drawableReference.get(); if (null != drawable) { return drawable; } } service.submit(new Runnable() {//// 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中 @Override public void run() { // TODO Auto-generated method stub final Drawable drawable = loadImage(url); imageCache.put(url, new SoftReference<Drawable>(drawable)); handler.post(new Runnable() { @Override public void run() { // TODO Auto-generated method stub callback.imageLoaded(drawable); } }); } }); return null; } private Drawable loadImage(final String url) { // TODO Auto-generated method stub Drawable drawable = null; try { drawable = Drawable.createFromStream(new URL(url).openStream(),"haha.png"); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return drawable; } }我们的MainActivity代码也很简单,如下:
public class MainActivity extends Activity { AsyncImageLoader loader = new AsyncImageLoader(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //加载四张网络图片,将图片的url和索要显示图片的iamgeviewd的的id当做参数传入方法 loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/one.png",R.id.imageOne); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/two.jpg",R.id.imageTwo); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/three.jpg",R.id.imageThree); loadImage("http://192.168.0.8:8080/Struts2JQueryJson/test/four.png",R.id.imageFour); } private void loadImage(final String string, final int id) { // TODO Auto-generated method stub Drawable cacheImage = loader.loadDrawable(string, new ImageCallback() { @Override public void imageLoaded(Drawable imageDrawable) { // TODO Auto-generated method stub ((ImageView) findViewById(id)).setImageDrawable(imageDrawable); } }); if (cacheImage != null) { ((ImageView) findViewById(id)).setImageDrawable(cacheImage); } } }注意这里我搭建了一个本地服务器来存放网络上的图片,需要将其中的连接改为实际网络上的图片连接,通过Map<String,SoftReference<Drawable>>imageCache = new HashMap<String, SoftReference<Drawable>>();这种方式就实现了简单的内存缓存图片,首先访问并加载所需图片,断开网络,发现图片仍然可以显示,这就是因为我们已经将其缓存到内存中了,注意这种方式同样存在一个很大的缺陷,就是当内存吃紧的时候,系统会自动释放其认为没有用的内存,这样,我们的缓存效果就不是那么明显了,实际上在实际开发中,我们更多用的是lrucache和disklrucache来缓存图片的,这来那个种方法,以后会有,敬请期待。
源码下载