zoukankan      html  css  js  c++  java
  • Android 异步加载图片分析总结

    异步加载图片主要是利用多线程进行下载、图片弱引用缓存和Handler操作UI进行实现的。

    异步加载图片的主要流程是进行判断缓存中是否存在图片,如果存在则直接返回,如果不存在则进行下载并进行缓存。

    以下是建立一个异步下载类:

    复制代码代码如下:

    /**
     * User: Tom
     * Date: 13-5-13
     * Time: 下午8:07
     */
    public class AsnycImageLoader {

        //定义一个HashMap进行存放缓存的Image key为String Value为一个弱引用的一个资源文件
        // 图片 为了方便JAVA的回收
        private Map<String, SoftReference<Drawable>> imageCache = null;
        public AsnycImageLoader() {
            imageCache = new HashMap<String, SoftReference<Drawable>>();
        }

        /**
         * 加载图片
         * <p>imageurl为下载资源的URL,
         * ImageCallback当缓存中不存在相关图片时时行网络下载
         * 在多线程下要使用Handler进行操作UI 利用回调接口的方式进行更新UI
         * </p>
         * @param imageUrl
         * @param callback
         * @return
         */
        public Drawable loadDrawable(final String imageUrl, final ImageCallback callback) {
            //进行判断ImageCache中是否存在缓存图片
            if (imageCache.containsKey(imageUrl)) {
                SoftReference<Drawable> softReference = imageCache.get(imageUrl);
                if (softReference.get() != null) {
                    return softReference.get();
                }
            }
            //定义操作UI的Handler
            final Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    callback.imageLoaded((Drawable) msg.obj);
                }
            };

            new Thread(new Runnable() {
                @Override
                public void run() {
                    Drawable drawable = loadImageFromUrl(imageUrl);
                    imageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
                    Message message = handler.obtainMessage(0, drawable);
                    handler.sendMessage(message);
                }
            }).start();
            return null;
        }

        //根据URL地址进行获取资源
        protected Drawable loadImageFromUrl(String imageUrl) {
            try {
                return Drawable.createFromStream(new URL(imageUrl).openStream(), "src");
            } catch (Exception e) {
                throw new RuntimeException();
            }
        }

        //回调接口
        public interface ImageCallback {
            public abstract void imageLoaded(Drawable drawable);
        }
    }

    主Activity:

    复制代码代码如下:

    /**
     * User: Tom
     * Date: 13-5-13
     * Time: 下午8:33
     */
    public class LoadImage extends Activity {
        private AsnycImageLoader loader = null;

        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.loadimages);

            loader = new AsnycImageLoader();

            loadImage("http://www.jb51.net/images/icon-partners.png", R.id.image1);
            loadImage("http://www.jb51.net/images/icon-dev.png", R.id.image2);
            loadImage("http://pic28.jb51.net/20130421/12302174_231210305323_2.jpg", R.id.image3);


        }

        public void loadImage(String url, int id) {
            final ImageView imageView = (ImageView) findViewById(id);
            Drawable cacheImage = loader.loadDrawable(url, new AsnycImageLoader.ImageCallback() {
                @Override
                public void imageLoaded(Drawable drawable) {
                    imageView.setImageDrawable(drawable);
                }
            });
            if (cacheImage != null) {
                imageView.setImageDrawable(cacheImage);
            }
        }
    }


    研究了android从网络上异步加载图像,现总结如下: 
    (1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法。 

    在主线程中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.png"); 
    } catch (IOException e) { 

    ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable); 

    }); 


    上面这个方法缺点很显然,经测试,如果要加载多个图片,这并不能实现异步加载,而是等到所有的图片都加载完才一起显示,因为它们都运行在一个线程中。 

    然后,我们可以简单改进下,将Handler+Runnable模式改为Handler+Thread+Message模式不就能实现同时开启多个线程吗? 
    (2)在主线程中new 一个Handler对象,代码如下: 
    复制代码代码如下:

    final Handler handler2=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= handler2.obtainMessage() ; 
    message.arg1 = id; 
    message.obj = drawable; 
    handler2.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)是图像对象Drawable。先看一下我们封装的类 
    复制代码代码如下:

    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 回调接口 
    * <a href=""http://www.eoeandroid.com/home.php?mod=space&uid=7300"" target=""_blank"">@return</a> 返回内存中缓存的图像,第一次加载返回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 = Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png"); 
      
    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 { 
    return Drawable.createFromStream(new URL(imageUrl).openStream(), "image.png"); 
    } catch (Exception e) { 
    throw new RuntimeException(e); 


    //对外界开放的回调接口 
    public interface ImageCallback { 
    //注意 此方法是用来设置目标对象的图像资源 
    public void imageLoaded(Drawable imageDrawable); 



    这样封装好后使用起来就方便多了。在主线程中首先要引入AsyncImageLoader3 对象,然后直接调用其loadDrawable方法即可,需要注意的是ImageCallback接口的imageLoaded方法是唯一可以把加载的图 像设置到目标ImageView或其相关的组件上。 

    在主线程调用代码: 
    先实例化对象 private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3(); 
    调用异步加载方法: 
    复制代码代码如下:

    //引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程 
    private void loadImage4(final String url, final int id) { 
    //如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行 
    Drawable cacheImage = asyncImageLoader.loadDrawable(url,new AsyncImageLoader.ImageCallback() {
    //请参见实现:如果第一次加载url时下面方法会执行 
    public void imageLoaded(Drawable imageDrawable) { 
    ((ImageView) findViewById(id)).setImageDrawable(imageDrawable); 

    }); 
    if(cacheImage!=null){ 
    ((ImageView) findViewById(id)).setImageDrawable(cacheImage); 



    5)同理,下面也给出采用Thread+Handler+MessageQueue+内存缓存代码,原则同(4),只是把线程池换成了Thread+Handler+MessageQueue模式而已。代码如下: 
    复制代码代码如下:

    public class AsyncImageLoader { 
    //为了加快速度,加入了缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动) 
    private Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>(); 
      
    /** 

    * @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(); 


      
    final Handler handler = new Handler() { 
    @Override 
    public void handleMessage(Message msg) { 
    callback.imageLoaded((Drawable) msg.obj); 

    }; 
    new Thread() { 
    public void run() { 
    Drawable drawable = loadImageFromUrl(imageUrl); 
    imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); 
    handler.sendMessage(handler.obtainMessage(0, drawable)); 
      

      
    }.start(); 
    /* 
    下面注释的这段代码是Handler的一种代替方法 
    */ 
    // new AsyncTask() { 
    // @Override 
    // protected Drawable doInBackground(Object... objects) { 
    // Drawable drawable = loadImageFromUrl(imageUrl); 
    // imageCache.put(imageUrl, new SoftReference<Drawable>(drawable)); 
    // return drawable; 
    // } 
    // 
    // @Override 
    // protected void onPostExecute(Object o) { 
    // callback.imageLoaded((Drawable) o); 
    // } 
    // }.execute(); 
    return null; 

      
    protected Drawable loadImageFromUrl(String imageUrl) { 
    try { 
    return Drawable.createFromStream(new URL(imageUrl).openStream(), "src"); 
    } catch (Exception e) { 
    throw new RuntimeException(e); 


    //对外界开放的回调接口 
    public interface ImageCallback { 
    public void imageLoaded(Drawable imageDrawable); 



    至此,异步加载就介绍完了,下面给出的代码为测试用的完整代码: 
    复制代码代码如下:

    package com.bshark.supertelphone.activity; 
      
    import android.app.Activity; 
    import android.graphics.drawable.Drawable; 
    import android.os.Bundle; 
    import android.os.Handler; 
    import android.os.Message; 
    import android.widget.ImageView; 
    import com.bshark.supertelphone.R; 
    import com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader; 
    import com.bshark.supertelphone.ui.adapter.util.AsyncImageLoader3; 
      
    import java.io.IOException; 
    import java.net.URL; 
    import java.util.concurrent.ExecutorService; 
    import java.util.concurrent.Executors; 
      
    public class LazyLoadImageActivity extends Activity { 
    final Handler handler=new Handler(); 
    final Handler handler2=new Handler(){ 
    @Override 
    public void handleMessage(Message msg) { 
    ((ImageView) LazyLoadImageActivity.this.findViewById(msg.arg1)).setImageDrawable((Drawable)msg.obj); 

    }; 
    private ExecutorService executorService = Executors.newFixedThreadPool(5); //固定五个线程来执行任务 
    private AsyncImageLoader asyncImageLoader = new AsyncImageLoader(); 
    private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3(); 
      
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 
      
    // loadImage("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); 
    // loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); 
    // loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); 
    // loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); 
    // loadImage("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); 
      
    loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); 
    loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); 
    loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); 
    loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); 
    loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); 
    // loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); 
    // loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); 
    // loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); 
    // loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); 
    // loadImage3("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); 
      
    // loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); 
    // loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); 
    // loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); 
    // loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); 
    // loadImage4("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); 
      
    // loadImage5("http://www.chinatelecom.com.cn/images/logo_new.gif", R.id.image1); 
    // //为了测试缓存而模拟的网络延时 
    // SystemClock.sleep(2000); 
    // loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image2); 
    // SystemClock.sleep(2000); 
    // loadImage5("http://cache.soso.com/30d/img/web/logo.gif", R.id.image3); 
    // SystemClock.sleep(2000); 
    // loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); 
    // SystemClock.sleep(2000); 
    // loadImage5("http://cache.soso.com/30d/img/web/logo.gif", R.id.image5); 
    // SystemClock.sleep(2000); 
    // loadImage5("http://www.baidu.com/img/baidu_logo.gif", R.id.image4); 

      
    @Override 
    protected void onDestroy() { 
    executorService.shutdown(); 
    super.onDestroy(); 

    //线程加载图像基本原理 
    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.png"); 
    } catch (IOException e) { 

    ((ImageView) LazyLoadImageActivity.this.findViewById(id)).setImageDrawable(drawable); 

    }); 

    //采用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= handler2.obtainMessage() ; 
    message.arg1 = id; 
    message.obj = drawable; 
    handler2.sendMessage(message); 

    }; 
    thread.start(); 
    thread = null; 

    // 引入线程池来管理多线程 
    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); 


    }); 

    //引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程 
    private void loadImage4(final String url, final int id) { 
    //如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行 
    Drawable cacheImage = asyncImageLoader.loadDrawable(url,new AsyncImageLoader.ImageCallback() {
    //请参见实现:如果第一次加载url时下面方法会执行 
    public void imageLoaded(Drawable imageDrawable) { 
    ((ImageView) findViewById(id)).setImageDrawable(imageDrawable); 

    }); 
    if(cacheImage!=null){ 
    ((ImageView) findViewById(id)).setImageDrawable(cacheImage); 


      
    //采用Handler+Thread+封装外部接口 
    private void loadImage5(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); 


      
      


    xml文件大致如下: 
    复制代码代码如下:

    <SPAN style="FONT-SIZE: 18px"><STRONG>< ?xml version="1.0" encoding="utf-8"?> 
      
    < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" 
    android:orientation="vertical" 
    android:layout_height="fill_parent" > 
    <ImageView android:id="@+id/image1" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
    <ImageView android:id="@+id/image2" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
    <ImageView android:id="@+id/image3" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
    <ImageView android:id="@+id/image5" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
    <ImageView android:id="@+id/image4" android:layout_height="wrap_content" android:layout_width="fill_parent"></ImageView> 
    < /LinearLayout></STRONG></SPAN> 

  • 相关阅读:
    UVA 10617 Again Palindrome
    UVA 10154 Weights and Measures
    UVA 10201 Adventures in Moving Part IV
    UVA 10313 Pay the Price
    UVA 10271 Chopsticks
    Restore DB後設置指引 for maximo
    每行SQL語句加go換行
    种服务器角色所拥有的权限
    Framework X support IPV6?
    模擬DeadLock
  • 原文地址:https://www.cnblogs.com/xieping/p/4784536.html
Copyright © 2011-2022 走看看