安卓缓存那些事情面试,一篇全部搞定
安卓缓存机制
1.网络缓存:服务器缓存数据
2.本地持久化缓存:磁盘缓存,数据库db文件,SP xml文件,ACache文件缓存(https://blog.iprac.cn/blogs/285.html)
3.内存缓存:
4.活动缓存:
其中磁盘缓存和内存缓存都涉及到LruCache算法,下面我们一起来研究LruCache算法
LruCache算法
记住2个关键字:近期最少使用原则/LinkedHashMap
https://blog.csdn.net/zenmela2011/article/details/125191137
手写Bitmap的三级缓存
一.为什么Bitmap三级缓存?
- 没有缓存的弊端 :费流量, 加载速度慢
- 加入缓存的优点: 省流量,支持离线浏览
二.原理
思路:
- 从内存获取图片, 如果存在, 则显示; 如果不存在, 则从SD卡中获取图片
- 从SD卡中获取图片, 如果文件中存在, 显示, 并且添加到内存中; 否则开启网络下载图片
- 从网络下载图片, 如果下载成功, 则添加到缓存中, 存入SD卡, 显示图片
三.代码
(1)添加读写SD卡的权限和网络权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
(2)操作内存工具类:提供从内存中读写的方法,内存不能持久保存,可能过一会就会被回收掉
//Lrucache存储工具类
public class LruUtils {
//TODO 1:实例化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 2:读图片
public Bitmap getBitmap(String key){
return lruCache.get(key);
}
//TODO 3:存图片
public void setBitmap(String key,Bitmap bitmap){
lruCache.put(key,bitmap);
}
}
(3)操作SD卡工具类:提供从SD卡中读写的方法
//TODO 读图片和写图片
public class SDUtils {
//TODO 1:存图片: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 2:读图片: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;
}
}
(3)网络下载工具类:提供下载图片的方法
//网络获取工具类
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的三级缓存
/*
一。三级缓存
1.运行内存:LruUtils(创建实例,存,读)
2.文件SD
3.网络请求
* */
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的二次采样和质量压缩
一.为什么二次采样
二.哪二次采样
三.代码:网络请求图片进行尺寸压缩
第一次:获得缩放比例 ,是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;
}
}
}
四.质量压缩
1.方法介绍
Bitmap.compress(CompressFormat format, int quality, OutputStream stream)
参数一:Bitmap被压缩成的图片格式
参数二:压缩的质量控制,范围0~100
参数三:输出流
2.案例:将一张Bitmap图片采用50%质量压缩到SD卡中
//判断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();
}
}
RecyclerView四级缓存
0.先看视频:
https://www.bilibili.com/video/BV1Td4y1g7w7/?spm_id_from=333.880.my_history.page.click
1.概述
我们知道 RecyclerView 其实是继承 ViewGroup 实现的,也就是说它的最根本的功能是可以往 RecyclerView 中添加子 View。如往 LinearLayout 中添加子 View,子 View 就会横向或纵向展开排列。以 LinearLayout 纵向排列为例,当子 View 过多时,超出屏幕部分的子 View 就显示不出来,这时引入一种 滑动机制 来确保所有子 View 都可以展示 (通过 Scroller/ OverScroller 类辅助实现滑动效果),类似 ScrollerView。当子 View 数量很多时,又会产生另一个新问题 (内存占用大:因为所有子 View 需要同时存在)。但是这种场景下有个特点,即屏幕展示的子 View 有限,大部分子 View 都不可见。所以新增了 View 的复用机制,即通过回收不可见的子 View 并在展示新 View 时进行复用来实现优化。这便是 ListView、RecyclerView 等存在的意义。
RecyclerView 的关联模块:
- LayoutManager:负责 RecyclerView 中,控制 item 的布局方向
- Recycler:负责View的缓存。
- RecyclerView.Adapter:为 RecyclerView 承载数据
- ItemDecoration:为 RecyclerView 添加分割线
- ItemAnimator:控制 RecyclerView 中 item 的动画
2.缓存分类
Recycler 负责管理废弃或被 detached 的 item 视图,以便重复利用。先来看下他的四级缓存:
2.1 Scrap缓存
Scrap 是 RecyclerView 中最轻量的缓存,它不参与滑动时的回收复用,仅作为重新布局时的一种临时缓存。目的是缓存当界面重新布局的前后都出现在屏幕上的ViewHolder,以此省去不必要的重新加载与绑定工作。
- mAttachedScrap:负责保存将会原封不动的ViewHolder。
- mChangedScrap:负责保存位置会发生移动的ViewHolder,注意只是位置发生移动,内容仍原封不动。
public final class Recycler {
// 负责保存将会原封不动的ViewHolder。
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
// 负责保存位置会发生移动的ViewHolder,注意只是位置发生移动,内容仍原封不动。
ArrayList<ViewHolder> mChangedScrap = null;
}
上面两个 Scrap 缓存的实际应用场景如下图所示
2.2 CachedViews 缓存
CacheView 是以 ViewHolder 为单位,负责在 RecyclerView 列表位置产生变动的时候,对刚刚移出屏幕的 View 进行回收复用的缓存列表。
public final class Recycler {
// 缓存刚刚移除屏幕的ViewHolder。
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE; //DEFAULT_CACHE_SIZE=2
int mViewCacheMax = DEFAULT_CACHE_SIZE;
}
上面 CachedViews 缓存的实际应用场景如下图所示:
2.3 ViewCacheExtension 缓存
ViewCacheExtension 是一个由用户自定义的缓存,其定义如下
public abstract static class ViewCacheExtension {
public abstract View getViewForPositionAndType(
@NonNull Recycler recycler, int position, int type);
}
小结:
RecyclerView 调用缓存的顺序, Scrap缓存 -> CacheViews 缓存 -> ViewCacheExtension 缓存 -> RecyclerViewPool 缓存。
ViewCacheExtension 缓存只有取缓存的接口,但是 Recycler 没有对应添加缓存的接口,因此需要自己找场景插入添加缓存的逻辑。
2.4 RecycledViewPool 缓存
在 Srap 和 CacheView 不愿意缓存的时候,都会放入 RecycledViewPool 进行回收。同时,RecycledViewPool 只按照 ViewType 进行区分,只要 ViewType 是相同的,甚至可以在多个 RecyclerView 中进行通用的复用,只要为它们设置同一个RecycledViewPool 就可以了。
public static class RecycledViewPool {
// 默认最多缓存5个。
private static final int DEFAULT_MAX_SCRAP = 5;
SparseArray<ScrapData> mScrap = new SparseArray<>();
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
}
RecycledViewPool 的数据结构如下图所示,可以缓存 viewType 相同的多个 ViewHolder。
3、RecyclerView 与 ListView 的区别
ListView:2级缓存
RecyclerView:4级缓存
4、结合源码分析总结recyclerview的缓存机制
复用机制:
https://www.bilibili.com/video/BV1Yb4y1R7xn?p=2&vd_source=d61c44703fccc74954c692402d0116b8
复用总结:
- 从 mChangedScrap 中获取去获取。
- 根据 position 依次尝试从 mAttachedScrap、隐藏的列表、mCachedViews 中获取。
- 根据 id 依次尝试从 mAttachedScrap、mCachedViews 中获取。
- 尝试从我们自定义的 mViewCacheExtension 中去获取。
- 根据 ViewType 从缓存池里面获取。
- 如果上面都无法获取的话就通过 Adapter.createViewHolder() 来创建ViewHolder。
缓存机制:
缓存总结: