"安卓网络请求图片三级缓存"-带您写一个自定义图片三级缓存.
来源:互联网 发布:androidframework源码 编辑:程序博客网 时间:2024/05/17 07:22
今天跟大家讲解一下图片的三级缓存
开发中我们经常涉及到图片的处理,几乎每个开发者都会碰到这个问题,我们经常在开发工程中使用各种各样的第三方框架做图片缓存,比如最简单的picasso,但是今天我们就要模仿他,并自己写一个轻量版的图片三级缓存工具MyImageTool,好的下面我们就开始步入正题.
所谓三级缓存就是涉及到三个方面
- 1.内存
- 2.本地存储
- 3.网络
原理就是如下方案(给大家提供两个方案,大家人选一个即可.建议第二个):
- 方案一:
- 1.先从内存”读取”照片,有就显示照片,没有就第2步;
- 2.从磁盘”获取”照片,如果有照片,就把图片存到内存,再从内存”读取”显示,没有就第3步;
- 3.从网络”获取”如果有照片,就存到磁盘,然后存到内存,在从内存读取显示,没有就给用户显示一个错误的图片
方案一的流程图如下:
流程图
- 方案二:
- 1.先从内存”读取”照片,有就显示照片,没有就第2步;
- 2.从磁盘”获取”照片,如果有照片就显示,并把图片存到内存,没有就第3步;
- 3.从网络”获取”如果有照片,如果有照片就显示,并存到磁盘,然后存到内存;没有就给用户显示一个错误的图片
方案二的流程图如下:
流程图
两种方案对比,并做出选择
方案一:从内存读取(有就显示)->从磁盘读取(有就存内存,内存再显示)->从网络(有就存磁盘(没有就显示错误图片)->再存内存->再显示)(PS:一些老师为了方便同学理解,用了方案一,但是效率上每次都是存到了内存,再显示,都要跨几层,用方案二更合理(高效,且迅速))方案二:从内存读取(有就显示)->从磁盘读取(有就转换并显示,再存内存)->从网络(有就显示,然后存磁盘->再存内存)->没有显示错误图片其实一般磁盘有,那么肯定都存到了内存,出现三级缓存的目的也是为了开关机(内存的消失),或者清理了所有数据(内置卡程序数据的清空)
代码详情和内部讲解
package goodjobtome.com.lrucechepic;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Handler;import android.support.annotation.NonNull;import android.util.Log;import android.widget.ImageView;import android.widget.Toast;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;import java.util.HashMap;/** * 做成单例,安全 */public class MyImageTool { private static MyImageTool myImageTool = new MyImageTool(); //HashMap当内存. private HashMap<String, Bitmap> mMCeche; private MyImageTool() { mMCeche = new HashMap(); } //1.上下文 static Context mContext; public static MyImageTool with(Context context) { mContext = context; return myImageTool; } //2.路径 public RequestCreator load(String path) { RequestCreator creator = new RequestCreator(path); return creator; } Handler mHandler = new Handler(); class RequestCreator { String mUrl = null; ImageView mIv; RequestCreator(String path) { mUrl = path; } //3.控件 public void into(ImageView iv) { mIv = iv; load(); } private void load() { /*-从内存里读取图片 HashMap-*/ Bitmap bitmapCeche = mMCeche.get(mUrl); if (bitmapCeche != null) { Log.d("RequestCreator", "显示的是内存的图"); mIv.setImageBitmap(bitmapCeche); return; } /*--从磁盘读取"文件"存入磁盘的是文件file-MD5统一名字,在内存中才是图片bitmap-用url当做名字就行,所以从磁盘读取需要转换成图片;--*/ File file = getFileName(); try { if (file.exists()) { Bitmap bitmapDisk = BitmapFactory.decodeStream(new FileInputStream(file)); if (bitmapDisk != null) { Log.d("RequestCreator", "显示的是磁盘的图"); mIv.setImageBitmap(bitmapDisk); //立马且存到内存 saveCeche(bitmapDisk); return; } } } catch (Exception e) { e.printStackTrace(); } /*--从网络获取,开启线程,然后子线程显示即可,再存磁盘,存内存--*/ new Thread(new Runnable() { @Override public void run() { try { HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection(); conn.setConnectTimeout(4 * 1000); conn.setReadTimeout(4 * 1000); InputStream is = conn.getInputStream(); final Bitmap bitmap = BitmapFactory.decodeStream(is); if (bitmap == null) { //没有就显示错误图片 showErrorPic(); return; } //子线程->主线程显示 mHandler.post(new Runnable() { @Override public void run() { Log.d("RequestCreator", "显示的是网络的图"); mIv.setImageBitmap(bitmap); } }); //存到磁盘,存到磁盘的是文件格式 saveDisk(bitmap); //存到内存的是纯bitmap格式. saveCeche(bitmap); conn.disconnect(); is.close(); } catch (IOException e) { //异常显示错误图片 showErrorPic(); e.printStackTrace(); } } }).start(); } //磁盘里的file文件名字 @NonNull private File getFileName() { String packageName = mContext.getPackageName(); File dir = new File("data/data/" + packageName + "/" + "myimage/");//自定义的 if (!dir.exists()) { dir.mkdir();//没有就创建 } String name = MD5Tool.getMD5(mUrl); return new File(dir, name); } //图片保存到磁盘,要弄成文件的 private void saveDisk(Bitmap bitmap) { File fileName = getFileName(); try { //图片变成文件,注意方法 bitmap->file:压缩成文件 FileOutputStream fos = new FileOutputStream(fileName); bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); } catch (FileNotFoundException e) { e.printStackTrace(); } } //图片存到内存的方法 private void saveCeche(Bitmap bitmap) { mMCeche.put(mUrl, bitmap); } //显示错误图片 private void showErrorPic() { mHandler.post(new Runnable() { @Override public void run() { Bitmap error = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.errorpic); Toast.makeText(mContext, "Error", Toast.LENGTH_SHORT).show(); mIv.setImageBitmap(error); } }); } }}
下面到我们精彩的演示过程了
一.演示从网络获取并显示:
1.点开电脑网络上的一张图片
2.,然后复制地址,粘贴到程序的地址栏
3.点击按钮就会出现结果
4.打印的日志:
通过打印的日志,发现我们此次是从网络获取,因为是第一次访问
二.演示从内存获取并显示:
- 1.再次点击请求,或者点返回键回到手机主界面
- 2.再进去,再同样请求
- 3.打印的日志:
通过日志发现,我们此次是从内存读取,因为刚刚网络显示后已经把缓存放到磁盘再到内存了
三.演示从内存存储(相当磁盘)获取并显示:
- 1.点击程序任务键,滑动并关闭程序.
- 2.再进去,再同样请求
- 3.打印的日志:
重点内容
四.显示“获取错误图片”,即没有该图片,证明输入地址错误或者服务器有问题.
- 输入错误的地址
显示效果:
优化和拓展
大家有没有发现,我们这里的内存,我所用的是HashMap,但是大家有没有想过,如果有成千上百张,如果我们都这么存的话,那么就出现大问题了,可能内存就会崩掉,说以内存这一块我们要做出改进,就要用到我们的LruCeche();
LruCeche():全称:Least Recently Used 最近最少使用缓存.他是一个缓存集合**.其实底层还是LinkedHashMap.
特点:
当缓存的图片达到了预先设定的值的时候,那么近期使用次数最少的图片就会被回收掉。
使用方法:
- 1.我们先定义好字节变成kb.
- 2.缓存集合对象创建的时候构造传入大小,并重写方法sizeOf
- 3.返回获取的值的字节大小,或者自定义单位转换.
代码如下:
//改用缓存集合 private static LruCache<String, Bitmap> mMCeche; private MyImageTool2() { //这个是定义大小 //int maxSize = 1024*1024*4; //注意这个地方及其的容易出错,因为我写成了freeMemory()/4+0.5f,导致大半天都集合都无法存入东西,因为两者不一样大了,而用int maxSize = 1024*1024*4;里面出现效果 int maxSize = (int) (Runtime.getRuntime().totalMemory());//四舍五入,用总内存就没错 mMCeche = new LruCache<String,Bitmap>(maxSize){ @Override protected int sizeOf(String key, Bitmap value) { //这个是对应上面的byte,注意单位对应,如果这里想作为M那么上面就写4,这里就写/1024/1024 return value.getByteCount(); } }; }
好的下面给出大家所有的代码
- 1.主界面代码:MainActivity.java
package goodjobtome.com.lrucechepic;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.ImageView;public class MainActivity extends AppCompatActivity { private EditText mAdress; private Button mGo; private ImageView mIv; private ImageView mIv2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private void init() { initView(); initEvent(); } private void initView() { mAdress = (EditText) findViewById(R.id.et); mGo = (Button) findViewById(R.id.bt); mIv = (ImageView) findViewById(R.id.iv); mIv2 = (ImageView) findViewById(R.id.iv2); } private void initEvent() { mGo.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String path = mAdress.getText().toString().trim(); //使用框架 //Picasso.with(MainActivity.this).load(path).into(mIv); UseTimeTool.getInstance().start(); //我们自己做的: 上下文->路径->控件 MyImageTool2.with(MainActivity.this).load(path).into(mIv); UseTimeTool.getInstance().stop(); } }); }}
- 2.清单文件:注册,并添加网络权限即可
- 3.封装更好的控件代码:
package goodjobtome.com.lrucechepic;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Handler;import android.support.annotation.NonNull;import android.util.Log;import android.util.LruCache;import android.widget.ImageView;import android.widget.Toast;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;/** * 做成单例,安全 */public class MyImageTool2 { private static MyImageTool2 myImageTool = new MyImageTool2(); //改用缓存集合 private static LruCache<String, Bitmap> mMCeche; private MyImageTool2() { //这个是定义大小 //int maxSize = 1024*1024*4; //注意这个地方及其的容易出错,因为我写成了freeMemory()/4+0.5f,导致大半天都集合都无法存入东西,因为两者不一样大了,而用int maxSize = 1024*1024*4;里面出现效果 int maxSize = (int) (Runtime.getRuntime().totalMemory());//四舍五入,用总内存就没错 mMCeche = new LruCache<String,Bitmap>(maxSize){ @Override protected int sizeOf(String key, Bitmap value) { //这个是对应上面的byte,注意单位对应,如果这里想作为M那么上面就写4,这里就写/1024/1024 return value.getByteCount(); } }; } //1.上下文 static Context mContext; public static MyImageTool2 with(Context context) { mContext = context; return myImageTool; } //2.路径 public RequestCreator load(String path) { RequestCreator creator = new RequestCreator(path); return creator; } Handler mHandler = new Handler(); class RequestCreator { String mUrl = null; ImageView mIv; RequestCreator(String path) { mUrl = path; } //3.控件 public void into(ImageView iv) { mIv = iv; load(); } private void load() { /*-从内存里读取图片 HashMap-*/ if (loadFromCache()) return; /*--从磁盘读取"文件"存入磁盘的是文件file-MD5统一名字,在内存中才是图片bitmap-用url当做名字就行,所以从磁盘读取需要转换成图片;--*/ if(loadFromDisk()) return; /*--从网络获取,开启线程,然后子线程显示即可,再存磁盘,存内存--*/ loadFromNet(); } //从内存读取 private boolean loadFromCache() { Bitmap bitmapCeche = mMCeche.get(mUrl); if (bitmapCeche != null) { Log.d("RequestCreator", "显示的是内存的图"); mIv.setImageBitmap(bitmapCeche); return true; } return false; } //从磁盘获取 private boolean loadFromDisk() { File file = getFileName(); try { if (file.exists()) { Bitmap bitmapDisk = BitmapFactory.decodeStream(new FileInputStream(file)); if (bitmapDisk != null) { Log.d("RequestCreator", "显示的是磁盘的图"); mIv.setImageBitmap(bitmapDisk); //立马且存到内存 saveCeche(bitmapDisk); return true; } } } catch (Exception e) { e.printStackTrace(); } return false; } //从网络获取,并存如磁盘和内存 private void loadFromNet() { new Thread(new Runnable() { @Override public void run() { try { HttpURLConnection conn = (HttpURLConnection) new URL(mUrl).openConnection(); conn.setConnectTimeout(4 * 1000); conn.setReadTimeout(4 * 1000); InputStream is = conn.getInputStream(); final Bitmap bitmap = BitmapFactory.decodeStream(is); if (bitmap == null) { //没有就显示错误图片 showErrorPic(); return; } //子线程->主线程显示 mHandler.post(new Runnable() { @Override public void run() { Log.d("RequestCreator", "显示的是网络的图"); mIv.setImageBitmap(bitmap); } }); //存到磁盘是file格式,所以内部要处理 saveDisk(bitmap); //存到内存的是纯bitmap格式. saveCeche(bitmap); conn.disconnect(); is.close(); } catch (IOException e) { //异常显示错误图片 showErrorPic(); e.printStackTrace(); } } }).start(); } //磁盘里的file文件名字 @NonNull private File getFileName() { String packageName = mContext.getPackageName(); File dir = new File("data/data/" + packageName + "/" + "myimage/");//自定义的 if (!dir.exists()) { dir.mkdir();//没有就创建 } String name = MD5Tool.getMD5(mUrl); return new File(dir, name); } //图片存到磁盘 private void saveDisk(Bitmap bitmap) { File fileName = getFileName(); //bitmap->file,图片转成文件 try { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(fileName)); } catch (FileNotFoundException e) { e.printStackTrace(); } } //图片存到内存的方法 private void saveCeche(Bitmap bitmap) { mMCeche.put(mUrl, bitmap); System.out.println("putSize---------------------------------------" + mMCeche.size()); } //显示错误图片 private void showErrorPic() { mHandler.post(new Runnable() { @Override public void run() { Bitmap error = BitmapFactory.decodeResource(mContext.getResources(), R.mipmap.errorpic); Toast.makeText(mContext, "Error", Toast.LENGTH_SHORT).show(); mIv.setImageBitmap(error); } }); } }}
至于布局代码,时间工具,MD5工具,错误图片,布局文件,都比较简单,大家自己添加即可.
总结
1.三级缓存:
- 1.内存: HashMap或者LruCache(底层其实是linkedHaskmap)
- 1.注意的是maxSize这个的大小,否则画一天都找不到原因所在.
- 2.存放的是:存bitMap格式的,获取是根据key:ulr获取
2.磁盘:使用的是data/data/包名/myimage自定义文件夹下
- 1.存放的是:存file格式的文件,为了统一姓名用md5加密ulr后的key
- 2.图片转成文件即:bitmap.copress方法进行压缩即可.
- 3.取出的时候用图片工厂解析流即可.
3.网络:获取成功就显示,不成功就显示本地错误提示图片,异常也显示
- 1.存磁盘
- 2.存网络
- "安卓网络请求图片三级缓存"-带您写一个自定义图片三级缓存.
- 安卓的图片三级缓存
- Android网络图片三级缓存
- android自定义图片三级缓存(内存、SD卡、网络)
- 图片的三级缓存
- Android 图片三级缓存
- Android 图片三级缓存
- Android 图片三级缓存
- 三级图片缓存介绍
- 图片的三级缓存
- 图片的三级缓存
- 图片处理,三级缓存
- 三级缓存图片类
- android图片三级缓存
- Android三级图片缓存
- 关于图片三级缓存
- 图片的三级缓存
- Android图片三级缓存
- Glide的使用
- [译]Python中有效的字符串合并方法
- Android动画之---帧动画
- hibernate学习之三----映射关系
- Android ProGuard打包混淆代码
- "安卓网络请求图片三级缓存"-带您写一个自定义图片三级缓存.
- Android---Volley请求 Json解析 xUtils数据存储
- 个人在开发中导jar包与生成jar包的开发的经验
- ORA-09968 unable to lock file
- 51CTO之任意密码存在重置风险(各种组合综合利用) 20160328
- Unity中Detroy之后的引用情况
- 如何才能更短? -------- 20160630
- 问题 System.out.println(versionName);输出结果:System.out: INSTANT_RUN
- 练习:Stringers