Android性能优化-图片篇

(1)drawable目录详解(mdpi,hdpi,xhdpi,xxhdpi,xxxhdpi)

1.1、图片在各个目录中要如何存放?(必须理解)

android的drawable目录有:

  • drawable-ldpi(低密度)
  • drawable-mdpi(中等密度)
  • drawable-hdpi(高密度)
  • drawable-xhdpi(超高密度)
  • drawable-xxhdpi(超超高密度)
  • drawable-xxxhdpi(超超超高密度)
  • drawable-nohdpi(无缩放)
  • 默认的drawable

而安卓加载图片的原理是根据手机的密度(dpi)来选择不同的文件夹下的图片,如果没有,就会从别的密度文件夹来获取图片并按照一定比例来缩放展示图片。获取设备密度的方法为:

float xdpi = getResources().getDisplayMetrics().xdpi;
float ydpi = getResources().getDisplayMetrics().ydpi;

知道了dpi,就知道会去哪个文件夹获取图片了:

对应文件夹 密度范围(dpi) 缩放比例
ldpi 0~120 0.75
mdpi 120~160 1(基准)
hdpi 160~240 1.5
xhdpi 240~320 2.0
xxhdpi 320~480 3.0
xxxhdpi 480~640 4.0

而当前的主流机型密度基本都是在320~480之间,一般放两套图就够了,一套放到xhdpi,一套放到xxhdpi,那么如果是一个hdpi的手机来运行会发生什么呢?安卓是有一套图片匹配规则的:

一个hdpi密度的手机,肯定是先去匹配hdpi目录下的图片,如果没有,那么就会向上级去查找,分别是xhdpi->xxhdpi->xxxhdpi->nodpi,如果都没有,就会往下级目录去查,分别是mdpi->ldpi,如果还没有,就会去drawable目录去查找,如果还没有!就会开启自毁模式,嘣!!(resouse not found)

1.2、相同图片在不同目录所占用资源分析(了解即可)

不知道有没有朋友做过这样一个实验,把一个xhdpi密度的图片放到xxhdpi密度的文件夹中去加载会发生什么,它所占的内存会有区别吗?
例如一个在xhdpi下为200 * 200的图片,xxhdpi密度下为300 * 300,但此时图片只有200 * 200,所以它会放大到300 * 300,这时候图片就会出现模糊的情况了,所以如果你在项目中发现图片模糊的情况可以检查下图片的尺寸是否正确。

接下来我们来做一个实验,使用一个名叫icon_database_xls 64 * 64 PNG的图片,将它放到xhdpi目录中,执行下面的代码获取内存占用情况:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.icon_database_xls);
Log.i("HJ",bitmap.getByteCount()+"");

结果如下:

2019-01-08 10:56:01.872 18507-18507/jie.com.imageoptimize I/HJ: 30976

接下来将图片放到xxhdpi目录下,再次执行代码:

2019-01-08 10:56:32.123 18677-18677/jie.com.imageoptimize I/HJ: 13924

可以看到结果差距巨大,我们知道,一个图片的内存占用公式为:图片内存宽度 * 图片内存高度* 每像素占用的内存位数,那我们的内存宽高是如何计算的呢?我们可以追踪framework的源码,路径为/frameworks/base/core/jni/android/graphics/BitmapFactory.cpp,找到doDecode方法:

static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream,
                        jobject padding, jobject options) {
    int sampleSize = 1;
    bool onlyDecodeSize = false;
    ... ...
    float scale = 1.0f;
    ... ...
    if (options != NULL) {
        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
        if (sampleSize <= 0) {
            sampleSize = 1;
        }

        if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) {
            onlyDecodeSize = true;
        }
        ... ...
        if (env->GetBooleanField(options, gOptions_scaledFieldID)) {
            const int density = env->GetIntField(options, gOptions_densityFieldID);
            const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID);
            const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID);
            if (density != 0 && targetDensity != 0 && density != screenDensity) {
                scale = (float) targetDensity / density;  //缩放比在这里计算的
            }
        }
    }
    ... ...

    if (scale != 1.0f) {
        willScale = true;
        scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); //内存宽度计算
        scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); //内存高度计算
     }

以上源码总结为:
内存宽高 = 图片本身的宽高 * (设备密度/使用的密度)+ 0.5
所以,内存的占用与图片所在的密度目录是有密切关系的

1.3、 drawable-xxhdpi与mipmap-xxhdpi的区别(了解即可)

从新版Android studio开始,系统会默认给我们创建mipmap文件夹而不是drawable,先前我一直以为这个目录只是放icon的(流下了菜逼的心酸泪水),后来发现这个目录与drawable都可以放图片,而且貌似也没有任何差别????,为了弄清楚,上网查了一下,大意是会对图片缩放做性能优化,不过我在平常的使用中并没有发现有太大的区别,可能是高版本drawable也做了优化把,所以这个简单了解一下即可。

(2)Bitmap优化详解

2.1、基础知识

安卓的图片加载都会对应到bitmap对象,当bitmap占用的内存过高,超过了android为app分配的最大内存,那么它就会不开心,它就会OOM,所以我们的图片优化,本质就是内存优化。
我们常用的图片格式一般有三种:

  • JPEG 一种有损压缩格式,不支持透明通道,所以有透明背景需求的图片不要用jpeg格式,它会变成黑色,会变黑!!!,会变黑!!!,会变黑!!!
  • PNG 无损压缩格式,支持透明通道
  • WEBP 同时支持无损和有损压缩格式,然而兼容性较差,需要适配库
    可参考:webp-android

在代码中可以通过Bitmap.CompressFormat来指定生成的图片格式
bitmap还可以配置像素占用字节数(一个字节对应8位),这与内存占用息息相关,对应Bitmap.Config类:

  • ALPHA_8 8位ALPHA通道,即A=8,一个像素占用一个字节,它是没有颜色的,只有透明度
  • ARGB_4444 16位 A=4,R=4,G=4,B=4,一个像素占用字节 = 4+4+4+4 = 16位 = 2个字节
  • ARGB_8888 32位 A=8,R=8,G=8,B=8,一个像素占用字节 = 8+8+8+8 = 32位 = 4个字节
  • RGB_565 16位 R=5,G=6,B=5,因为没有A,所以它没有透明度,计算同上也是占用2个字节
    像平常如果没有透明度要求使用RGB_565即可

2.2、优化的本质

前面已经提到我们的图片优化实际是内存的优化,而我们的内存计算公式是:
内存占用宽高相乘 * 每像素占用的内存位数,所以要么就减少图片内存占用宽高,要么就减少像素占用内存数。减少像素内存占用数可以通过配置Bitmap.Config解决,而减少图片内存占用宽高,可以有以下几个方式:
1.缩小图片实际宽高,缩小到正好正常展示
2.匹配合适的像素密度
此外还能主动回收占用的内存从而缓解内存紧张的问题。

2.3、优化方式

分析了一下优化的本质问题,我们可以针对性的使用以下几种方式来进行优化:
1.配置BitmapConfig(前面已经讲到)
2.拿到一个图片,通过inJustDecodeBounds不消耗内存拿到图片的宽高,然后使用inSampleSize来设置图片的缩放比。
简单示例代码:

        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(),R.mipmap.icon_database_xls,options);
        Log.i("HJ","初始图片宽:"+options.outWidth);
        Log.i("HJ","初始图片高:"+options.outHeight);
        options.inSampleSize = 2;
        options.inJustDecodeBounds = false;
        BitmapFactory.decodeResource(getResources(),R.mipmap.icon_database_xls,options);
        Log.i("HJ","缩放后图片宽:"+options.outWidth);
        Log.i("HJ","缩放后图片高:"+options.outHeight);

运行后:

2019-01-08 17:07:50.935 25900-25900/jie.com.imageoptimize I/HJ: 初始图片宽:64
2019-01-08 17:07:50.935 25900-25900/jie.com.imageoptimize I/HJ: 初始图片高:64
2019-01-08 17:07:50.936 25900-25900/jie.com.imageoptimize I/HJ: 缩放后图片宽:32
2019-01-08 17:07:50.937 25900-25900/jie.com.imageoptimize I/HJ: 缩放后图片高:32

可以看到inSampleSize设为2,图片的宽和高都变成了原来的一半,所以可得缩放公式为:
原始图片宽度/inSampleSize = 缩放后的图片宽度。高度同理,所以图片的整体大小变为了原来的1/4。

3.压缩图片,除了外部压缩,内部也可以使用compress()来压缩图片。或者自定义压缩算法,使用比较广泛的有Luban算法

compress()示例代码:

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.icon_database_xls);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
boolean compress = bitmap.compress(Bitmap.CompressFormat.PNG, 90, stream);

第一个参数是需要生成的图片格式,第二个是压缩百分比,第三个是生成的输出流容器。请注意,此方法只会压缩图片大小,但是并不会减少内存占用

4.主动释放内存,绑定控件生命周期,在销毁的时候调用recycler()方法

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

推荐阅读更多精彩内容