Bitmap三级缓存
一、为什么Bitmap需要三级缓存?
没有缓存的弊端 :费流量, 加载速度慢
加入缓存的优点: 省流量,支持 离线浏览
二、原理
三、思路
- 从内存获取图片, 如果存在, 则显示; 如果不存在, 则从SD卡中获取图片
- 从SD卡中获取图片, 如果文件中存在, 显示, 并且添加到内存中; 否则开启网络下载图片
- 从网络下载图片, 如果下载成功, 则添加到缓存中, 存入SD卡, 显示图片
四、撸代码
1、首先需要添加网络权限和读写SD卡的权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
2、封装操作内存的工具类:提供从内存中读写的方法,内存不能持久保存,可能过一会就会被回收掉
/**
* Lrucache存储工具类
* /
public class LruUtils {
//TODO 实例化LruCache对象
private LruCache<String,Bitmap> lruCache;
//获得手机的最大内存
private long max=Runtime.getRuntime().maxMemory();
public LruUtils(){
lruCache=new LruCache<String,Bitmap>((int)max/8){
//给内存大小,一般是最大内存的1/8
//重写该方法返回每个对象的大小
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount();
}
};
}
//TODO 读图片
public Bitmap getBitmap(String key){
return lruCache.get(key);
}
//TODO 存图片
public void setBitmap(String key,Bitmap bitmap){
lruCache.put(key,bitmap);
}
}
3、封装操作SD卡的工具类:提供从SD卡中读写的方法
/**
* 读图片和写图片的工具类
* /
public class SDUtils {
//TODO 存图片:bitmap.compress()
public static void setBitmap(String name,Bitmap bitmap){
//获取路径存储图片
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
File file=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File file1=new File(file,name);
//存储图片:bitmap对象---->SD卡
try {
//参数一 图片的格式 参数二 图片质量 0-100 参数三:输出流
bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(file1));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
//TODO 读图片:BitmapFactory.decodeFile()
public static Bitmap getBitmap(String name){
//获取路径存储图片
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
File file=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File file1=new File(file,name);
//读取图片:SD卡-----Bitmap
return BitmapFactory.decodeFile(file1.getAbsolutePath());
}
return null;
}
}
4、封装网络下载的工具类:提供下载图片的方法,这里使用了AsyncTask异步来处理网络数据
/*
* 网络获取工具类
* /
public class NetUtils {
//TODO 获取网络图片
public static Bitmap getBitmap(String url) throws ExecutionException, InterruptedException {
return new MyTask().execute(url).get();//get方法获取执行完毕返回的结果Bitmap对象
}
static class MyTask extends AsyncTask<String,String,Bitmap>{
@Override
protected Bitmap doInBackground(String... strings) {
String imageUrl = strings[0];
HttpURLConnection conn = null;
try {
URL url = new URL(imageUrl);
conn = (HttpURLConnection) url.openConnection(); // 打开连接
conn.setReadTimeout(5000); // 设置读取超时时间
conn.setConnectTimeout(5000); // 设置连接超时时间
conn.setRequestMethod("GET"); // 设置请求方式
if (conn.getResponseCode() == 200) {
InputStream is = conn.getInputStream(); // 获取流数据
Bitmap bitmap = BitmapFactory.decodeStream(is); // 将流数据转成Bitmap对象
return bitmap;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect(); // 断开连接
}
}
return null;
}
}
}
4、使用三个工具类完成Bitmap的三级缓存
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
private LruUtils lruUtils= new LruUtils();//TODO 注意:同一个内存对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView=findViewById(R.id.image);
}
public void click(View view) throws ExecutionException, InterruptedException {
//TODO 1:先从运行内存中读取
Bitmap bitmap=lruUtils.getBitmap("宋定行");
if(bitmap!=null){//有图片
imageView.setImageBitmap(bitmap);
Toast.makeText(this, "图片来自内存", Toast.LENGTH_SHORT).show();
}else{
//TODO 2:若内存没有去SD卡
bitmap=SDUtils.getBitmap("宋定行.jpg");
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
Toast.makeText(this, "图片从SD卡来de", Toast.LENGTH_SHORT).show();
//向内存中存储一下
lruUtils.setBitmap("宋定行",bitmap);
}else{
//TODO 3:运行内存和SD卡都没有图片,接下来网络获取
bitmap=NetUtils.getBitmap("http://upload.cbg.cn/2015/1126/1448506973451.jpg");
if(bitmap!=null){
imageView.setImageBitmap(bitmap);
Toast.makeText(this, "图片从网络获取", Toast.LENGTH_SHORT).show();
//将图片再放到SD卡和内存中
SDUtils.setBitmap("宋定行.jpg",bitmap);
lruUtils.setBitmap("宋定行",bitmap);
}else{
Toast.makeText(this, "穷玩意检查下你的网吧", Toast.LENGTH_SHORT).show();
}
}
}
}
}
Bitmap二次采样
一.为什么要对Bitmap进行二次采样
不知道大家在开发App的过程中有没有遇到过类似于图片墙这样的功能?在做图片墙的时候你有没有遇到过0OM异常呢?遇到了又是怎么解决的?
再比如我现在有一张100M大的图片,我想把这张图片用一个ImageView显示出来,那么你的ImageView能够显示出来这张图片吗?
上面我们说的这两种情况其实都涉及到图片加载时内存溢出的问题,内存溢出可能发生在加载- -张大图的时候,也有可能发生在加载多张普通小图的时候,如果我们不对图片做二次采样,那么0OM就是一把悬在头上的剑,随时可能会掉下。所以一定要对图片进行二次采样。事实上,我在手机上显示一张分辨率特别大的图片和显示一张分辨率小的图片(不要小的太离谱即可),对用户的视觉体验来说,并不会有多大变化,但是对我们手机的内存来说,影响却是非常巨大的。
总而言之,二次采样就是为了避免图片加载时的0OM异常。
二.哪二次采样
既然是二次采样,那当然要分为两步了,下面我们来说说每次采样的主要工作:
1、第一次采样:
第一次采样我主要是想要获得图片的压缩比例,假如说我有一张图片是200200,那么我想把这张图片的缩略图显示在一个5050的ImageView上,那我的压缩比例应该为4,那么这个4应该怎么样来获得呢?这就是我们第一步的操作了,我先加载图片的边界到内存中,这个加载操作并不会耗费多少内存,加载到内存之后,我就可以获得这张图片的宽高参数,然后根据图片的宽高,再结合控件的宽高计算出缩放比例。
2、第二次采样
在第一次采样的基础上,我来进行二次采样。二次采样的时候,我把第一次采样后算出来的结果作为一个参数传递给第BitmapFactory,这样在加载图片的时候系统就不会将整张图片加载进来了,而是只会加载该图片的一张缩略图进来,这样不仅提高了加载速率,而且也极大的节省了内存,而且对于用户来说,他也不会有视觉上的差异。
三、再来撸代码
1、第一次采样:首先获得缩放比例 ,是2的幂次,然后根据缩放比例进行压缩
public class Main5Activity extends AppCompatActivity {
Button bt;
ImageView imageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt=findViewById(R.id.bt);
imageView=findViewById(R.id.iv);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//网络获取一张大图,进行二次采样之后再放到ImageView
//https://cdn.duitang.com/uploads/item/201211/24/20121124230042_Bfhim.jpeg
try {
Bitmap bitmap = new MyTask().execute("https://cdn.duitang.com/uploads/item/201211/24/20121124230042_Bfhim.jpeg").get();
imageView.setImageBitmap(bitmap);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
});
}
class MyTask extends AsyncTask<String,Object,Bitmap>{
@Override
protected Bitmap doInBackground(String... strings) {
try {
URL url = new URL(strings[0]);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
if(urlConnection.getResponseCode()==200){
InputStream inputStream = urlConnection.getInputStream();
//将inputStream流存储起来
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] bytes = new byte[1024];
int len=0;
while((len=inputStream.read(bytes))!=-1){
byteArrayOutputStream.write(bytes,0,len);
}
//桶:网络的图片都放在数组里面了
byte[] data = byteArrayOutputStream.toByteArray();
//TODO 1:第一次采样:只采边框 计算压缩比例
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;//设置只采边框
BitmapFactory.decodeByteArray(data,0,data.length,options);//采样
int outWidth = options.outWidth;//获得原图的宽
int outHeight = options.outHeight;//获得原图的高
//计算缩放比例
int size=1;
while(outWidth/size>100||outHeight/size>100){
size*=2;
}
//TODO 2:第二次采样:按照比例才像素
options.inJustDecodeBounds=false;//设置只采边框为fasle
options.inSampleSize=size;//设置缩放比例
Bitmap bitmap=BitmapFactory.decodeByteArray(data,0,data.length,options);//采样
return bitmap;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
}
2、第二次采样:对第一次采样后的图片进行质量压缩
Bitmap.compress(CompressFormat format, int quality, OutputStream stream)
参数一:Bitmap被压缩成的图片格式
参数二:压缩的质量控制,范围0~100
参数三:输出流.
//判断SD卡是否挂载
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
//获得SD卡的跟路径
File file=Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File file1=new File(file,name);
//存储图片:bitmap对象---->SD卡
try {
//参数一 图片的格式 参数二 图片质量 0-100 参数三:输出流
bitmap.compress(Bitmap.CompressFormat.JPEG,50,new FileOutputStream(file1));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}