zoukankan      html  css  js  c++  java
  • Android中图片的三级缓存策略

    一、简单介绍

    如今的Android应用程序中。不可避免的都会使用到图片。假设每次载入图片的时候都要从网络又一次拉取,这样不但非常耗费用户的流量。并且图片载入的也会非常慢,用户体验非常不好。

    所以一个应用的图片缓存策略是非常重要的。

    通常情况下。Android应用程序中图片的缓存策略採用“内存-本地-网络”三级缓存策略,首先应用程序訪问网络拉取图片,分别将载入的图片保存在本地SD卡中和内存中。当程序再一次须要载入图片的时候,先推断内存中是否有缓存。有则直接从内存中拉取,否则查看本地SD卡中是否有缓存。SD卡中假设存在缓存,则图片从SD卡中拉取,否则从网络载入图片。依据这三级缓存机制。能够让我们的应用程序在载入图片的时候做到游刃有余,有效的避免内存溢出。

    PS:当然如今处理网络图片的时候,一般人都会选择XUtils中的BitmapUtil,它已经将网络缓存处理的相当好了,使用起来非常方便--本人就一直在用。

    仿照BitMapUtil的实现思路,定制一个自己的图片载入工具,来理解一下三级缓存的策略,希望对自己会有一个提升。

    二、网络缓存

    网络拉取图片严格来讲不能称之为缓存,实质上就是下载url相应的图片。我们这里姑且把它看作是缓存的一种。仿照BitmapUtil中的display方法,我自己定制的CustomBitmapUtils也定义这种方法,依据传入的url,将图片设置到ivPic控件上。

    public void display(ImageView ivPic, String url) {
    
    }
    定义网络缓存的工具类,在訪问网络的时候,我使用了AsyncTask来实现。在AsyncTask的doInBackGround方法里下载图片。然后将 图片设置给ivPic控件,AsyncTask有三个泛型,其中第一个泛型是运行异步任务的时候,通过execute传过来的參数。第二个泛型是更新的进度。第三个泛型是异步任务运行完毕之后,返回来的结果,我们这里返回一个Bitmap。详细的下载实现代码例如以下:

    <pre name="code" class="java">/**
     * 网络缓存的工具类
     * 
     * @author ZHY
     * 
     */
    public class NetCacheUtils {
    
    	private LocalCacheUtils localCacheUtils;
    	private MemoryCacheUtils memoryCacheUtils;
    
    	public NetCacheUtils() {
    		localCacheUtils = new LocalCacheUtils();
    		memoryCacheUtils = new MemoryCacheUtils();
    	}
    
    	/**
    	 * 从网络下载图片
    	 * 
    	 * @param ivPic
    	 * @param url
    	 */
    	public void getBitmapFromNet(ImageView ivPic, String url) {
    		// 訪问网络的操作一定要在子线程中进行,採用异步任务实现
    		MyAsyncTask task = new MyAsyncTask();
    		task.execute(ivPic, url);
    
    	}
    
    	/**
    	 * 第一个泛型--异步任务运行的时候。通过execute传过来的參数; 第二个泛型--更新进度。 第三个泛型--异步任务运行以后返回的结果
    	 * 
    	 * @author ZHY
    	 * 
    	 */
    	private class MyAsyncTask extends AsyncTask<Object, Void, Bitmap> {
    
    		private ImageView ivPic;
    		private String url;
    
    		// 耗时任务运行之前 --主线程
    		@Override
    		protected void onPreExecute() {
    			super.onPreExecute();
    		}
    
    		// 后台运行的任务
    		@Override
    		protected Bitmap doInBackground(Object... params) {
    			// 运行异步任务的时候,将URL传过来
    			ivPic = (ImageView) params[0];
    			url = (String) params[1];
    			Bitmap bitmap = downloadBitmap(url);
    			// 为了保证ImageView控件和URL一一相应。给ImageView设定一个标记
    			ivPic.setTag(url);// 关联ivPic和URL
    
    			return bitmap;
    		}
    
    		// 更新进度 --主线程
    		@Override
    		protected void onProgressUpdate(Void... values) {
    			super.onProgressUpdate(values);
    		}
    
    		// 耗时任务运行之后--主线程
    		@Override
    		protected void onPostExecute(Bitmap result) {
    			String mCurrentUrl = (String) ivPic.getTag();
    			if (url.equals(mCurrentUrl)) {
    				ivPic.setImageBitmap(result);
    				System.out.println("从网络获取图片");
    				// 从网络载入完之后。将图片保存到本地SD卡一份,保存到内存中一份
    				localCacheUtils.setBitmap2Local(url, result);
    				// 从网络载入完之后。将图片保存到本地SD卡一份,保存到内存中一份
    				memoryCacheUtils.setBitmap2Memory(url, result);
    
    			}
    		}
    	}
    
    	/**
    	 * 下载网络图片
    	 * 
    	 * @param url
    	 * @return
    	 */
    	private Bitmap downloadBitmap(String url) {
    		HttpURLConnection conn = null;
    		try {
    			URL mURL = new URL(url);
    			// 打开HttpURLConnection连接
    			conn = (HttpURLConnection) mURL.openConnection();
    			// 设置參数
    			conn.setConnectTimeout(5000);
    			conn.setReadTimeout(5000);
    			conn.setRequestMethod("GET");
    			// 开启连接
    			conn.connect();
    
    			// 获得响应码
    			int code = conn.getResponseCode();
    			if (code == 200) {
    				// 相应成功,获得网络返回来的输入流
    				InputStream is = conn.getInputStream();
    
    				// 图片的输入流获取成功之后,设置图片的压缩參数,将图片进行压缩
    				BitmapFactory.Options options = new BitmapFactory.Options();
    				options.inSampleSize = 2;// 将图片的宽高都压缩为原来的一半,在开发中此參数须要依据图片展示的大小来确定,否则可能展示的不正常
    				options.inPreferredConfig = Bitmap.Config.RGB_565;// 这个压缩的最小
    
    				// Bitmap bitmap = BitmapFactory.decodeStream(is);
    				Bitmap bitmap = BitmapFactory.decodeStream(is, null, options);// 经过压缩的图片
    
    				return bitmap;
    			}
    
    		} catch (Exception e) {
    			e.printStackTrace();
    		} finally {
    			// 断开连接
    			conn.disconnect();
    		}
    
    		return null;
    	}
    }
    </pre><p></p><pre>

    三、本地缓存

    从网络载入完图片之后,将图片保存到本地SD卡中。在载入图片的时候。推断一下SD卡中是否有图片缓存,假设有。就直接从SD卡载入图片。本地缓存的工具类中有两个公共的方法。各自是向本地SD卡设置网络图片,获取SD卡中的图片。

    设置图片的时候採用键值对的形式进行存储,将图片的url作为键,作为文件的名字。图片的Bitmap作位值来保存。

    由于url含有特殊字符,不能直接作为图片的名字来存储。故採用url的MD5值作为文件的名字。

    /**
     * 本地缓存
     * 
     * @author ZHY
     * 
     */
    public class LocalCacheUtils {
    	/**
    	 * 文件保存的路径
    	 */
    	public static final String FILE_PATH = Environment
    			.getExternalStorageDirectory().getAbsolutePath() + "/cache/pics";
    
    	/**
    	 * 从本地SD卡获取网络图片,key是url的MD5值
    	 * 
    	 * @param url
    	 * @return
    	 */
    	public Bitmap getBitmapFromLocal(String url) {
    		try {
    			String fileName = MD5Encoder.encode(url);
    			File file = new File(FILE_PATH, fileName);
    			if (file.exists()) {
    				Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(
    						file));
    				return bitmap;
    			}
    
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    
    		return null;
    
    	}
    
    	/**
    	 * 向本地SD卡写网络图片
    	 * 
    	 * @param url
    	 * @param bitmap
    	 */
    	public void setBitmap2Local(String url, Bitmap bitmap) {
    		try {
    			// 文件的名字
    			String fileName = MD5Encoder.encode(url);
    			// 创建文件流,指向该路径。文件名称叫做fileName
    			File file = new File(FILE_PATH, fileName);
    			// file事实上是图片,它的父级File是目录,推断一下目录是否存在,假设不存在,创建目录
    			File fileParent = file.getParentFile();
    			if (!fileParent.exists()) {
    				// 目录不存在
    				fileParent.mkdirs();// 创建目录
    			}
    			// 将图片保存到本地
    			bitmap.compress(CompressFormat.JPEG, 100,
    					new FileOutputStream(file));
    
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    }

    四、内存缓存

    内存缓存说白了就是在内存中保存一份图片集合,首先会想到HashMap这样的键值对的形式来进行保存。以url作为key,bitmap作为value。可是在Java中这样的默认的new对象的方式是强引用。JVM在进行垃圾回收的时候是不会回收强引用的,所以假设载入的图片过多的话,map会越来越大,非常easy出现OOM异常。在Android2.3之前,还能够通过软引用或者弱引用来解决。可是Android2.3之后,Google官方便不再推荐软引用了,Google推荐我们使用LruCache。

    在过去,我们常常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。

    可是如今已经不再推荐使用这样的方式了。由于从 Android 2.3 (API Level 9)開始。垃圾回收器会更倾向于回收持有软引用或弱引用的对象。这让软引用和弱引用变得不再可靠。

    另外,Android 3.0 (API Level 11)中。图片的数据会存储在本地的内存其中,因而无法用一种可预见的方式将其释放。这就有潜在的风险造成应用程序的内存溢出并崩溃。

    为了能够选择一个合适的缓存大小给LruCache, 有下面多个因素应该放入考虑范围内。比如:

    • 你的设备能够为每一个应用程序分配多大的内存?Android默认是16M。

    • 设备屏幕上一次最多能显示多少张图片?有多少图片须要进行预载入,由于有可能非常快也会显示在屏幕上?
    • 你的设备的屏幕大小和分辨率各自是多少?一个超高分辨率的设备(比如 Galaxy Nexus) 比起一个较低分辨率的设备(比如 Nexus S)。在持有同样数量图片的时候,须要更大的缓存空间。
    • 图片的尺寸和大小。还有每张图片会占领多少内存空间。
    • 图片被訪问的频率有多高?会不会有一些图片的訪问频率比其他图片要高?假设有的话,你或许应该让一些图片常驻在内存其中。或者使用多个LruCache 对象来区分不同组的图片。

    • 你能维持好数量和质量之间的平衡吗?有些时候。存储多个低像素的图片。而在后台去开线程载入高像素的图片会更加的有效。
    以上是Google对LruCache的描写叙述,事实上LruCache的使用非常简单,跟Map非常相近,仅仅是在创建LruCache对象的时候须要指定它的最大同意内存,一般设置为当前应用程序的最大运行内存的八分之中的一个就可以。

    /**
     * 内存缓存
     * 
     * @author ZHY
     * 
     */
    public class MemoryCacheUtils {
    	/*
    	 * 由于map默认是强引用,全部在JVM进行垃圾回收的时候不会回收map的引用
    	 */
    	// private HashMap<String, Bitmap> map = new HashMap<String, Bitmap>();
    	// 软引用的实例,在内存不够时。垃圾回收器会优先考虑回收
    	// private HashMap<String, SoftReference<Bitmap>> mSoftReferenceMap = new
    	// HashMap<String, SoftReference<Bitmap>>();
    	// LruCache
    	private LruCache<String, Bitmap> lruCache;
    
    	public MemoryCacheUtils() {
    		// lruCache最大同意内存一般为Android系统分给每一个应用程序内存大小(默认Android系统给每一个应用程序分配16兆内存)的八分之中的一个(推荐)
    		// 获得当前应用程序运行的内存大小
    		long mCurrentMemory = Runtime.getRuntime().maxMemory();
    		int maxSize = (int) (mCurrentMemory / 8);
    		// 给LruCache设置最大的内存
    		lruCache = new LruCache<String, Bitmap>(maxSize) {
    			@Override
    			protected int sizeOf(String key, Bitmap value) {
    				// 获取每张图片所占内存的大小
    				// 计算方法是:图片显示的宽度的像素点乘以高度的像素点
    				int byteCount = value.getRowBytes() * value.getHeight();// 获取图片占用内存大小
    				return byteCount;
    			}
    		};
    	}
    
    	/**
    	 * 从内存中读取Bitmap
    	 * 
    	 * @param url
    	 * @return
    	 */
    	public Bitmap getBitmapFromMemory(String url) {
    
    		// Bitmap bitmap = map.get(url);
    		// SoftReference<Bitmap> softReference = mSoftReferenceMap.get(url);
    		// Bitmap bitmap = softReference.get();
    		// 软引用在Android2.3以后就不推荐使用了,Google推荐使用lruCache
    		// LRU--least recently use
    		// 近期最少使用,将内存控制在一定的大小内。超过这个内存大小,就会优先释放近期最少使用的那些东东
    		Bitmap bitmap = lruCache.get(url);
    		return bitmap;
    
    	}
    
    	/**
    	 * 将图片保存到内存中
    	 * 
    	 * @param url
    	 * @param bitmap
    	 */
    	public void setBitmap2Memory(String url, Bitmap bitmap) {
    		// 向内存中设置,key,value的形式,首先想到HashMap
    		// map.put(url, bitmap);
    		// 保存软引用到map中
    		// SoftReference<Bitmap> mSoftReference = new
    		// SoftReference<Bitmap>(bitmap);
    		// mSoftReferenceMap.put(url, mSoftReference);
    		lruCache.put(url, bitmap);
    	}
    
    }
    好了。如今三级缓存策略封装完毕。接下来定制我们自己的BitmapUtils

    /**
     * 自己定义的载入图片的工具类,相似于Xutils中的BitmapUtil,在实际使用中,一般使用BitmapUtil。为了理解三级缓存。
     * 这里模拟BitmapUtil自己定义了CustomBitmapUtil
     * 
     * @author ZHY
     * 
     */
    public class CustomBitmapUtils {
    
    	private Bitmap bitmap;
    
    	private NetCacheUtils netCacheUtils;
    	private LocalCacheUtils localCacheUtils;
    	private MemoryCacheUtils memoryCacheUtils;
    
    	public CustomBitmapUtils() {
    		netCacheUtils = new NetCacheUtils();
    		localCacheUtils = new LocalCacheUtils();
    		memoryCacheUtils = new MemoryCacheUtils();
    	}
    
    	/**
    	 * 载入图片,将当前URL相应的图片显示到ivPic的控件上
    	 * 
    	 * @param ivPic
    	 *            ImageView控件
    	 * @param url
    	 *            图片的地址
    	 */
    	public void display(ImageView ivPic, String url) {
    		// 设置默认显示的图片
    		ivPic.setImageResource(R.drawable.ic_launcher);
    
    		// 1、内存缓存
    		bitmap = memoryCacheUtils.getBitmapFromMemory(url);
    		if (bitmap != null) {
    			ivPic.setImageBitmap(bitmap);
    			System.out.println("从内存缓存中载入图片");
    			return;
    		}
    		// 2、本地磁盘缓存
    		bitmap = localCacheUtils.getBitmapFromLocal(url);
    		if (bitmap != null) {
    			ivPic.setImageBitmap(bitmap);
    			System.out.println("从本地SD卡载入的图片");
    			memoryCacheUtils.setBitmap2Memory(url, bitmap);// 将图片保存到内存
    			return;
    		}
    		// 3、网络缓存
    		netCacheUtils.getBitmapFromNet(ivPic, url);
    		/*
    		 * 从网络获取图片之后。将图片保存到手机SD卡中,在进行图片展示的时候,优先从SD卡中读取缓存,key是图片的URL的MD5值。
    		 * value是保存的图片bitmap
    		 */
    	}
    
    }
    在mainActivity中使用ListView载入网络图片

    /**
     * Android中三级缓存--网络缓存-本地缓存-内存缓存
     * 
     * @author ZHY
     * 
     */
    public class MainActivity extends Activity {
    
    	private ListView list;
    	private Button btn;
    	private CustomBitmapUtils utils;
    
    	private static final String BASE_URL = "http://192.168.0.148:8080/pics";
    	// 初始化一些网络图片
    	String[] urls = { BASE_URL + "/1.jpg", BASE_URL + "/2.jpg",
    			BASE_URL + "/3.jpg", BASE_URL + "/4.jpg", BASE_URL + "/5.jpg",
    			BASE_URL + "/6.jpg", BASE_URL + "/7.jpg", BASE_URL + "/8.jpg",
    			BASE_URL + "/9.jpg", BASE_URL + "/10.jpg", BASE_URL + "/11.jpg",
    			BASE_URL + "/12.jpg", BASE_URL + "/13.jpg", BASE_URL + "/14.jpg",
    			BASE_URL + "/15.jpg", BASE_URL + "/16.jpg", BASE_URL + "/17.jpg",
    			BASE_URL + "/18.jpg", BASE_URL + "/19.jpg", BASE_URL + "/20.jpg",
    			BASE_URL + "/21.jpg", BASE_URL + "/22.jpg", BASE_URL + "/23.jpg",
    			BASE_URL + "/24.jpg", BASE_URL + "/25.jpg", BASE_URL + "/26.jpg",
    			BASE_URL + "/27.jpg", BASE_URL + "/28.jpg", BASE_URL + "/29.jpg",
    			BASE_URL + "/30.jpg" };
    
    	@Override
    	protected void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    		list = (ListView) findViewById(R.id.list);
    		btn = (Button) findViewById(R.id.btn_load);
    		utils = new CustomBitmapUtils();
    
    		// 载入网络图片
    		btn.setOnClickListener(new OnClickListener() {
    			@Override
    			public void onClick(View v) {
    				MyAdapter adapter = new MyAdapter();
    				list.setAdapter(adapter);
    			}
    		});
    	}
    
    	class MyAdapter extends BaseAdapter {
    
    		@Override
    		public int getCount() {
    			return urls.length;
    		}
    
    		@Override
    		public String getItem(int position) {
    			return urls[position];
    		}
    
    		@Override
    		public long getItemId(int position) {
    			return position;
    		}
    
    		@Override
    		public View getView(int position, View convertView, ViewGroup parent) {
    			ViewHolder holder;
    			if (convertView == null) {
    				convertView = View.inflate(MainActivity.this,
    						R.layout.item_list, null);
    				holder = new ViewHolder();
    				holder.ivPic = (ImageView) convertView.findViewById(R.id.iv);
    				convertView.setTag(holder);
    			} else {
    				holder = (ViewHolder) convertView.getTag();
    			}
    			utils.display(holder.ivPic, urls[position]);
    			return convertView;
    		}
    
    		class ViewHolder {
    			ImageView ivPic;
    		}
    	}
    }
    运行的结果例如以下:

    程序第一次运行。日志打印例如以下



    之后将图片缓存在SD卡中,从本地载入图片


    然后将图片缓存到内存,从内存载入图片


    OK,到眼下为止。Android中图片的三级缓存原理就都介绍完了,我自己本人受益匪浅,希望能够帮助到须要的朋友。须要源代码的请点击例如以下链接进行下载。

    源代码下载


  • 相关阅读:
    python excel导入到数据库
    ubuntu14.04修改mysql默认编码
    python 向MySQL里插入中文数据
    hbase框架原理
    hive框架原理
    Hadoop的MapReduce模型基本原理
    机器学习模型效果评价
    spark架构原理
    Hadoop架构原理
    特征工程
  • 原文地址:https://www.cnblogs.com/zhchoutai/p/8423214.html
Copyright © 2011-2022 走看看