概述:
网上加载图片的框架已经有很多,例如:
Picasso所能实现的功能,Glide都能做,无非是所需的设置不同。但是Picasso体积比起Glide小太多如果项目中网络请求本身用的就是okhttp或者retrofit(本质还是okhttp),那么建议用Picasso,体积会小很多(Square全家桶的干活)。Glide的好处是大型的图片流,比如gif、Video,如果你们是做美拍、爱拍这种视频类应用,建议使用。此外还有okhpptutils、smartimageview、imageloader、xutils等。
这些框架都已经封装得很好,很成熟了,这里只是自己的一个demo加深对图片三级缓存的理解。
加载网络图片:
/** * 从网络中获取图片 */ public void getNetBitmap(ImageView imageView , String url){ //异步加载图片 MyAsyncTask task = new MyAsyncTask(); task.execute(imageView, url); } /** * 第一个泛型--异步任务执行的时候,通过execute传过来的参数; 第二个泛型--更新进度; 第三个泛型--异步任务执行以后返回的结果 */ private class MyAsyncTask extends AsyncTask<Object , Void , Bitmap> { private HttpURLConnection connection; private ImageView imgeview; private String url; // 耗时任务执行之前 --主线程 @Override protected void onPreExecute() { super.onPreExecute(); } /** * 耗时操作 --- 子线程 */ @Override protected Bitmap doInBackground(Object... params) { imgeview = (ImageView) params[0]; url = (String) params[1]; try { URL mUrl = new URL(url); connection = (HttpURLConnection) mUrl.openConnection(); connection.setConnectTimeout(5000);//连接超时 connection.setReadTimeout(5000);//读取超时 connection.setRequestMethod("GET");//请求方法 connection.connect();//开启连接 int responseCode = connection.getResponseCode();//获取响应码 if(responseCode == 200){ InputStream inputStream = connection.getInputStream();//响应成功,获取网络反应回来的输入流 //获取输入流之后,开始设置图片压缩参数,将图片进行压缩 BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 2;// 将图片的宽高都压缩为原来的一半,在开发中此参数需要根据图片展示的大小来确定,否则可能展示的不正常 options.inPreferredConfig = Bitmap.Config.RGB_565;// 这个压缩的最小 Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);// 经过压缩的图片 imgeview.setTag(url);// 为了保证ImageView控件和URL一一对应,给ImageView设定一个标记 return bitmap; } }catch (Exception e){ e.printStackTrace(); }finally { connection.disconnect();//断开连接 } return null; } /** * 更新进度 --- 主线程 */ @Override protected void onProgressUpdate(Void... values) { super.onProgressUpdate(values); } /** * 耗时操作执行之后----主线程 */ @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); String mUrl = (String) imgeview.getTag(); if(mUrl != null && url.equals(mUrl)){ if(bitmap != null){ imgeview.setImageBitmap(bitmap); // 从网络加载完之后,将图片保存到本地SD卡一份,保存到内存中一份 LocalCacheUtils.setLocalBitmap(url , bitmap); } } }
内存缓存,考虑的问题:
1. 你的设备可以为每个应用程序分配多大的内存?以前Android默认是16M。测试的小米2A是64M
2. 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
3. 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,
需要更大的缓存空间。
4. 图片的尺寸和大小,还有每张图片会占据多少内存空间。
5. 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache
对象来区分不同组的图片。
6. 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
以上是Google对LruCache的描述,其实LruCache的使用非常简单,跟Map非常相近,只是在创建LruCache对象的时候需要指定它的最大允许内存,
一般设置为当前应用程序的最大运行内存的八分之一即可
public MemoryCacheUtils() { // lruCache最大允许内存一般为Android系统分给每个应用程序内存大小的八分之一(推荐) // 获得当前应用程序运行的内存大小 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 */ public Bitmap getBitmapFromMemory(String url) { Bitmap bitmap = lruCache.get(url); LogUtils.e("从内存中获取图片: " + bitmap); return bitmap; } /** * 将图片保存到内存中 */ public void setBitmap2Memory(String url, Bitmap bitmap) { lruCache.put(url, bitmap); }
本地缓存:
设置图片的时候采用键值对的形式进行存储,将图片的url作为键,作为文件的名字,图片的Bitmap作位值来保存。由于url含有特殊字符,不能直接作为图片的名字来存储,故采用url的MD5值作为文件的名字
1、向本地sd卡设置图片
2、获取sd卡中的图片
public static final String FILE_PATH_MIRK = Environment.getExternalStorageDirectory().getAbsolutePath(); /** * 从本地sd中获取图片,key是之前用md5加密的值 */ public static Bitmap getLocalBitmap(String url){ try { String fileName = md5Code(url); File file = new File(FILE_PATH_MIRK, fileName); LogUtils.e("获取的sd卡路径:" + file.getAbsolutePath()); if (file.exists()) { Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream( file)); return bitmap; } } catch (Exception e) { e.printStackTrace(); } return null; } /** * 向本地sd卡设置保存图片 */ public static void setLocalBitmap(String url , Bitmap bitmap){ LogUtils.e("开始保存文件到sd卡"); String fileName = md5Code(url); File f = new File(FILE_PATH_MIRK, fileName); if (f.exists()) { f.delete(); } try { FileOutputStream out = new FileOutputStream(f); bitmap.compress(Bitmap.CompressFormat.PNG, 90, out); out.flush(); out.close(); } catch (Exception e) { e.printStackTrace(); } } /** * 进行简单的md5加密 */ private static String md5Code(String url){ //生成实现指定摘要算法的 MessageDigest 对象。 String bufs = null; try { MessageDigest md = MessageDigest.getInstance("MD5"); //使用指定的字节数组更新摘要。 md.update(url.getBytes()); //通过执行诸如填充之类的最终操作完成哈希计算。 byte b[] = md.digest(); //生成具体的md5密码到buf数组 int i; StringBuffer buf = new StringBuffer(""); for (int offset = 0; offset < b.length; offset++) { i = b[offset]; if (i < 0) i += 256; if (i < 16) buf.append("0"); buf.append(Integer.toHexString(i)); } bufs = buf.toString(); // 32位的加密 LogUtils.e("加密后的字符串:" + bufs); System.out.println("16位: " + buf.toString().substring(8, 24));// 16位的加密,其实就是32位加密后的截取 } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return bufs; }
给指定的ImageView加载图片:
第一个参数是imageview对象,第二个参数是加载的图片url
public void setImageView(ImageView imageView , String url){ //从内存中获取图片 bitmap = memoryCacheUtils.getBitmapFromMemory(url); if(bitmap != null){ imageView.setImageBitmap(bitmap); LogUtils.e("从内存中获取图片: " + bitmap); return; } //从sd卡中获取图片 bitmap = LocalCacheUtils.getLocalBitmap(url); if(bitmap != null){ imageView.setImageBitmap(bitmap); memoryCacheUtils.setBitmap2Memory(url, bitmap); LogUtils.e("从sd卡中获取图片: " + bitmap); return; } //从网络中获取图片 LogUtils.e("从网络中获取图片"); netCacheUtils.getNetBitmap(imageView , url); }