图片的三级缓存基础

Flutter图片加载优化:cached_network_image插件实战指南 本文是Flutter图片加载优化实战指南,重点解析cached_network_image插件的核心优势与使用技巧。通过对比原生Image.network,详细介绍了如何通过三级缓存机制、进度指示器、自定义容器等高级功能,有效解决图片加载卡顿、白屏问题,提升应用流畅度与用户体验。 阅读详情

概述:

现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多。

现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量。在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响。当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略。总之,图片缓存是很重要而且是必须的。

应用实例


比如一个新闻客户端,要在listView中显示新闻、图片等信息,如果没有网络,就会显示以前显示过的新闻。图片。原因:这些数据被缓存到SD卡中,也就是下面说的二级缓存中(一级缓存中的数据 在退出程序时就会被释放的。程序结束,下载的图片对象被销毁,所以说数据不会存储在以及缓存中。)

动态地显示列表中的图片

1.图片缓存优先级:Bitmap优于手机本地的图片文件优于服务器端的图片文件

一级缓存:内存缓存,缓存的是bitmap对象(这些对象在内存如何存储呢?一般而言有两种方式:List和Map,List根据下标来得到对象,Map根据Key得到对象,实际图片存储都是用Map)。

对于绝大多数图片缓存,用的都是LruCatch,(我下面这段代码只是简单的存储在Map中,没有用较复杂的数据结构。)比如这篇博文:https://blog.youkuaiyun.com/lovoo/article/details/51456515

LruCache非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

二级缓存:本地(SD卡)缓存,缓存的是图片文件,/storage/sdcard/Android/data/packageName/files/图片文件名(xxx.jpg)

三级缓存:远程服务器缓存,缓存的是图片文件,远程服务器上的应用中

2.如何使用三级缓存?-----如何根据图片的URL动态显示图片?

String imagePath = http://192.168.10.165:8080//L05_Web/images/f10.jpg和ImageView对象

1)。根据Url从一级缓存中取对应的bitmap对象

        如果有,显示(结束)

       如果没有,进入2

2)。从二级缓存中查找:得到文件名,并在sd卡的缓存目录下加载对应的图片得到Bitmap对象

        如果有:显示,缓存到一级缓存中(结束) 

        如果没有,进入3

3)。显示代表提示正在加载的图片,启动分线程联网请求得到Bitmap对象

        如果没有:显示提示错误的图片(结束)

        如果有:

                     显示

                     缓存到一级缓存

                     缓存到二级缓存

package com.atguigu.app05_handler;

import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;

/**
 * 用于加载图片并显示的类
 * @author 张晓飞
 *
 */
/*
	String iamgePath = http://192.168.10.165:8080//L05_Web/images/f10.jpg和ImageView对象
	1). 根据url从一级缓存中取对应的bitmap对象
		如果有, 显示(结束)
		如果没有, 进入2)
	2). 从二级缓存中查找: 得到文件名并在sd卡的缓存目录下加载对应的图片得到Bitmap对象
		如果有: 显示, 缓存到一级缓存中(结束)
		如果没有, 进入3)
	3). 显示代表提示正在加载的图片, 启动分线程联网请求得到Bitmap对象
			如果没有: 显示提示错误的图片(结束)
			如果有: 
				显示
				缓存到一级缓存
				缓存到二级缓存
 */
public class ImageLoader {
	
	private Context context;
	private int loadingImageRes;
	private int errorImageRes;

	public ImageLoader(Context context, int loadingImageRes, int errorImageRes) {
		super();
		this.context = context;
		this.loadingImageRes = loadingImageRes;
		this.errorImageRes = errorImageRes;
	}

	//用于缓存bitmap的容器对象
	private Map<String, Bitmap> cacheMap = new HashMap<String, Bitmap>();
	
	/**
	 * 加载图片并显示
	 * @param imagePath
	 * @param imageView
	 */
	public void loadImage(String imagePath, ImageView imageView) {
		
		//将需要显示的图片url保存到视图上
		imageView.setTag(imagePath);      //     1
		
		/*
		 1). 根据url从一级缓存中取对应的bitmap对象
			如果有, 显示(结束)
			如果没有, 进入2)
		 */
		Bitmap bitmap = getFromFirstCache(imagePath);
		if(bitmap!=null) {
			imageView.setImageBitmap(bitmap);
			return;
		}
		/*
		2). 从二级缓存中查找: 得到文件名并在sd卡的缓存目录下加载对应的图片得到Bitmap对象
				如果有: 显示, 缓存到一级缓存中(结束)
				如果没有, 进入3)
			
			/storage/sdcard/Android/data/packageName/files/图片文件名(xxx.jpg)
		 */
		bitmap = getFromSecondCache(imagePath);
		if(bitmap!=null) {
			imageView.setImageBitmap(bitmap);
			cacheMap.put(imagePath, bitmap);
			return;
		}
		
		/*
		 3). 显示代表提示正在加载的图片, 启动分线程联网请求得到Bitmap对象
			如果没有: 显示提示错误的图片(结束)
			如果有: 
				缓存到一级缓存(分线程)
				缓存到二级缓存(分线程)
				显示(主线程)
				
		 */
		
		loadBitmapFromThirdCache(imagePath, imageView);
	}

	/**
	 * 根据图片url从三级缓存中取对应的bitmap对象并显示
	 * @param imagePath
	 * @param imageView
	 * AsyncTask
	 * loadBitmapFromThirdCache("../b.jpg", imageView)
	 * loadBitmapFromThirdCache("../f.jpg", imageView)--->imageView.setTag("../f.jpg")
	 */
	private void loadBitmapFromThirdCache(final String imagePath, final ImageView imageView) {
		new AsyncTask<Void, Void, Bitmap>() {
			protected void onPreExecute() {
				imageView.setImageResource(loadingImageRes);
			}
			
			//联网请求得到bitmap对象
			@Override
			protected Bitmap doInBackground(Void... params) {
				//在分线程执行, 可能需要等待一定时间才会执行
				//在等待的过程中imageView中的tag值就有可能改变了
				//如果改变了, 就不应该再去加载图片(此图片此时不需要显示)
				
				Bitmap bitmap = null;
				try {
					
					//在准备请求服务器图片之前, 判断是否需要加载
					String newImagePath = (String) imageView.getTag();
					if(newImagePath!=imagePath) {//视图已经被复用了
						return null;
					}                  //          2
					 
					//得到连接
					URL url = new URL(imagePath);
					HttpURLConnection connection = (HttpURLConnection) url.openConnection();
					//设置
					connection.setConnectTimeout(5000);
					connection.setReadTimeout(5000);
					//连接
					connection.connect();
					//发请求读取返回的数据并封装为bitmap
					int responseCode = connection.getResponseCode();
					if(responseCode==200) {
						InputStream is = connection.getInputStream();//图片文件流
						//将is封装为bitmap
						bitmap = BitmapFactory.decodeStream(is);
						is.close();
						
						if(bitmap!=null) {
							//缓存到一级缓存(分线程)
							cacheMap.put(imagePath, bitmap);
							//缓存到二级缓存(分线程)
							// /storage/sdcard/Android/data/packageName/files/
							String filesPath = context.getExternalFilesDir(null).getAbsolutePath();
							// http://192.168.10.165:8080//L05_Web/images/f10.jpg
							String fileName = imagePath.substring(imagePath.lastIndexOf("/")+1);//  f10.jpg
							String filePath = filesPath+"/"+fileName;
							bitmap.compress(CompressFormat.JPEG, 100, new FileOutputStream(filePath));
						}
					}
					connection.disconnect();
				} catch (Exception e) {
					e.printStackTrace();
				}
				
				
				return bitmap;
			}
			
			protected void onPostExecute(Bitmap bitmap) {//从联网请求图片到得到图片对象需要一定的时间, 视图可能被复用了,不需要显示
				//在主线程准备显示图片之前, 需要判断是否需要显示
				String newImagePath = (String) imageView.getTag();
				if(newImagePath!=imagePath) {//视图已经被复用了
					return;
				}    //        3
						
				//如果没有: 显示提示错误的图片(结束)
				if(bitmap==null) {
					imageView.setImageResource(errorImageRes);
				} else {//如果有, 显示
					imageView.setImageBitmap(bitmap);
				}
			}
		}.execute();
	}

	/**
	 * 根据图片url从二级缓存中取对应的bitmap对象
	 * @param imagePath
	 * @return
	 */
	private Bitmap getFromSecondCache(String imagePath) {
		
		// /storage/sdcard/Android/data/packageName/files/
		String filesPath = context.getExternalFilesDir(null).getAbsolutePath();
		// http://192.168.10.165:8080//L05_Web/images/f10.jpg
		String fileName = imagePath.substring(imagePath.lastIndexOf("/")+1);//  f10.jpg
		String filePath = filesPath+"/"+fileName;
		
		return BitmapFactory.decodeFile(filePath);
	}

	/**
	 * 根据图片url从一级缓存中取对应的bitmap对象
	 * @param imagePath
	 * @return
	 */
	private Bitmap getFromFirstCache(String imagePath) {
		return cacheMap.get(imagePath);
	}
}

运行效果:


但是,图片一直在变换,并不固定。

3)。在ListView使用图片三级缓存会存在图片闪动的bug(不包括红色的代码)

ps:反正我是没听说在GridView、RecycleView中出现了这种BUG

图片闪动是listView中特有的bug

1。原因

1.convertView被复用了(convertView用于将之前加载好的布局进行缓存,以便之后进行重用。)

2.开启了多线程


getView方法的调用:

getview()一般会在,第一次显示,页面刷新,或者当adapter对应的数据源变化时候会主动调用,notifyDataSetChanged等方法主动刷新界面时被调用,也就是当listview需要显示item的时候就会调用getview()

每当Listiiew滑动的时候,就会调用getView(),由于需要加载图片,所以必须开启线程。

当向下滑动到C、D、F、G的时候,F的imageView会复用A的imageView,G的imageView会复用B的imageview,于是就会出现F显示a.jpg,G显示b.jpg。等到线程调用结束,也就是得到了f.jpg/g.jpg并完成对imageView1、imageView2的赋值时,才会显示f.jpg,G.jpg.这就是闪动的原因(这是开启线程比较少时的情况分析,理解上面代码时,最好不用这个方式,会将你带入误区的,进去了就出不来了,出不来了,出不来了----------------------------------------------------------)。

当快速向上滑动时,比如滑动100个item,那么就会调用100此getView,image1/2/3/4会复用至少7/8次,会闪动25次。(为什么不是25次?线程运行完成需要时间的嘛!在这么短的时间里又不是所有的都运行完成了。)

2。解决

在这里说一下解决问题关键:

 在分线程执行, 可能需要等待一定时间才会执行
在等待的过程中imageView中的tag值就有可能改变了,也就是说imageView已经加载了别的图片了
如果改变了, 就不应该再去加载图片(此图片此时不需要显示)

比如,在快速滑动的过程中,会开启多个线程,属于F这个item的线程可能要等待一定时间才会执行,等待的过程中,tag值就可能发生了改变。

总结:解决以上问题只需要三部:就是以上红色代码标志的地方。

a。每次getView()都将图片的url保存到ImageView上:imageView.setTag(imagePath)

b。在分线程准备请求服务器加载图片之前,比较准备加载图片的url与ImageView中保存的最新图片的url是同一个

如果不是同一个,当前加载图片的任务不应该执行

如果相同,继续执行加载远程图片

c。在主线程准备显示图片之前,比较加载到图片的url与ImageView中保存的最新图片的url是同一个

  如果不是同一个,不需要显示此图片

如果相同,显示图片

另附一句:getTag()、setTag()是View中的方法。

思考:为什么TextView中不会出现闪动的问题?

很简单,没有使用多线程,不会出现使用多线程读取服务器数据,即先调用别的线程已经读取了数据,然后再读取此线程中的数据的情况。

思考:为什么会说和convertView有关呢?

由于convertView可以被复用,也就是说,在快速向下滑动的时候,imageView1会被多次复用,只有imageView1中线程的图片才会被加载到这个被多次复用的imageView1中。这么多属于imageView1的线程,由于可以“先调用别的线程已经读取了数据,然后再读取此线程中的数据。”所以imageView1在特定时间内会不断地出现闪动。所以说convertView+多线程导致了图片加载的闪动

视频学习地址:http://www.gulixueyuan.com/course/112/task/1732/show

图片三级缓存进阶:https://blog.youkuaiyun.com/lovoo/article/details/51456515

Android中图片的三级缓存原理 Android图片的三级缓存原理,介绍了图片三级缓存的原理,介绍了Android中软引用的使用,以及lruCache进行图片缓存,请访问我的博客进行查看:http://blog.youkuaiyun.com/qq_20889581/article/details/51034472 立即下载

相关推荐

【Python程序开发】湖南省职业院校技能大赛python程序开发赛项样题二

【Python程序开发】湖南省职业院校技能大赛python程序开发赛项样题二

况老师话竞赛&blog 2204

获取指定路径下得照片

//[NSBundle mainBundle]用来获取应用程序包 //pathForResource 用来获取包内资源的路径NSString *imagePath=[[NSBundle mainBundle]pathForResource:[dictionary2 objectForKey:cell.textLabel.text] ofType:nil]; cell.imageView.image

stevenlfg的专栏 653

五、e2studio VS STM32CubeIDE之汉化

介绍stm32cubeide汉化方案和汉化包e2studio自带汉化包,在安装过程中可选择安装e2studio STM32CubeIDE切换中英文。

cl234583745的博客 1770

c# 通过本地地址创建Image

c# 通过本地地址创建Image

xie__jin__cheng的博客 653

Opencv相机校准之棋盘格标定

Reference: https://yq.aliyun.com/articles/62472

y459541195的博客 3万+

overleaf导入模板

本文介绍了如何在Overleaf平台导入Springer Nature模板。首先下载所需的模板压缩包,然后在Overleaf中选择"Upload project"上传该压缩包。上传后会显示模板文件,包括EPS格式图像文件、LaTeX源文件、BibTeX参考文献文件、PDF输出文件以及文档类文件。这些文件共同构成了完整的LaTeX模板,可用于学术论文写作和排版。

A2787960542的博客 1148

c++ int转string

imageFile为left文件夹下名为1到10的图片 for (int i = 1; i { string ii; int2str(i, ii); string str = ii + ".jpg"; string imagepath = "D:\\FACE\\testsvm\\left\\"; string imagepath1 =imagepath+str; char i

qq_32039433的博客 400

探索Python融合地学:一文解决osgeo库安装失败问题

总结一下,要安装osgeo模块,关键在GDAL依赖库的安装,如果能够顺利安装GDAL依赖库,因其是osego的模块,因此osgeo的部分模块也能够得到自动安装,此时再运行代码,发现代码并不报错,能够正常运行了。第一步,我们要安装imagecodecs库,它支持多种常见的图像格式,当然也包含我们的目标tif格式,为方便后续的图像处理和安装osgeo模块,先安装此库。目前GDAL已经更新到了3.10版本,这里主要看你的Python版本是上面,小编用的是Python3.13,电脑是64位的,因此,我们选择。

Promising_GEO的博客 5461

C# 图片的读取

//图片转成二进制 public byte[] GetPictureData(string imagepath) { /**/ ////根据图片文件的路径使用文件流打开,并保存为byte[] FileStream FileStream = new FileStre...

JiYuee Note 9070

笔记本电脑更换主板后出现2203:System configuration is invalid,以及2201、2202系统错误的解决

主板更换后刷新S/N

晓夏IT笔记 3519

思科路由器重分发(静态路由+OSPF动态路由+RIP动态路由)

思科路由器重分发(静态路由+OSPF动态路由+RIP动态路由)

2301_81256766的博客 1261

图片的三级缓存

介绍本文介绍了三级缓存的原理及操作,并且用策略模式的架子框起来。

buglei123的博客 2904

educoder中Hive综合应用案例 — 用户搜索日志分析

Hive综合应用案例 — 用户搜索日志分析

weixin_46026136的博客 4789

嵌入式工程师拼技术却拿低薪?四维定价模型揭秘薪资逻辑

薪资谈判的本质,是市场对个人能力的综合定价,而非单纯对工作强度的补偿。薪酬背后存在一套由技术复杂度、稀缺指数、商业价值距离与杠杆半径构成的逻辑框架。在嵌入式行业,驱动开发与底层调试等岗位技术门槛高、劳动强度大,但薪资却常低于做平台或架构的同行,根源正是商业价值维度上的差距。理解四维定价模型,可以帮助嵌入式工程师在职业规划、跳槽谈判与技术选型中明确发力方向,从单纯解决技术问题走向影响产品线营收,真正提升市场议价能力。

weixin_34101784的博客 338

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

android基础学习之android图片的三级缓存

从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。” 另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放, 这就有潜在的风险造成应用程序的内存溢出并崩溃。 所以软引用能不用尽量不用 内存缓存技术对那些大量

xinguanyiban07的专栏 499

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

HarmonyOS Image图片组件与加载优化——从基础属性到三级缓存、渐进加载与企业级图片加载框架

摘要:图片是移动应用中内存占用最高的资源类型,一张未经优化的4K图片可能占用超过50MB内存。在HarmonyOS应用开发中,Image组件看似简单,实则涉及图片解码、内存管理、缓存策略、网络加载、渲染优化等多个复杂领域。

从事移动开发八年,csdn博客专家,阿里云特邀专家,华为云云享专家,高级网络信息安全工程师,大数据工程师(高级),python技术开发(高级),HarmonyOS 应用开发者高级认证,现在某学校任教。 1万+

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

自己实现图片缓存

文章目录前言杂谈思路 前言 杂谈 emm,同学建议说,要在面试的时候给面试官一个亮点。又说数据结构与算法不会考得很难,考得很难的话,现在也学不到那么难的。 我觉得他说的有道理,所以做一个能易于表现出学习能力的东西,然后能完全展示给面试官看的东西。 之前接受过一个说法,说 Android学习,缓存框架和网络交互框架写好了,基本上60%的工作都完成了,剩下30%是自定义View,剩下10%就是很多博...

Wby_Nju的博客 1617

android性能优化之图片Bitmap优化(含源码)

android性能优化之图片Bitmap优化

qq_23081779的博客 1230

Vue Native中处理图片:加载、缓存与优化策略

Vue Native作为构建跨平台原生移动应用的框架,图片处理是提升用户体验的关键环节。本文将系统介绍在Vue Native应用中实现图片高效加载、智能缓存及性能优化的完整方案,涵盖核心API使用、缓存机制实现与高级优化策略。 ## 图片加载基础实现 Vue Native中加载图片需使用框架提供的基础组件,通过指定正确的资源路径实现跨平台兼容。 ### 基础图片组件使用 Vue Nativ...

gitblog_01029的博客 1049

Glide用户指南:打造流畅滑动的图片列表终极方案

Glide是一款专注于实现Android平台流畅滑动体验的图片加载与缓存库。它通过高效的内存管理和智能缓存机制,让开发者能够轻松构建高性能的图片列表应用,即使在快速滑动场景下也能保持界面流畅无卡顿。 ## 🚀 为什么选择Glide?三大核心优势解析 ### 1. 专为滑动优化的加载引擎 Glide采用独特的图片加载策略,能够根据列表滑动状态动态调整加载优先级。当用户快速滑动列表时,Glide

gitblog_00584的博客 873

Java和Android基础知识

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34102807的博客 86

MoviePilot TMDB图片访问优化:深度解析3层缓存架构与智能代理策略

MoviePilot作为NAS媒体库自动化管理工具,其核心功能依赖TMDB(The Movie Database)提供丰富的影视元数据。在国内网络环境下,TMDB图片资源加载失败成为影响用户体验的主要瓶颈。本文将深入分析MoviePilot的异步请求处理机制和智能缓存架构,提供从问题诊断到性能优化的完整技术解决方案。 ## 问题根源:网络限制与架构瓶颈分析 TMDB图片服务器位于境外,国内用户

gitblog_00640的博客 25

win32Day05:GDI绘图

目录 0x00画一个像素点: 0x01画线: 0x03画图形:圆形矩形等 0x04在客户区绘制文字: 0x05三级缓存防闪烁 GDI:Graphical Device Interface 图形设备接口(windows上用来控制图形设备的一些函数),设备指显卡 显示器等硬件设备。 控制硬件设备的程序称为驱动程序。操作系统层有一些GDI GUI COM的库。应用程序则是程序员写的。 G...

坚持硬核 4710

ListView的优化

【代码】ListView的优化。

weixin_43956070的博客 521

终极Fresco安装与使用指南:Android图片管理神器入门教程

Fresco是一款由Facebook开发的功能强大的Android图片管理库,专门用于高效加载、显示和管理图片资源,显著提升应用性能和用户体验。本指南将带你快速掌握Fresco的安装配置与核心功能使用方法,让你的Android应用轻松处理各种图片场景。 ## 🚀 快速安装Fresco库 ### Gradle依赖配置 在你的Android项目模块的`build.gradle`文件中添加以下依赖

gitblog_00630的博客 414
上一篇: Android中Application的总结
下一篇: Context内存泄漏问题
jenkin1011
博客等级 码龄10年 104粉丝 39原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值