Android中的缓存处理及异步加载图片类的封装 有更新!

  hehaitao074

    一、缓存介绍:

    (一)、Android中缓存的必要性:

    智能手机的缓存管理应用非常的普遍和需要,是提高用户体验的有效手段之一。

    1、没有缓存的弊端:

    • 流量开销:对于客户端——服务器端应用,从远程获取图片算是经常要用的一个功能,而图片资源往往会消耗比较大的流量。

    • 加载速度:如果应用中图片加载速度很慢的话,那么用户体验会非常糟糕。

    • 那么如何处理好图片资源的获取和管理呢?异步下载+本地缓存

    2、缓存带来的好处:

    • 服务器的压力大大减小;

    • 客户端的响应速度大大变快(用户体验好);

    • 客户端的数据加载出错情况大大较少,大大提高了应有的稳定性(用户体验好);

    • 一定程度上可以支持离线浏览(或者说为离线浏览提供了技术支持)。

    3、缓存管理的应用场景:

    • 提供网络服务的应用;

    • 数据更新不需要实时更新,即便是允许3-5分钟的延迟也建议采用缓存机制;

    • 缓存的过期时间是可以接受的(不会因为缓存带来的好处,导致某些数据因为更新不及时而影响产品的形象等)

    4、大位图导致内存开销大的原因是什么?

    • 下载或加载的过程中容易导致阻塞;
    • 大位图Bitmap对象是png格式的图片的30至100倍;
    • 大位图在加载到ImageView控件前的解码过程;BitmapFactory.decodeFile()会有内存消耗。

    5、缓存设计的要点:

    • 命中率;
    • 合理分配占用的空间;
    • 合理的缓存层级。

    (二)、加载图片的正确流程是:“内存-文件-网络 三层cache策略”

    1、先从内存缓存中获取,取到则返回,取不到则进行下一步;

    2、从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行下一步;

    3、从网络下载图片,并更新到内存缓存和文件缓存。

    具体说就是:同一张图片只要从网络获取一次,然后在本地缓存起来,之后加载同一张图片时就从缓存中去加载。从内存缓存读取图片是最快的,但是因为内存容量有限,所以最好再加上文件缓存。文件缓存空间也不是无限大的,容量越大读取效率越低,因此可以设置一个限定大小比如10M,或者限定保存时间比如一天。

    在键值对(key-value)中,图片缓存的key是图片url的hash值,value就是bitmap。所以,按照这个逻辑,只要一个url被下载过,其图片就被缓存起来了。

    (三)、内存缓存分类:

    在JDK1.2以前的版本中,当一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及状态,程序才能使用它。这 就像在日常生活中,从商店购买了某样物品后,如果有用,就一直保留它,否则就把它扔到垃圾箱,由清洁工人收走。一般说来,如果物品已经被扔到垃圾箱,想再 把它捡回来使用就不可能了。但有时候情况并不这么简单,你可能会遇到类似鸡肋一样的物品,食之无味,弃之可惜。这种物品现在已经无用了,保留它会占空间,但是立刻扔掉它也不划算,因为也许将来还会派用场。对于这样的可有可无的物品,一种折衷的处理办法是:如果家里空间足够,就先把它保留在家里,如果家里空间不够,即使把家里所有的垃圾清除,还是无法容纳那些必不可少的生活用品,那么再扔掉这些可有可无的物品。

    从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。

    这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

    1、强引用:(在Android中LruCache就是强引用缓存)

    平时我们编程的时候例如:Object object=new Object();那object就是一个强引用了。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OOM异常,使程序异常终止,也不会回收具有强引用的对象来解决内存不足问题。

    2、软引用(SoftReference):

    软引用类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

    使用软引用能防止内存泄露,增强程序的健壮性。

    3、弱引用(WeakReference):

    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    4、虚引用(PhantomReference)

    "虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动。

    虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

    【相关应用:】

    在java.lang.ref包中提供了三个类:SoftReference类、WeakReference类和PhantomReference类,它们分别代表软引用、弱引用和虚引用。ReferenceQueue类表示引用队列,它可以和这三种引用类联合使用,以便跟踪Java虚拟机回收所引用的对 象的活动。

        Lru:Least Recently Used 
    
        近期最少使用算法,是一种页面置换算法,其思想是在缓存的页面数目固定的情况下,那些最近使用次数最少的页面将被移出,对于我们的内存缓存来说,强引用缓存大小固定为4M,如果当缓存的图片大于4M的时候,有些图片就会被从强引用缓存中删除,哪些图片会被删除呢,就是那些近期使用次数最少的图片。
    

    (四)、内存保存:

    在内存中保存的话,只能保存一定的量,而不能一直往里面放,需要设置数据的过期时间、LRU等算法。这里有一个方法是把常用的数据放到一个缓存中(A),不常用的放到另外一个缓存中(B)。当要获取数据时先从A中去获取,如果A中不存在那么再去B中获取。B中的数据主要是A中LRU出来的数据,这里的内存回收主要针对B内存,从而保持A中的数据可以有效的被命中。

    要写带有缓存机制的异步图片批量加载首先要有数据源

    Json字符串:

    {“data”:{“BookList”:[{“BookId”:“87”,“CoverStoryName”:“JNC 8存争议,我国高血压实践以中国指南为准”,“CoverStoryName2”:“小儿便秘多由饮食和排便习惯引起”,“IssueDate”:“2014年01月10日”,“Issue”:“584”,“IssueYear”:“2014年第2期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/one.jpg”},{“BookId”:“86”,“CoverStoryName”:“道阻且长,溯洄从之”,“CoverStoryName2”:“对慢性荨麻疹病因治疗的思考”,“IssueDate”:“2014年01月01日”,“Issue”:“583”,“IssueYear”:“2014年第1期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/two.jpg”},{“BookId”:“85”,“CoverStoryName”:“急性胰腺炎诊断:上腹部疼痛结合辅助检查”,“CoverStoryName2”:“急性胰腺炎一经诊断,应作严重程度评估”,“IssueDate”:“2013年12月27日”,“Issue”:“582”,“IssueYear”:“2013年第48期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/three.jpg”},{“BookId”:“84”,“CoverStoryName”:“绩效考核分配机制引争议”,“CoverStoryName2”:“护肝药物知多少”,“IssueDate”:“2013年12月20日”,“Issue”:“581”,“IssueYear”:“2013年第47期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/four.jpg”},{“BookId”:“83”,“CoverStoryName”:“脑卒中后运动功能障碍的康复治疗”,“CoverStoryName2”:“脑卒中后感觉、认知、言语、吞咽功能障碍的康复”,“IssueDate”:“2013年12月13日”,“Issue”:“580”,“IssueYear”:“2013年第46期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/five.jpg”},{“BookId”:“82”,“CoverStoryName”:“契合基层定位,有的放矢谈糖尿病的药物应用”,“CoverStoryName2”:“导致不孕的因素具有多样性和复杂性”,“IssueDate”:“2013年12月06日”,“Issue”:“579”,“IssueYear”:“2013年第45期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/six.jpg”},{“BookId”:“81”,“CoverStoryName”:“川崎病:容易与感冒混淆的皮肤黏膜淋巴结综合征”,“CoverStoryName2”:“川崎病的治疗措施”,“IssueDate”:“2013年10月18日”,“Issue”:“573”,“IssueYear”:“2013年第38期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/seven.jpg”},{“BookId”:“80”,“CoverStoryName”:“人文关怀与服务意识——好医生的两大法宝”,“CoverStoryName2”:“特发性震颤的再认识:并非总是良性过程”,“IssueDate”:“2013年10月04日”,“Issue”:“571”,“IssueYear”:“2013年第37期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/eight.jpg”},{“BookId”:“79”,“CoverStoryName”:“中西药复方制剂花甲之年的哀愁”,“CoverStoryName2”:“白癜风,受遗传基因与环境的多因素影响的疾病”,“IssueDate”:“2013年08月23日”,“Issue”:“566”,“IssueYear”:“2013年第32期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/nine.jpg”},{“BookId”:“78”,“CoverStoryName”:“聚焦基本药物制度:破浪前行需要多助力”,“CoverStoryName2”:“认识小儿特应性皮炎”,“IssueDate”:“2013年08月16日”,“Issue”:“565”,“IssueYear”:“2013年第31期”,“CoverStoryImage”:“http://192.168.125.125:8080/AndroidServer/ten.jpg”}]}}

    通过这个json字符串我们来模拟一下

    MainActivity类的相关操作

    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import org.apache.http.HttpResponse;
    import org.apache.http.client.ClientProtocolException;
    import org.apache.http.client.HttpClient;
    import org.apache.http.client.methods.HttpGet;
    import org.apache.http.impl.client.DefaultHttpClient;
    import org.apache.http.util.EntityUtils;
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.view.Menu;
    import android.widget.ListView;
    
    public class MainActivity extends Activity {
    
    	private ListView listView_main;
    
    	private static final String uriString = "http://192.168.125.125:8080/AndroidServer/MyJsonServlet";
    
    	private Handler handler = new Handler() {
    
    		@Override
    		public void handleMessage(Message msg) {
    			// TODO Auto-generated method stub
    			super.handleMessage(msg);
    			switch (msg.what) {
    			case 1:
    
    				List> list = (List>) msg.obj;
    				listView_main
    						.setAdapter(new MyAdapter(MainActivity.this, list));
    				break;
    
    			default:
    				break;
    			}
    		}
    
    	};
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    
    		listViewmain = (ListView) findViewById(R.id.listViewmain);
    
    		new Thread(new Mythread()).start();
    
    	}
    
    	@Override
    	public boolean onCreateOptionsMenu(Menu menu) {
    		// Inflate the menu; this adds items to the action bar if it is present.
    		getMenuInflater().inflate(R.menu.main, menu);
    		return true;
    	}
    
    	class Mythread implements Runnable {
    
    		@Override
    		public void run() {
    			handler.sendEmptyMessage(0);
    			HttpGet get = new HttpGet(uriString);
    			HttpClient client = new DefaultHttpClient();
    
    			HttpResponse response;
    			try {
    				response = client.execute(get);
    				Message message = Message.obtain();
    				if (response.getStatusLine().getStatusCode() == 200) {
    
    					String data = EntityUtils.toString(response.getEntity());
    					try {
    						// json字符串的解析
    						List> list = new ArrayList>();
    						JSONObject jsonObject = new JSONObject(data);
    						JSONObject jsonObject2 = jsonObject
    								.getJSONObject("data");
    						JSONArray array = jsonObject2.getJSONArray("BookList");
    						for (int i = 0; i < array.length(); i++) {
    							Map map = new HashMap();
    							JSONObject jsonObject3 = array.getJSONObject(i);
    							map.put("BookId", jsonObject3.get("BookId"));
    							map.put("CoverStoryName",
    									jsonObject3.get("CoverStoryName"));
    							map.put("CoverStoryName2",
    									jsonObject3.get("CoverStoryName2"));
    							map.put("IssueDate", jsonObject3.get("IssueDate"));
    							map.put("Issue", jsonObject3.get("Issue"));
    							map.put("IssueYear", jsonObject3.get("IssueYear"));
    							map.put("CoverStoryImage",
    									jsonObject3.get("CoverStoryImage"));
    							list.add(map);
    
    						}
    						// 将得到的list返回到主线程,进行操作
    						message.what = 1;
    						message.obj = list;
    						handler.sendMessage(message);
    					} catch (JSONException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    
    				}
    			} catch (ClientProtocolException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			} catch (IOException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    
    		}
    	}
    
    }
    

    自定义适配器

    import java.util.List;
    import java.util.Map;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.BaseAdapter;
    import android.widget.ImageView;
    import android.widget.TextView;
    
    import com.hht.homework_asynclistviewimage.ImageDownloadHelper.OnImageDownloadListener;
    
    public class MyAdapter extends BaseAdapter {
    
    	private Context context;
    	private List> list;
    
    	private View view = null;
    
    	private ImageDownloadHelper downloadHelper = null;
    
    	public MyAdapter(Context context, List> list) {
    		super();
    		this.context = context;
    		this.list = list;
    	}
    
    	@Override
    	public int getCount() {
    		// TODO Auto-generated method stub
    		return list.size();
    	}
    
    	@Override
    	public Object getItem(int position) {
    		// TODO Auto-generated method stub
    		return list.get(position);
    	}
    
    	@Override
    	public long getItemId(int position) {
    		// TODO Auto-generated method stub
    		return position;
    	}
    
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		// TODO Auto-generated method stub
    
    		// 重写getView方法,并进行相关优化,合理利用converView
    		final ViewHolder holder;
    		if (convertView == null) {
    			holder = new ViewHolder();
    			convertView = LayoutInflater.from(context).inflate(
    					R.layout.itemlistviewmain, null);
    			holder.imageViewmaincover = (ImageView) convertView
    					.findViewById(R.id.imageViewmaincover);
    			holder.CoverStoryName = (TextView) convertView
    					.findViewById(R.id.CoverStoryName);
    			holder.CoverStoryName2 = (TextView) convertView
    					.findViewById(R.id.CoverStoryName2);
    			holder.IssueDate = (TextView) convertView
    					.findViewById(R.id.IssueDate);
    			holder.Issue = (TextView) convertView.findViewById(R.id.Issue);
    			holder.IssueYear = (TextView) convertView
    					.findViewById(R.id.IssueYear);
    			// 打标签
    			convertView.setTag(holder);
    		} else {
    			// 通过标签获得holder
    			holder = (ViewHolder) convertView.getTag();
    		}
    		view = convertView;
    		String uriString = list.get(position).get("CoverStoryImage").toString();
    		holder.imageViewmaincover.setTag(uriString);
    		holder.imageViewmaincover.setImageResource(R.drawable.defaultcovers);
    		holder.CoverStoryName.setText(list.get(position).get("CoverStoryName")
    				.toString());
    		holder.CoverStoryName2.setText(list.get(position)
    				.get("CoverStoryName2").toString());
    		holder.IssueDate
    				.setText(list.get(position).get("IssueDate").toString());
    		holder.Issue.setText(list.get(position).get("Issue").toString());
    		holder.IssueYear
    				.setText(list.get(position).get("IssueYear").toString());
    
    		if (downloadHelper == null) {
    			downloadHelper = new ImageDownloadHelper();
    		}
    		if (downloadHelper != null) {
    			// 图片下载利用了缓存的机制强缓存,软缓存,以及文件缓存
    			downloadHelper.imageDownload(context, uriString,
    					holder.imageViewmaincover, "hht",
    					new OnImageDownloadListener() {
    
    						// 利用接口回调的方法使得view控件能与工作线程进行数据交换
    						@Override
    						public void onImageDownload(Bitmap bitmap, String imgUrl) {
    							// TODO Auto-generated method stub
    							ImageView imageView = (ImageView) view
    									.findViewWithTag(imgUrl);
    							if (imageView != null) {
    								imageView.setImageBitmap(bitmap);
    								imageView.setTag(null);
    							}
    
    						}
    					});
    		}
    
    		return convertView;
    	}
    
    	class ViewHolder {
    		private ImageView imageViewmaincover;
    		private TextView CoverStoryName;
    		private TextView CoverStoryName2;
    		private TextView IssueDate;
    		private TextView Issue;
    		private TextView IssueYear;
    
    	}
    
    }
    

    其实主要的操作在一个异步下载图片的工具类中,里面融合了异步加载,缓存技术,IO流,网络操作,SDCard文件操作等知识点

    package com.hht.homework_asynclistviewimage;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.lang.ref.SoftReference;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.HashMap;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.AsyncTask;
    import android.os.Environment;
    import android.util.Log;
    import android.util.LruCache;
    import android.widget.ImageView;
    
    public class ImageDownloadHelper {
    	private static final String TAG = "ImageDownloaderHelper";
    	private HashMap map = new HashMap();
    	private Map> softCaches = new LinkedHashMap>();
    	private LruCache lruCache = null;
    
    	public ImageDownloadHelper() {
    		int memoryAmount = (int) Runtime.getRuntime().maxMemory();
    		// 获取剩余内存的8分之一作为缓存
    		int cacheSize = memoryAmount / 8;
    		if (lruCache == null) {
    			lruCache = new MyLruCache(cacheSize);
    		}
    		Log.i(TAG, "==LruCache尺寸:" + cacheSize);
    	}
    
    	/**
    	 * 
    	 * @param context
    	 * @param url
    	 *            该mImageView对应的url
    	 * @param mImageView
    	 * @param path
    	 *            文件存储路径
    	 * @param downloadListener
    	 *            OnImageDownload回调接口,在onPostExecute()中被调用
    	 */
    	public void imageDownload(Context context, String url,
    			ImageView mImageView, String path,
    			OnImageDownloadListener downloadListener) {
    		Bitmap bitmap = null;
    		// 先从强引用中拿数据
    		if (lruCache != null) {
    			bitmap = lruCache.get(url);
    		}
    		if (bitmap != null && url.equals(mImageView.getTag())) {
    			Log.i(TAG, "==从强引用中找到数据");
    			mImageView.setImageBitmap(bitmap);
    		} else {
    			SoftReference softReference = softCaches.get(url);
    			if (softReference != null) {
    				bitmap = softReference.get();
    			}
    
    			// 从软引用中拿数据
    			if (bitmap != null && url.equals(mImageView.getTag())) {
    				Log.i(TAG, "==从软引用中找到数据");
    				// 添加到强引用中
    				lruCache.put(url, bitmap);
    				Log.i(TAG, "==添加到强引用中");
    
    				// 从软引用集合中移除
    				softCaches.remove(url);
    				mImageView.setImageBitmap(bitmap);
    			} else {
    				// 从文件缓存中拿数据
    				String imageName = "";
    				if (url != null) {
    					imageName = ImageDownloaderUtil.getInstance().getImageName(
    							url);
    				}
    				bitmap = getBitmapFromFile(context, imageName, path);
    				if (bitmap != null && url.equals(mImageView.getTag())) {
    					Log.i(TAG, "==从文件缓存中找到数据");
    					// 放入强缓存
    					lruCache.put(url, bitmap);
    					mImageView.setImageBitmap(bitmap);
    				} else {
    					// 从网络中拿数据
    					if (url != null && needCreateNewTask(mImageView)) {
    						MyAsyncTask task = new MyAsyncTask(context, url,
    								mImageView, path, downloadListener);
    						Log.i(TAG, "==从网络中拿数据");
    						if (mImageView != null) {
    							task.execute();
    							// 将对应的url对应的任务存起来
    							map.put(url, task);
    						}
    					}
    				}
    			}
    		}
    	}
    
    	/**
    	 * 判断是否需要重新创建线程下载图片,如果需要,返回值为true。
    	 * 
    	 * @param url
    	 * @param mImageView
    	 * @return
    	 */
    	private boolean needCreateNewTask(ImageView mImageView) {
    		boolean b = true;
    		if (mImageView != null) {
    			String currtaskurl = (String) mImageView.getTag();
    			if (isTasksContains(currtaskurl)) {
    				b = false;
    			}
    		}
    		return b;
    	}
    
    	/**
    	 * 检查该url(最终反映的是当前的ImageView的tag,tag会根据position的不同而不同)对应的task是否存在
    	 * 
    	 * @param url
    	 * @return
    	 */
    	private boolean isTasksContains(String url) {
    		boolean b = false;
    		if (map != null && map.get(url) != null) {
    			b = true;
    		}
    		return b;
    	}
    
    	/**
    	 * 删除map中该url的信息,这一步很重要,不然MyAsyncTask的引用会“一直”存在于map中
    	 * 
    	 * @param url
    	 */
    	private void removeTaskFromMap(String url) {
    		if (url != null && map != null && map.get(url) != null) {
    			map.remove(url);
    			Log.i(TAG, "当前map的大小==" + map.size());
    		}
    	}
    
    	/**
    	 * 从文件中拿图片
    	 * 
    	 * @param mActivity
    	 * @param imageName
    	 *            图片名字
    	 * @param path
    	 *            图片路径
    	 * @return
    	 */
    	private Bitmap getBitmapFromFile(Context context, String imageName,
    			String path) {
    		Bitmap bitmap = null;
    		if (imageName != null) {
    			File file = null;
    			String real_path = "";
    			try {
    				if (ImageDownloaderUtil.getInstance().hasSDCard()) {
    					real_path = ImageDownloaderUtil.getInstance().getExtPath()
    							+ (path != null && path.startsWith("/") ? path
    									: "/" + path);
    				} else {
    					real_path = ImageDownloaderUtil.getInstance()
    							.getPackagePath(context)
    							+ (path != null && path.startsWith("/") ? path
    									: "/" + path);
    				}
    				file = new File(real_path, imageName);
    				if (file.exists())
    					bitmap = BitmapFactory.decodeStream(new FileInputStream(
    							file));
    			} catch (Exception e) {
    				e.printStackTrace();
    				bitmap = null;
    			}
    		}
    		return bitmap;
    	}
    
    	/**
    	 * 将下载好的图片存放到文件中
    	 * 
    	 * @param path
    	 *            图片路径
    	 * @param mActivity
    	 * @param imageName
    	 *            图片名字
    	 * @param bitmap
    	 *            图片
    	 * @return
    	 */
    	private boolean setBitmapToFile(String path, Context mActivity,
    			String imageName, Bitmap bitmap) {
    		File file = null;
    		String real_path = "";
    		try {
    			if (ImageDownloaderUtil.getInstance().hasSDCard()) {
    				real_path = ImageDownloaderUtil.getInstance().getExtPath()
    						+ (path != null && path.startsWith("/") ? path : "/"
    								+ path);
    			} else {
    				real_path = ImageDownloaderUtil.getInstance().getPackagePath(
    						mActivity)
    						+ (path != null && path.startsWith("/") ? path : "/"
    								+ path);
    			}
    			file = new File(real_path, imageName);
    			if (!file.exists()) {
    				File file2 = new File(real_path + "/");
    				file2.mkdirs();
    			}
    
    			file.createNewFile();
    			FileOutputStream fos = null;
    			if (ImageDownloaderUtil.getInstance().hasSDCard()) {
    				fos = new FileOutputStream(file);
    			} else {
    				fos = mActivity.openFileOutput(imageName, Context.MODE_PRIVATE);
    			}
    
    			if (imageName != null
    					&& (imageName.contains(".png") || imageName
    							.contains(".PNG"))) {
    				bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos);
    			} else {
    				bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
    			}
    			fos.flush();
    			if (fos != null) {
    				fos.close();
    			}
    			return true;
    		} catch (Exception e) {
    			e.printStackTrace();
    			return false;
    		}
    	}
    
    	/**
    	 * 辅助方法,一般不调用
    	 * 
    	 * @param path
    	 * @param mActivity
    	 * @param imageName
    	 */
    	private void removeBitmapFromFile(String path, Context mActivity,
    			String imageName) {
    		File file = null;
    		String real_path = "";
    		try {
    			if (ImageDownloaderUtil.getInstance().hasSDCard()) {
    				real_path = ImageDownloaderUtil.getInstance().getExtPath()
    						+ (path != null && path.startsWith("/") ? path : "/"
    								+ path);
    			} else {
    				real_path = ImageDownloaderUtil.getInstance().getPackagePath(
    						mActivity)
    						+ (path != null && path.startsWith("/") ? path : "/"
    								+ path);
    			}
    			file = new File(real_path, imageName);
    			if (file != null)
    				file.delete();
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	/**
    	 * 异步下载图片的方法
    	 * 
    	 * @author
    	 * 
    	 */
    	private class MyAsyncTask extends AsyncTask {
    		private Context context;
    		private ImageView mImageView;
    		private String url;
    		private OnImageDownloadListener downloadListener;
    		private String path;
    
    		public MyAsyncTask(Context context, String url, ImageView mImageView,
    				String path, OnImageDownloadListener downloadListener) {
    			this.context = context;
    			this.url = url;
    			this.mImageView = mImageView;
    			this.path = path;
    			this.downloadListener = downloadListener;
    		}
    
    		@Override
    		protected Bitmap doInBackground(String... params) {
    			Bitmap bm = null;
    			if (url != null) {
    				try {
    					URL urlObj = new URL(url);
    
    					HttpURLConnection httpConn = (HttpURLConnection) urlObj
    							.openConnection();
    					httpConn.setDoInput(true);
    					httpConn.setRequestMethod("GET");
    					httpConn.connect();
    					if (httpConn.getResponseCode() == 200) {
    						InputStream is = httpConn.getInputStream();
    						bm = BitmapFactory.decodeStream(is);
    					}
    					String imageName = ImageDownloaderUtil.getInstance()
    							.getImageName(url);
    					if (!setBitmapToFile(path, context, imageName, bm)) {
    						removeBitmapFromFile(path, context, imageName);
    					}
    					// 放入强缓存
    					lruCache.put(url, bm);
    					Log.i(TAG, "==放入强缓存ok");
    				} catch (Exception e) {
    					e.printStackTrace();
    				}
    			}
    			return bm;
    		}
    
    		@Override
    		protected void onPostExecute(Bitmap result) {
    			// 回调设置图片
    			if (downloadListener != null) {
    				downloadListener.onImageDownload(result, url);
    				// 该url对应的task已经下载完成,从map中将其删除
    				removeTaskFromMap(url);
    			}
    			super.onPostExecute(result);
    		}
    	}
    
    	public interface OnImageDownloadListener {
    		void onImageDownload(Bitmap bitmap, String imgUrl);
    	}
    
    	// 定义强引用缓存
    	class MyLruCache extends LruCache {
    		public MyLruCache(int maxSize) {
    			super(maxSize);
    		}
    
    		@Override
    		protected int sizeOf(String key, Bitmap value) {
    			// return value.getHeight() * value.getWidth() * 4;
    			// Bitmap图片的一个像素是4个字节
    			return value.getByteCount();
    		}
    
    		@Override
    		protected void entryRemoved(boolean evicted, String key,
    				Bitmap oldValue, Bitmap newValue) {
    
    			if (evicted) {
    				SoftReference softReference = new SoftReference(
    						oldValue);
    				softCaches.put(key, softReference);
    			}
    		}
    	}
    
    	static class ImageDownloaderUtil {
    		private static ImageDownloaderUtil util;
    
    		private ImageDownloaderUtil() {
    		}
    
    		public static ImageDownloaderUtil getInstance() {
    			if (util == null) {
    				util = new ImageDownloaderUtil();
    			}
    			return util;
    		}
    
    		/**
    		 * 判断是否有sdcard
    		 * 
    		 * @return
    		 */
    		public boolean hasSDCard() {
    			boolean b = false;
    			if (Environment.MEDIA_MOUNTED.equals(Environment
    					.getExternalStorageState())) {
    				b = true;
    			}
    			return b;
    		}
    
    		/**
    		 * 得到sdcard路径
    		 * 
    		 * @return
    		 */
    		public String getExtPath() {
    			String path = "";
    			if (hasSDCard()) {
    				path = Environment.getExternalStorageDirectory()
    						.getAbsolutePath();
    			}
    			return path;
    		}
    
    		/**
    		 * 得到包目录
    		 * 
    		 * @param mActivity
    		 * @return
    		 */
    		public String getPackagePath(Context mActivity) {
    			return mActivity.getFilesDir().toString();
    		}
    
    		/**
    		 * 根据url得到图片名
    		 * 
    		 * @param url
    		 * @return
    		 */
    		public String getImageName(String url) {
    			String imageName = "";
    			if (url != null) {
    				imageName = url.substring(url.lastIndexOf("/") + 1);
    			}
    			return imageName;
    		}
    	}
    }
    

    看几张效果图

    初始的加载页面,此时从网络加载的图片还没加载完全

    从网络加载的图片加载完全显示出来

    我们看一下图片加载的日志文件以及第二次启动程序是图片加载的日志

    这个例子可以很好的练习网络,异步加载,Handler,looper,IO操作,SDCard操作,缓存技术等相关知识点

    我是菜鸟,如有不妥之处,多谢指出。。。。。