首页 > 其他分享 >Android大图监测的这三种实现方式,你最喜欢哪种?

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

时间:2024-01-03 20:32:31浏览次数:33  
标签:大图 image 三种 fun Android 图片 监测 加载

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 onl oadFailed(
                    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大图监测有了深入的理解,并可以在实际项目中应用这些知识,提升应用的性能和用户体验。

标签:大图,image,三种,fun,Android,图片,监测,加载
From: https://blog.51cto.com/u_16175630/9088574

相关文章

  • Android GB28181-2022 图像抓拍
     GB28181-2022新增图像抓拍功能,这个功能很有用,无需在设备实时点播的情况下,就可以抓图上传到指定的图像存储服务器上。如果配置合适的抓拍间隔,JPEG也选择适当的压缩参数,相比实时音视频回传更省流量,设备功耗也低。 GB28181图像抓拍分为三步,一是下发图像抓拍配置命令给设备,......
  • Windows Subsystem for Android (WSA) 下载:在 Windows 11 上运行 Android 应用 (July
    WindowsSubsystemforAndroid(WSA)下载:在Windows11上运行Android应用(July2023)适用于Android™️的Windows子系统,2023年7月更新:2306.40000.4.0作者主页:sysin.org适用于Android™️的Windows子系统使你的Windows11设备能够运行AmazonAppstore中提供的Andr......
  • [Android] 如何把自定义的 可执行文件/库文件/apk 放到系统目录下
    找到源码目录device/......./<devicename>/<devicename>.mk,以waydroid为例:/device/waydroid/waydroid/waydroid_arm64打开如下文件: lineage_waydroid_arm64.mk添加PRODUCT_PACKAGES变量到上述文件中,比如:PRODUCT_PACKAGES+=可执行文件名/动态库名字/静态库文件名字/apk文件名......
  • Android头像列表重叠最简实现
    手把手教程,针对初学者(老家伙请跳过),先看效果,能用得上的再继续往下看……针对上图重叠头像的展示实现方式,最简单的就是使用RecyclerView,利用其装饰器。平常使用RecyclerView都是每个item之间有间距,而这里不仅没间距还重叠了一部分,其实本质上也可以看成是有间距,只不过间距是负值。直接......
  • ThreadLocal:你不知道的优化技巧,Android开发者都在用
    引言在Android开发中,多线程是一个常见的话题。为了有效地处理多线程的并发问题,Android提供了一些工具和机制。其中,ThreadLocal是一个强大的工具,它可以使得每个线程都拥有自己独立的变量副本,从而避免了线程安全问题。本文将深入探讨Android中的ThreadLocal原理及其使用技巧,帮助你更......
  • 为什么Android系统开发工程师这么稀缺?
    前言今日得闲,思考了一个问题。为什么Android系统开发工程师各个公司都在招聘,而且是每天都在招,工资待遇也很客观,但结果却找不到人?首先,我们来看下Android系统开发工程师的主要职责是什么,以下是招聘网的要求。根据以上要求,我们来分析一下:本科学历,这个要求目前基本上都可以满足,再高一点......
  • 在Android设备上设置和使用隧道代理HTTP
    随着互联网的深入发展,网络信息的传递已经成为人们日常生活中不可或缺的一部分。对于我们中国人来说,由于某些特殊的原因,访问国外网站时常常会遇到限制。为了解决这个问题,使用代理服务器成为了许多人的选择。而在Android设备上设置和使用隧道代理HTTP,可以更好地保护用户的隐私,同时提......
  • 安卓期末大作业(AndroidStudio开发),垃圾分类app,代码有注释,能正常运行
    1 项目基本信息1.1 项目名称垃圾分类助手APP的设计与实现1.2开发运行环境开发语言:Java开发工具:AndroidStudio模拟器:雷电模拟器9数据库:SQLite  1.3使用的核心类及组件Activity:作为实现界面的窗体类BaseAdapter:适配器类SqliteOpenHelper:数据库类Intent:页面跳转传值Fragmen......
  • 安卓期末大作业(AndroidStudio开发),日记本app,代码注释详细,能正常运行
    安卓期末大作业-日记本app(附下载链接)压缩包内包含源代码,运行各个界面截图,一条日记可以记录2000字符以下的文本、最多8张配图和最多8个视频。每条日记都可以以评论的形式或转发引用的形式追更,评论的最大长度也是2000字符。日记还可以同时记录所处位置和当时的天气情况(当然,现在并......
  • Android期末大作业:使用AndroidStudio开发图书管理系统APP(使用sqlite数据库)
    AndroidStudio开发项目图书管理系统项目视频展示:引言现在是一个信息高度发达的时代,伴随着科技的进步,文化的汲取,人们对于图书信息的了解与掌握也达到了一定的高度。尤其是学生对于知识的渴求更是与日俱增。图书馆作为学生学习知识的重要场所,作为信息资源的集散地,图书和用户借阅资......