`

演化理解 Android 异步加载图片

 
阅读更多
LinearLayout 布局,其下放了5个ImageView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="fill_parent"
    android:layout_height="fill_parent">
 
    <TextView android:text="图片区域开始" android:id="@+id/textView2"
        android:layout_width="wrap_content" android:layout_height="wrap_content">
        </TextView>
 
    <ImageView android:id="@+id/imageView1"
        android:layout_height="wrap_content" android:src="@drawable/icon"
        android:layout_width="wrap_content">
        </ImageView>
 
    <ImageView android:id="@+id/imageView2"
        android:layout_height="wrap_content" android:src="@drawable/icon"
        android:layout_width="wrap_content">
        </ImageView>
 
    <ImageView android:id="@+id/imageView3"
        android:layout_height="wrap_content" android:src="@drawable/icon"
        android:layout_width="wrap_content">
        </ImageView>
 
    <ImageView android:id="@+id/imageView4"
        android:layout_height="wrap_content" android:src="@drawable/icon"
        android:layout_width="wrap_content">
        </ImageView>
 
    <ImageView android:id="@+id/imageView5"
        android:layout_height="wrap_content" android:src="@drawable/icon"
        android:layout_width="wrap_content">
        </ImageView>
 
    <TextView android:text="图片区域结束" android:id="@+id/textView1"
        android:layout_width="wrap_content" android:layout_height="wrap_content">
        </TextView>
</LinearLayout>


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


Handler+Runnable模式
我们先看一个并不是异步线程加载的例子,使用 Handler+Runnable模式。
这里为何不是新开线程的原因请参看这篇文章:Android Runnable 运行在那个线程 这里的代码其实是在UI 主线程中下载图片的,而不是新开线程。
我们运行下面代码时,会发现他其实是阻塞了整个界面的显示,需要所有图片都加载完成后,才能显示界面

import java.io.IOException;
import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;
 
public class MainActivity extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState){ 
 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
        loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1); 
        loadImage(http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2); 
        loadImage("http://cache.soso.com/30d/img/web/logo.gif, R.id.imageView3); 
        loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4); 
        loadImage("http://images.cnblogs.com/logo_small.gif", R.id.imageView5); 
    }
 
    private Handler handler = new Handler();
 
    private void loadImage(final String url, final int id) {
 
         handler.post(new Runnable() {
 
             public void run() {
                 Drawable drawable = null;
 
                 try{ 
                   drawable = Drawable.createFromStream( new URL(url).openStream(), "image.gif"); 
                 }catch (IOException e){ 
                  Log.d("test", e.getMessage()); 
                 }
 
                 if (drawable == null){ 
                    Log.d("test", "null drawable"); 
                 }else{ 
                    Log.d("test", "not null drawable"); 
                 }
                 // 为了测试缓存而模拟的网络延时
                 SystemClock.sleep(2000);
                ((ImageView) MainActivity.this.findViewById(id)).setImageDrawable(drawable);
             }
      });
   }
}


Handler+Thread+Message模式
这种模式使用了线程,所以可以看到异步加载的效果。
import java.io.IOException;
import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;
 
public class MainActivity extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState){ 
 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
        loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1); 
        loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2); 
        loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3); 
        loadImage2("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4); 
        loadImage2("http://images.cnblogs.com/logo_small.gif", R.id.imageView5); 
    }
 
    final Handler handler2 = new Handler() {
 
       @Override
       public void handleMessage(Message msg){ 
          ((ImageView) MainActivity.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){  
                   Log.d("test", e.getMessage()); 
                }
 
                // 模拟网络延时
                SystemClock.sleep(2000);
 
                Message message = handler2.obtainMessage();
                message.arg1 = id;
                message.obj = drawable;
                handler2.sendMessage(message);
            }
    };
    thread.start();
    thread = null;
  }
 
}

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


Handler+ExecutorService(线程池)+MessageQueue模式
能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。
这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就来用它。
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
线程池的信息可以参看这篇文章:Java&Android的线程池-ExecutorService 下面的演示例子是创建一个可重用固定线程数的线程池。
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;
 
public class MainActivity extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState){ 
 
          super.onCreate(savedInstanceState); 
          setContentView(R.layout.main); 
          loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1); 
          loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2); 
          loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3); 
          loadImage3("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4); 
          loadImage3("http://images.cnblogs.com/logo_small.gif", R.id.imageView5); 
    }
 
          private Handler handler = new Handler();
 
          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");
                              // 模拟网络延时
                              SystemClock.sleep(2000);
                              handler.post(new Runnable() {
                                   public void run(){ 
                                        ((ImageView) MainActivity.this.findViewById(id)) .setImageDrawable(drawable); 
                                   }
                              });
                           } catch (Exception e){ 
                              throw new RuntimeException(e); 
                           }
                      }
               });
      }
}

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

Handler+ExecutorService(线程池)+MessageQueue+缓存模式
下面比起前一个做了几个改造:
  • 把整个代码封装在一个类中
  • 为了避免出现同时多次下载同一幅图的问题,使用了本地缓存

import java.lang.ref.SoftReference;
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.os.SystemClock;
 
public class AsyncImageLoader3 {
 
    // 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
    public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
 
    private ExecutorService executorService = Executors.newFixedThreadPool(5); // 固定五个线程来执行任务
    private final Handler handler = new Handler();
 
    /**
    *
    @param imageUrl  图像url地址
    @param callback  回调接口
    @return 返回内存中缓存的图像,第一次加载返回null
    */
 
    public Drawable loadDrawable(final String imageUrl,
                      final ImageCallback callback) {
 
         // 如果缓存过就从缓存中取出数据
         if (imageCache.containsKey(imageUrl)){
             SoftReference<Drawable> softReference = imageCache.get(imageUrl);
 
             if (softReference.get() != null){                 
                  return softReference.get();              
             }
         }
 
         // 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
         executorService.submit(new Runnable() {
              public void run() {
 
                   try {
                      final Drawable drawable = loadImageFromUrl(imageUrl);
                      imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
 
                      handler.post(new Runnable() {
 
                           public void run(){ 
                                callback.imageLoaded(drawable); 
                           }
                      });
                   } catch (Exception e){ 
                        throw new RuntimeException(e); 
                   }
             }
        });
        return null;
    }
 
    // 从网络上取数据方法
    protected Drawable loadImageFromUrl(String imageUrl) {
           try{ 
              // 测试时,模拟网络延时,实际时这行代码不能有 
              SystemClock.sleep(2000); 
              return Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png"); 
           }catch (Exception e){ 
                throw new RuntimeException(e); 
           }
     }
 
     // 对外界开放的回调接口
     public interface ImageCallback{ 
         // 注意 此方法是用来设置目标对象的图像资源 
         public void imageLoaded(Drawable imageDrawable); 
     }
}


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

import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
 
import android.widget.ImageView;
 
public class MainActivity extends Activity {
 
    @Override
    public void onCreate(Bundle savedInstanceState){
 
        uper.onCreate(savedInstanceState); setContentView(R.layout.main); 
        loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1); 
        loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.imageView2); 
        loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3); 
        loadImage4("http://csdnimg.cn/www/images/csdnindex_logo.gif", R.id.imageView4); 
        loadImage4("http://images.cnblogs.com/logo_small.gif", R.id.imageView5); 
    }
 
    private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();
 
    // 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
    private void loadImage4(final String url, final int id) {
 
    // 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
    Drawable cacheImage = asyncImageLoader3.loadDrawable(url,
             new AsyncImageLoader3.ImageCallback() {
 
                  // 请参见实现:如果第一次加载url时下面方法会执行
                  public void imageLoaded(Drawable imageDrawable){ 
                        ((ImageView) findViewById(id)) .setImageDrawable(imageDrawable); 
                  }
            });
 
            if (cacheImage != null){ 
                 ((ImageView) findViewById(id)).setImageDrawable(cacheImage); 
            }
      }
 
}


参考资料
Android异步加载图像小结
http://blog.csdn.net/sgl870927/archive/2011/03/29/6285535.aspx
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics