android基础知识点:高效大图加载

前言

最近线上有用户反馈在App使用过程中遇到大图的时,App异常的卡顿,甚至会出现崩溃的情况。后来排查了一番,发现一个同事在处理图片时,直接原图加载没有做任何“压缩”。这个case的出现,也就引出了这篇文章的必要性。

咱们日常开发过程中,都会使用各种各样的图片库比如Glide。由于所有图片操作都是一股脑的交给图片库去处理,所以即使在遇到大图加载的时候,也无法“复现”这类问题。

因为主流的图片库都帮咱们对大图进行了处理(正印证了那句话:当你能轻松进去的时候,你就该明白,不是你厉害,只是有人在前面替你开路——“鲁讯”)。

既然话都说开了,咱们作为新时代下的福报程序员,那就必须要在这条路上探探深浅。其实图片压缩的方式有很多种,今天咱们只要一种,那就是Google原生的高效加载大图的方案

正文

进行压缩之前,咱们先来感受一下不压缩会怎样...

一、不压缩,直接加载大图

我随便new了一下项目,搞了一个这样的图:

image

其实也不是特别大,就是一张1080P的图。

然后随便的用一个ImageView去加载一下:

iv.setImageResource(R.drawable.test)

当我尝试run的时候,我高估了我的测试机....没有加载出来,就直接崩了。Logcat也是够直接,无情吐槽:


image
image

这么一张图,一共需要132710400Bytes的内存,也就是132m....等等,不对?!分辨率1080 * 1920的图片怎么可能会使用100+m的内存?

我们都知道,正常一个图片被加载到内存里的文件大小 = 图片分辨率的宽 * 图片分辨率的高 * 色彩格式。带入这个公式内存大小 = 1080 * 1920 * 4 = 7.9m,绝不可能是100+m这么多!

这里可能有朋友会有疑问,为啥JPEG的格式会4,JPEG格式没有alpha通道,不应该占这么大的空间。其实具体几,还是需要看这张图最终Bitmap.Config解出来的值,我这张图解出来是ARGB_8888,所以还是要*4。

如果你也有这个疑问,那么接下来的内容你要好好看咯。这个知识点恐怕是盲区...

二、番外:drawble、drawble-xxhdpi有什么区别

作为一个番外的内容部分。这一章节其实和图片压缩没有什么关系,只是额外聊一聊drawble这个文件夹

上述问题的根本原因就是在于文件放置的位置,我只在drawble文件夹下放置了图片资源。

所以...这种case下,如果加载这个资源的手机是一个高密度屏幕,那么这张图片被展示时,并非1080 * 1920...

接下来咱们来看一看,为什么资源文件随便放会带来这么大的问题!(以下内容,部分来自于官方文档

image

文档中提到,如果资源提供不当,会导致缩放失真...。这里为什么系统要进行缩放其实也很好理解:

  • 对于系统来说,如果它向下(低密度)才找到需要引用的资源文件,那么最佳的策略便是将找到的图片资源整体放大。因为那里的图,预期是给低分辨率手机准备的。

  • 那么同理,如果系统向上(高密度)找到了需要引用的资源文件,那么缩小无疑是最佳的选择。因为那里的图,预期是给高分辨率手机准备的。

所以基于此,上述中OOM的内存值132710400bytes是这么算出来的:1080 * 4(这个4是手机dpi640 / 资源dpi160 所得) * 1920 * 4 * 4

小贴士:dpi = 手机分辨率长宽各自平方之和开方,除以对角线长度(单位英寸)。
当然我们也可以通过api:resources.displayMetrics.xdpi。这里得到的值就基本等于当前手机的dpi


所以,强制加载这么大的一张图,是不是不负责任!这么大,硬往里塞,搁谁谁受得了?

三、Google提供的解决方案

既然咱们已经明确硬来是不行了,所以还是要采取一些技巧的。文章中开篇就道出了问题的所在:

Images come in all shapes and sizes. In many cases they are larger than required for a typical application user interface (UI). For example, the system Gallery application displays photos taken using your Android devices's camera which are typically much higher resolution than the screen density of your device.

Given that you are working with limited memory, ideally you only want to load a lower resolution version in memory. The lower resolution version should match the size of the UI component that displays it. An image with a higher resolution does not provide any visible benefit, but still takes up precious memory and incurs additional performance overhead due to additional on the fly scaling.

简单翻译一下就是:太大就不要硬塞,缩到合适的尺寸再塞

文档里还有比较有意思的一句话:There are several libraries that follow best practices for loading images. You can use these libraries in your app to load images in the most optimized manner. We recommend the Glide

官方推荐,最为致命

其实文档中直接贴出了可以Ctrl +C/V就能使用的代码:

imageView.setImageBitmap(
    decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100)
)

fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
    // Raw height and width of image
    val (height: Int, width: Int) = options.run { outHeight to outWidth }
    var inSampleSize = 1

    if (height > reqHeight || width > reqWidth) {

        val halfHeight: Int = height / 2
        val halfWidth: Int = width / 2

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }

    return inSampleSize
}

fun decodeSampledBitmapFromResource(
        res: Resources,
        resId: Int,
        reqWidth: Int,
        reqHeight: Int
): Bitmap {
    // First decode with inJustDecodeBounds=true to check dimensions
    return BitmapFactory.Options().run {
        inJustDecodeBounds = true
        BitmapFactory.decodeResource(res, resId, this)

        // Calculate inSampleSize
        inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight)

        // Decode bitmap with inSampleSize set
        inJustDecodeBounds = false

        BitmapFactory.decodeResource(res, resId, this)
    }
}

代码很好理解,就是将需要加载的图片,按目标所需的加载尺寸进行一次采样,通过采样的值进行等比缩放。

不过这里有一个有趣的细节:官方的代码里是将采样结果进行了 * 2 (inSampleSize *= 2)。当时通过实战我们会发现,inSampleSize并不一定要传2的幂,传3传5传其他也是有效果的。

文档中提到这么一句话:

Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the inSampleSize documentation.(以2的幂作为计算结果,是根据inSampleSize文档,解码器通过四舍五入到最接近的2的幂来使用最终值。)

按照文档的解释inSampleSize为2/3时,效果一样,毕竟3最接近2的幂的值还是2。当时事实跑起来会发现,2和3的结果并不一样:

image

当inSampleSize = 3时,图片长和宽就是比减少了3倍...所以真是不知道官网的葫芦里卖的什么药。

尾声

到这,该唠的基本也就唠完了...内容并不深奥,但也算是必备的知识点~

我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~

个人公众号:咸鱼正翻身
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容