Android中的延迟加载系列4(ImageView)


在Android应用程序的开发中,从网络或者服务器上取得图片,往往需要花费一定的时间,占用一定的用户带宽,当页面有大量的图片时,如果不采取延迟加载的方法,则客户端需要等到所有的图片都获取之后,才可以呈现完整界面,这就可能导致界面反应不流畅,影响用户体验。


图片延迟加载的原理其实非常简单,有两种思路:

第一种思路是后台启动Thread下载图片,下载完成后,通过Message Handle的方式把控制权转让给UI Thread并绘制图片。此方法的优点是比较简单,缺点是不容易封装。

第二种思路是启动异步任务AsyncTask,通过doInBackground()方法获得图片资源之后,再通过onPostExecute()方法在UI Thread中绘制取好的图片。


以上两种方式都可以很好地处理图片的延迟加载。本文通过第一种方式来处理,对图片的延迟加载进行封装,并对性能进行如下优化:

1) 图片加载的线程优先级比UI低一级,这样可以优先处理UI任务。

2) 在内存、磁盘缓存下载的应用程序图片。读取图片的顺序为 内存 -> 磁盘 -> 网络,这样可以避免下载重复的图片,节省网络流量,并且提高响应速度和性能。

3) 本地缓存的图片可能由于某种原因过期,而与服务端不一致,比如服务端更新了图片资源,这个时候本地并不知道,从而导致了图片的不一致性,这也是采取缓存技术提高性能而导致的副作用。为了解决这个问题,可以引入时间戳的概念,当时间戳发生变化之后,重新从网络上获取图片并缓存。


以下将做简要的说明。


首先建立LazyImage对象,此对象包括了图片image,图片的网络资源链接url,以及图片所对应的时间戳(从服务端获得,如果没有时间戳的固定图片,也可以不用设置)。

package com.whyonly.core.bean;

import android.graphics.Bitmap;

public abstract class LazyImage {
	
	private Bitmap image;
	private String image_url;
	private long timestamp;
	
	
	public boolean hasLoadPhoto(){
		return image==null ? false : true;
	}
	public long getTimestamp() {
		return timestamp;
	}
	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
		this.hashCode();
	}
	
	
	
	public Bitmap getImage() {
		return image;
	}
	public void setImage(Bitmap image) {
		this.image = image;
	}
	public String getImage_url() {
		return image_url;
	}
	public void setImage_url(String image_url) {
		this.image_url = image_url;
	}

	
	public String toFileName(){ //convert the url + timestamp to file name to instore to local disk
		String fileName = "";
		if(image_url!=null && image_url.indexOf("/")!=-1 ){
			fileName=image_url.substring(image_url.lastIndexOf("/")+1,image_url.length());
		}
		return  fileName+"_"+timestamp;
	}
	
	

}

建立一个下载任务

//Task for the queue
    private class PhotoToLoad
    {
        public LazyImage lazyImage;
        public ImageView imageView;
        public boolean saveDisk;
        public PhotoToLoad(LazyImage l, ImageView i, boolean s){
        	lazyImage=l; 
            imageView=i;
            saveDisk = s;
        }
    }

以及下载队列

    //stores list of photos to download
    class PhotosQueue
    {
        private Stack<PhotoToLoad> photosToLoadStack=new Stack<PhotoToLoad>();
        
        //removes all instances of this ImageView
        public void clean(ImageView image)
        {
            for(int j=0 ;j<photosToLoadStack.size();){
                if(photosToLoadStack.get(j).imageView==image)
                    photosToLoadStack.remove(j);
                else
                    ++j;
            }
        }
    }




接着建立内存缓冲类:

class MemoryCache {
    private HashMap<String, SoftReference<Bitmap>> cache=new HashMap<String, SoftReference<Bitmap>>();
    
    public Bitmap get(String id){
        if(!cache.containsKey(id))
            return null;
        SoftReference<Bitmap> ref=cache.get(id);
        return ref.get();
    }
    
    public void put(String id, Bitmap bitmap){
        cache.put(id, new SoftReference<Bitmap>(bitmap));
    }

    public void clear() {
        cache.clear();
    }
}


和本地磁盘缓冲类

class FileCache {
  
	private File cacheDir;
    
    public FileCache(Context context){
        //Find the dir to save cached images
//        if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
            cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"whyonly/cache");
//        else
//            cacheDir=context.getCacheDir();
        Log.d("ImageLoader","cacheDir:"+cacheDir);
        if(!cacheDir.exists())
            cacheDir.mkdirs();
    }
    
	public File getFile(LazyImage lazyImage){   
        String filename= lazyImage.toFileName();
        File f = new File(cacheDir, filename);
        return f;
        
    }
    
    public void clear(){
        File[] files=cacheDir.listFiles();
        for(File f:files)
            f.delete();
    }

}

把图片资源显示到ImageView

//Used to display bitmap in the UI thread
    class BitmapDisplayer implements Runnable
    {
        Bitmap bitmap;
        ImageView imageView;
        public BitmapDisplayer(Bitmap b, ImageView i){bitmap=b;imageView=i;}
        public void run()
        {
            if(bitmap!=null)
                imageView.setImageBitmap(bitmap);
            else
                imageView.setImageResource(defaultImageResId);
        }
    }

最后是通过线程来控制图片的下载和显示过程

    class PhotosLoaderThread extends Thread {
        public void run() {
            try {
                while(true)
                {
                    //thread waits until there are any images to load in the queue
                    if(photosQueue.photosToLoadStack.size()==0)
                        synchronized(photosQueue.photosToLoadStack){
                            photosQueue.photosToLoadStack.wait();
                        }
                    if(photosQueue.photosToLoadStack.size()!=0)
                    {
                        PhotoToLoad photoToLoad;
                        synchronized(photosQueue.photosToLoadStack){
                            photoToLoad=photosQueue.photosToLoadStack.pop();
                        }
                        Bitmap bmp=getBitmap(photoToLoad.lazyImage,photoToLoad.saveDisk);
                        memoryCache.put(photoToLoad.lazyImage.toFileName(), bmp);
                        String tag=imageViews.get(photoToLoad.imageView);
                        if(tag!=null && tag.equals(photoToLoad.lazyImage.toFileName())){
                            BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView);
                            Activity a=(Activity)photoToLoad.imageView.getContext();
                            a.runOnUiThread(bd);
                        }
                    }
                    if(Thread.interrupted())
                        break;
                }
            } catch (InterruptedException e) {
                //allow thread to exit
            }
        }
    }

完整的图片加载类如下:

package com.whyonly.core.wrapper;

import java.io.File;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.WeakHashMap;

import com.whyonly.core.bean.LazyImage;
import com.whyonly.core.util.ImageUtil;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.util.Log;
import android.widget.ImageView;

public class ImageLoader {
    
    private static final String TAG = "ImageLoader";
	private MemoryCache memoryCache;
    private FileCache fileCache;
    private Map<ImageView, String> imageViews;
    
    private PhotosLoaderThread photoLoaderThread;
    private PhotosQueue photosQueue;
    private int defaultImageResId;
    //private Context context;
    
    public ImageLoader(Context context,int defaultImageResId){
        //Make the background thead low priority. So it will not affect the UI performance
    	photoLoaderThread=new PhotosLoaderThread();
    	photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1);  
    	memoryCache=new MemoryCache();
        fileCache=new FileCache(context);
        imageViews=Collections.synchronizedMap(new WeakHashMap<ImageView, String>());
        photosQueue=new PhotosQueue();
        //this.context = context;
        this.defaultImageResId = defaultImageResId;
    }
    
    public void displayImage(LazyImage lazyImage, ImageView imageView){
    	displayImage(lazyImage,imageView,true);
    }
    
    public void displayImage(LazyImage lazyImage, ImageView imageView,boolean saveDisk)
    {
        imageViews.put(imageView, lazyImage.toFileName());  
        if(lazyImage.getImage()!=null){
        	imageView.setImageBitmap(lazyImage.getImage());//get from lazy image
            //Log.d(TAG,"----LazyImage cache:"+lazyImage.toFileName());
        }else{
        	Bitmap bitmap=memoryCache.get(lazyImage.toFileName());//get from memory cache
            if(bitmap!=null){
            	lazyImage.setImage(bitmap);
                imageView.setImageBitmap(bitmap);
                //Log.d(TAG,"----MEMORY cache:"+lazyImage.toFileName());
            }else
            {
            	if(defaultImageResId>0)
                	imageView.setImageResource(defaultImageResId);
                else
                	imageView.setImageBitmap(null);
            	if(lazyImage.getImage_url() != null)
            		queuePhoto(lazyImage, imageView,saveDisk);//get from SD card or web 
            }    
        }
    }
        
    private void queuePhoto(LazyImage lazyImage, ImageView imageView,boolean saveDisk)
    {
        //This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them. 
        photosQueue.clean(imageView);
        PhotoToLoad photosToLoad=new PhotoToLoad(lazyImage, imageView, saveDisk);
        synchronized(photosQueue.photosToLoadStack){
            photosQueue.photosToLoadStack.push(photosToLoad);
            photosQueue.photosToLoadStack.notifyAll();
        }
        
        //start thread if it's not started yet
        if(photoLoaderThread.getState()==Thread.State.NEW)
            photoLoaderThread.start();
    }
    
    private Bitmap getBitmap(LazyImage lazyImage,boolean saveDisk) 
    {
      	if(!saveDisk){
      		return ImageUtil.returnBitMap(lazyImage.getImage_url());
      	}
    	
    	File f=fileCache.getFile(lazyImage);
        //from SD cache
        Bitmap b = ImageUtil.file2Bitmap(f);
        if(b!=null){
        	lazyImage.setImage(b);
        	//Log.d(TAG,"----FILE cache:"+lazyImage.toFileName());
            return b;
        } 
        
        //from web
        try {
            URL imageUrl = new URL(lazyImage.getImage_url());
            HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(30000);
            InputStream is=conn.getInputStream();
            ImageUtil.inputStream2File(is, f);
            lazyImage.setImage(ImageUtil.file2Bitmap(f));
            //Log.d(TAG,"----WEB URL:"+lazyImage.toFileName());
            return lazyImage.getImage();
        } catch (Exception ex){
           //ex.printStackTrace();
           return null;
        }
    }

    
    //Task for the queue
    private class PhotoToLoad
    {
        public LazyImage lazyImage;
        public ImageView imageView;
        public boolean saveDisk;
        public PhotoToLoad(LazyImage l, ImageView i, boolean s){
        	lazyImage=l; 
            imageView=i;
            saveDisk = s;
        }
    }
    
    
    public void stopThread()
    {
        photoLoaderThread.interrupt();
    }
    
    //stores list of photos to download
    class PhotosQueue
    {
        private Stack<PhotoToLoad> photosToLoadStack=new Stack<PhotoToLoad>();
        
        //removes all instances of this ImageView
        public void clean(ImageView image)
        {
            for(int j=0 ;j<photosToLoadStack.size();){
                if(photosToLoadStack.get(j).imageView==image)
                    photosToLoadStack.remove(j);
                else
                    ++j;
            }
        }
    }
    
    class PhotosLoaderThread extends Thread {
        public void run() {
            try {
                while(true)
                {
                    //thread waits until there are any images to load in the queue
                    if(photosQueue.photosToLoadStack.size()==0)
                        synchronized(photosQueue.photosToLoadStack){
                            photosQueue.photosToLoadStack.wait();
                        }
                    if(photosQueue.photosToLoadStack.size()!=0)
                    {
                        PhotoToLoad photoToLoad;
                        synchronized(photosQueue.photosToLoadStack){
                            photoToLoad=photosQueue.photosToLoadStack.pop();
                        }
                        Bitmap bmp=getBitmap(photoToLoad.lazyImage,photoToLoad.saveDisk);
                        memoryCache.put(photoToLoad.lazyImage.toFileName(), bmp);
                        String tag=imageViews.get(photoToLoad.imageView);
                        if(tag!=null && tag.equals(photoToLoad.lazyImage.toFileName())){
                            BitmapDisplayer bd=new BitmapDisplayer(bmp, photoToLoad.imageView);
                            Activity a=(Activity)photoToLoad.imageView.getContext();
                            a.runOnUiThread(bd);
                        }
                    }
                    if(Thread.interrupted())
                        break;
                }
            } catch (InterruptedException e) {
                //allow thread to exit
            }
        }
    }
    
    
    
    //Used to display bitmap in the UI thread
    class BitmapDisplayer implements Runnable
    {
        Bitmap bitmap;
        ImageView imageView;
        public BitmapDisplayer(Bitmap b, ImageView i){bitmap=b;imageView=i;}
        public void run()
        {
            if(bitmap!=null)
                imageView.setImageBitmap(bitmap);
            else
                imageView.setImageResource(defaultImageResId);
        }
    }

    public void clearCache() {
        memoryCache.clear();
        fileCache.clear();
    }
    
    public void clearMemoryCache() {
        memoryCache.clear();
    }

}

class MemoryCache {
    private HashMap<String, SoftReference<Bitmap>> cache=new HashMap<String, SoftReference<Bitmap>>();
    
    public Bitmap get(String id){
        if(!cache.containsKey(id))
            return null;
        SoftReference<Bitmap> ref=cache.get(id);
        return ref.get();
    }
    
    public void put(String id, Bitmap bitmap){
        cache.put(id, new SoftReference<Bitmap>(bitmap));
    }

    public void clear() {
        cache.clear();
    }
}

class FileCache {
  
	private File cacheDir;
    
    public FileCache(Context context){
        //Find the dir to save cached images
//        if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED))
            cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"whyonly/cache");
//        else
//            cacheDir=context.getCacheDir();
        Log.d("ImageLoader","cacheDir:"+cacheDir);
        if(!cacheDir.exists())
            cacheDir.mkdirs();
    }
    
	public File getFile(LazyImage lazyImage){   
        String filename= lazyImage.toFileName();
        File f = new File(cacheDir, filename);
        return f;
        
    }
    
    public void clear(){
        File[] files=cacheDir.listFiles();
        for(File f:files)
            f.delete();
    }

}


通过 ImageLoader的包装,应用起来应该非常简单,示例代码如下:

private ImageLoader imageLoader = new ImageLoader(this,R.drawable.nohead);

imageLoader.displayImage(bean, imageView);


以上给出来图片延迟加载的基本概念,以及通过一个示例,说明了如何封装,以便应用程序可以简单地调用。下一篇将通过一个完整的工程例子,对图片的延迟加载,以及ListView的延迟加载加载进行综述。(待续)



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值