赞
踩
在Android
应用中,大图的加载和显示可能导致内存占用过高,进而引发OOM
(Out Of Memory)异常,影响应用的稳定性和用户体验。为了更好地管理大图资源,我们需要建立起一套可靠的大图监测系统。
首先,我们需要了解如何计算一张图片在内存中的占用大小。Android中,图片占用的内存主要由其宽、高和每个像素的位数决定。我们可以使用以下公式计算:
[ 内存占用大小 = 宽 \times 高 \times 像素位数 / 8 ]
一般情况下,大图的定义是指超过一定阈值的图片。这个阈值可以根据应用的实际需求来设定,通常建议根据设备的内存情况和应用场景动态调整。
大图监测一般采用两种策略:主动监测和被动监测。主动监测通过周期性地扫描内存中的图片资源,识别大图,进行处理。而被动监测则是在图片加载过程中实时判断是否为大图。
主动监测只要获取到内存中的图片资源,通过扫描判断是否超过设置的阈值即可。
class LargeImageScanner { fun scanLargeImages() { // 遍历内存中的图片资源 for (image in MemoryManager.getAllImages()) { val imageSize = calculateImageSize(image) // 判断是否为大图 if (imageSize > LARGE_IMAGE_THRESHOLD) { // 进行处理,如压缩、裁剪或异步加载 handleLargeImage(image) } } } private fun calculateImageSize(image: Bitmap): Int { // 计算图片占用的内存大小 return image.width * image.height * (image.config.bitsPerPixel / 8) } private fun handleLargeImage(image: Bitmap) { // 实现大图的处理逻辑,例如压缩、裁剪或异步加载 // ... } }
被动监测的目的是,让图在加载的过程中,自动获取到加载图片的大小。所以切入的时机就非常重要。
如果你使用的是第三方图片加载库Glide
,最简单的直接的是在图片加载的成功的时机进行监测。
class GlideImageLoader { fun loadWithLargeImageCheck(context: Context, url: String, target: ImageView) { Glide.with(context) .asBitmap() .load(url) .listener(object : RequestListener<Bitmap> { override fun onLoadFailed( e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean ): Boolean { // 图片加载失败处理 // ... return false } override fun onResourceReady( resource: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean ): Boolean { // 图片加载成功,检查是否为大图 resource?.let { val imageSize = calculateImageSize(it) if (imageSize > LARGE_IMAGE_THRESHOLD) { // 处理大图逻辑,如压缩、裁剪或异步加载 handleLargeImage(it) } } return false } }) .into(target) } private fun calculateImageSize(image: Bitmap): Int { // 计算图片占用的内存大小 return image.width * image.height * (image.config.bitsPerPixel / 8) } private fun handleLargeImage(image: Bitmap) { // 实现大图的处理逻辑,例如压缩、裁剪或异步加载 // ... } }
但上面这种方式存在几个弊端
loadWithLargeImageCheck
方法,如果是一个现有的大项目,将无法改造。Glide
,后续换库也不兼容所以为了解决上面的这几个问题,我们要想的是,能否不依赖于第三方图片加载库呢?
于是就有了下面这种方式
现在使用网络请求基本都是使用Okhttp
,在这种情况下,你可以考虑使用拦截器(Interceptor)来实现通用的大图监测逻辑。拦截器是OkHttp
中的一种强大的机制,可以在请求发起和响应返回的过程中进行拦截、修改和监测。
以下是一个使用OkHttp
拦截器进行大图监测的示例:
import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response import java.io.IOException class LargeImageInterceptor : Interceptor { @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() // 发起请求前的处理,可以在这里记录请求时间等信息 val response = chain.proceed(request) // 请求返回后的处理 if (response.isSuccessful) { val contentType = response.body()?.contentType()?.toString() // 检查是否为图片资源 if (contentType?.startsWith("image/") == true) { // 获取图片大小并进行大图监测 val imageSize = calculateImageSize(response.body()?.byteStream()) if (imageSize > LARGE_IMAGE_THRESHOLD) { // 处理大图逻辑,如压缩、裁剪或异步加载 handleLargeImage() } } } return response } private fun calculateImageSize(inputStream: InputStream?): Int { // 通过输入流计算图片占用的内存大小 // ... } private fun handleLargeImage() { // 实现大图的处理逻辑,例如压缩、裁剪或异步加载 // ... } }
然后,在创建OkHttpClient
时,添加这个拦截器:
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(LargeImageInterceptor())
.build()
通过这种方式,你只需要在OkHttp
中添加一次拦截器,即可在每个图片请求中进行通用的大图监测处理,而不用在每个请求的响应回调中添加监测代码。这样使得代码更加清晰、易于维护。
可能又有人会说,我网络加载库换了,那不是一样无法兼容吗?
确实,虽然概率比直接换第三方图片加载库还低,但既然有可能,就要尽可能的解决。
于是就是了下面的这种终极方法。
这就升级到图片加载的本质了,任何图片加载最终都是要填充到ImageView
上。而在这过程中自然避免不了使用ImageView
的方法进行填充图片。
例如:setImageDrawable
等等。
当然也可以直接hook
整个ImageView
,全局将其替换成HookImageView
,再到其内部实现大图监测。这两种都是通过ASM
,只是对象不一样,但原理都基本一致。
以下是一个简单的示例,使用ASM
对Android
中的 ImageView
的 setImageDrawable
方法进行拦截:
import org.objectweb.asm.*; public class ImageViewInterceptor implements ClassVisitor { private final ClassVisitor cv; public ImageViewInterceptor(ClassVisitor cv) { this.cv = cv; } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); if (name.equals("setImageDrawable") && desc.equals("(Landroid/graphics/drawable/Drawable;)V")) { return new ImageViewMethodVisitor(mv); } return mv; } // 其他方法省略,你可以根据需要实现其他 visitX 方法 } class ImageViewMethodVisitor extends MethodVisitor { public ImageViewMethodVisitor(MethodVisitor mv) { super(Opcodes.ASM5, mv); } @Override public void visitCode() { super.visitCode(); // 在方法开头插入大图监测逻辑的字节码 // ... } @Override public void visitInsn(int opcode) { if (opcode == Opcodes.RETURN) { // 在 RETURN 指令前插入大图监测逻辑的字节码 // ... } super.visitInsn(opcode); } } // 在某处,使用 ASM 进行字节码修改 ClassReader cr = new ClassReader("android/widget/ImageView"); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ImageViewInterceptor interceptor = new ImageViewInterceptor(cw); cr.accept(interceptor, 0); ....
这个示例中,ImageViewInterceptor
对 ImageView
的 setImageDrawable
方法进行了拦截,ImageViewMethodVisitor
中插入了大图监测逻辑的字节码。
需要注意的是。在实际应用中,需谨慎考虑因字节码操作而引起的潜在问题和兼容性风险。
在实现大图监测时,我们需要注意以下事项:
通过本文的学习,相信你已经对Android大图监测有了深入的理解。
更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》
1.Android车载应用开发系统学习指南(附项目实战)
2.Android Framework学习指南,助力成为系统级开发高手
3.2023最新Android中高级面试题汇总+解析,告别零offer
4.企业级Android音视频开发学习路线+项目实战(附源码)
5.Android Jetpack从入门到精通,构建高质量UI界面
6.Flutter技术解析与实战,跨平台首要之选
7.Kotlin从入门到实战,全方面提升架构基础
8.高级Android插件化与组件化(含实战教程和源码)
9.Android 性能优化实战+360°全方面性能调优
10.Android零基础入门到精通,高手进阶之路
敲代码不易,关注一下吧。ღ( ´・ᴗ・` )
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。