原理解析
- 内存占用计算
首先,我们需要了解如何计算一张图片在内存中的占用大小。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中添加一次拦截器,即可在每个图片请求中进行通用的大图监测处理,而不用在每个请求的响应回调中添加监测代码。这样使得代码更加清晰、易于维护。
可能又有人会说,我网络加载库换了,那不是一样无法兼容吗?
确实,虽然概率比直接换第三方图片加载库还低,但既然有可能,就要尽可能的解决。
于是就是了下面的这种终极方法。
使用asm插桩进行大图监控
这就升级到图片加载的本质了,任何图片加载最终都是要填充到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大图监测系统的资料请关注代码网其它相关文章!
发表评论