3

Android大图监测的这三种实现方式,你最喜欢哪种?

 2 months ago
source link: http://rousetime.com/2024/01/11/Android%E5%A4%A7%E5%9B%BE%E7%9B%91%E6%B5%8B%E7%9A%84%E8%BF%99%E4%B8%89%E7%A7%8D%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F%EF%BC%8C%E4%BD%A0%E6%9C%80%E5%96%9C%E6%AC%A2%E5%93%AA%E7%A7%8D%EF%BC%9F/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android大图监测的这三种实现方式,你最喜欢哪种?

2024.01.11

Rouse

android

 热度 4℃

Android应用中,大图的加载和显示可能导致内存占用过高,进而引发OOM(Out Of Memory)异常,影响应用的稳定性和用户体验。为了更好地管理大图资源,我们需要建立起一套可靠的大图监测系统。

  1. 内存占用计算

首先,我们需要了解如何计算一张图片在内存中的占用大小。Android中,图片占用的内存主要由其宽、高和每个像素的位数决定。我们可以使用以下公式计算:

[ 内存占用大小 = 宽 \times 高 \times 像素位数 / 8 ]

  1. 大图判定标准

一般情况下,大图的定义是指超过一定阈值的图片。这个阈值可以根据应用的实际需求来设定,通常建议根据设备的内存情况和应用场景动态调整。

  1. 监测策略

大图监测一般采用两种策略:主动监测被动监测。主动监测通过周期性地扫描内存中的图片资源,识别大图,进行处理。而被动监测则是在图片加载过程中实时判断是否为大图。

主动监测只要获取到内存中的图片资源,通过扫描判断是否超过设置的阈值即可。

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) {
// 实现大图的处理逻辑,例如压缩、裁剪或异步加载
// ...
}
}

但上面这种方式存在几个弊端

  1. 适用性低,强制要求所以图片加载都要调用loadWithLargeImageCheck方法,如果是一个现有的大项目,将无法改造。
  2. 强依赖于第三方加载库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中添加一次拦截器,即可在每个图片请求中进行通用的大图监测处理,而不用在每个请求的响应回调中添加监测代码。这样使得代码更加清晰、易于维护。

可能又有人会说,我网络加载库换了,那不是一样无法兼容吗?

确实,虽然概率比直接换第三方图片加载库还低,但既然有可能,就要尽可能的解决。

于是就是了下面的这种终极方法。

使用ASM插桩进行大图监控

这就升级到图片加载的本质了,任何图片加载最终都是要填充到ImageView上。而在这过程中自然避免不了使用ImageView的方法进行填充图片。

例如:setImageDrawable等等。

当然也可以直接hook整个ImageView,全局将其替换成HookImageView,再到其内部实现大图监测。
这两种都是通过ASM,只是对象不一样,但原理都基本一致。

以下是一个简单的示例,使用ASMAndroid中的 ImageViewsetImageDrawable 方法进行拦截:

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);

....

这个示例中,ImageViewInterceptorImageViewsetImageDrawable 方法进行了拦截,ImageViewMethodVisitor 中插入了大图监测逻辑的字节码。

需要注意的是。在实际应用中,需谨慎考虑因字节码操作而引起的潜在问题和兼容性风险。

注意事项与优化技巧

在实现大图监测时,我们需要注意以下事项:

  • 灵活设置阈值: 根据不同设备和应用场景,动态调整大图的阈值,以保证监测的准确性和及时性。
  • 合理选择处理方式: 对于大图,可以选择合适的处理方式,如压缩、裁剪或异步加载,以降低内存占用。
  • 异步处理: 将大图的处理放在异步线程中,避免阻塞主线程,提高应用的响应性。

通过本文的学习,相信你已经对Android大图监测有了深入的理解,并可以在实际项目中应用这些知识,提升应用的性能和用户体验。

android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。

AwesomeGithub: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack\&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK