(2019.5.29)关于drawable你可能不知道的一些事

2023-11-15

(转载公司内部论坛本人文章2019.5.29)
导读: Android项目中,官方建议将不同分辨率的资源图片分别放在drawable-mdpi,drawable-hdpi,drawable-xhdpi等不同的drawable文件夹下,以便不同分辨率的手机加载不同的图片资源,从而实现不同手机上图片显示大小一致。那么,不同的手机是根据什么来决定加载哪个drawable文件夹下的资源图片呢?加载不同drawable文件夹下的图片对图片宽高和内存会有影响吗?假设手机对应的drawable文件夹下没有该图片资源,又该怎样加载呢?

首先,Android手机是根据屏幕的每英寸像素(dpi值)来决定加载哪个drawable文件夹下的图片资源的,这个相信大家都已经知道了。可以通过下面代码查看屏幕的dpi值

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

其中xdpi代表屏幕宽度的dpi值,ydpi代表屏幕高度的dpi值,通常这两个值几乎相等或及其接近。我用小米note1**(系统版本为android6.0,分辨率为1080*1920)**测得两个值分别是386.366和387.047。对应加载的drawable文件夹,可以参考下表:

dpi范围 密度类型 对应的density值
0dpi ~ 120dpi ldpi 0.75
120dpi ~ 160dpi mdpi 1
160dpi ~ 240dpi hdpi 1.5
240dpi ~ 320dpi xhdpi 2
320dpi ~ 480dpi xxhdpi 3
480dpi ~ 640dpi xxxhdpi 4

所以,小米note1的387dpi属于320dpi ~ 480dpi范围,会加载drawable-xxhdpi文件夹下的图片资源。
下面我做一个试验。准备一张412 * 536分辨率的图片ic_android,放在drawable-xxhdpi文件夹,然后布局引用这张图片。
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="获取图片信息"
        />

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/ic_android"
        />
</LinearLayout>

代码很简单,运行一下程序,得到显示效果如下所示:
在这里插入图片描述

小米note1的屏幕宽度分辨率是1080px,图片宽度是412px,所以图片宽度大约占据屏幕宽度的2/5左右,跟预期效果差不多。
接着,我将ic_android图片移动到drawable-xhdpi。注意,是移动不是复制。运行程序看一下效果。
在这里插入图片描述
图片反而变大了?接着,再将图片移动到drawable-mdpi。运行程序,效果如下:
在这里插入图片描述

此时图片已经铺满了整个屏幕。将图片放在低dpi的drawable文件夹下,加载出来的图片反而变大了?抱着好奇的心理,用代码打印出每种情况下,图片加载完成后的宽高信息:

ImageView imageView = findViewById(R.id.image_view);
BitmapDrawable bitmapDrawable = (BitmapDrawable) imageView.getDrawable();
Bitmap imageBitmap = bitmapDrawable.getBitmap();
Logger.i("wujunxuan", "imageWidth = " + imageView.getWidth() + ", imageHeight = " + imageView.getHeight());
Logger.i("wujunxuan", "bitmapWidth = " + imageBitmap.getWidth() + ", bitmapHeight = " + imageBitmap.getHeight() + ", size = " + imageBitmap.getAllocationByteCount() + "B");

xxhdpi
xhdpi
mdpi

可以看到,图片放在低dpi的drawable文件夹下,加载后的图片宽高是等比例放大的,并且内存占用也会变得很大,mdpi居然是xxhdpi内存的9倍,这无疑是一个巨坑啊。留意三种情况下bitmap的width或height比例,刚好是1:1.5:3。这个缩放比例是怎么确定的呢?
这时候,下意识的上网找资料。很遗憾,网上找了很久,没有找到相关的介绍资料。那只能查看源码了,看着看着,发现这块处理的相关代码AssetManager属于native层,是用C++写的,查阅起来比较麻烦,看了很久都没有找到对应代码。(这块找到源码的同学欢迎随时找我交流_
既然有比例数据,并且比例是有规律的,我们不妨从试验的数据上下手。细心观察,其实很容易发现其中的缩放逻辑。留意到文章开头那个表格,drawable-mdpi,drawable-xhdpi,drawable-xxhdpi对应的density值分别为1,2,3,我们一般也在这几个文件夹中存放所谓的1倍,2倍,3倍图。图片资源放在drawable-xhdpi中,系统默认该资源图片是2倍图,先将图片宽高除于2倍,然后再乘于手机自身的density值,这个逻辑也保证了不同分辨率的手机上,图片显示大小是接近一致的。即:

bitmap的宽(高) = 图片宽(高)分辨率  / 对应drawable文件夹的density值  * 手机自身的density值

上面的例子,通过代码查看到小米note1手机自身的density值是2.75dpi(非官方标准的3dpi),图片的宽是412,利用公式计算三个drawable文件夹下图片的宽:

//xxhdpi
412 / 3 * 2.75 = 378
//xhdpi
412 / 2 * 2.75 = 567
//mdpi
412 / 1 * 2.75 = 1133

计算出来的数值和上面的测试数值一致。可以证明那个计算公式是正确的。
排除机型的偶然性,拿另外一台手机(小米8)做同样的测试,将ic_android资源图片分别移动到三个drawable文件夹下。小米8的系统版本为android 9.0,分辨率为1080 * 2248,xdpi为403.411,ydpi为402.107,density为2.75。试验得到图片相关信息如下:
xxhdpi
xhdpi
mhdpi

果然不同的系统版本还真有点不同。发现一个有趣的现象,图片显示后,ImageView的宽高依然遵循上面的公式(手机屏幕宽度为1080px,所以ImageViev宽度最大为1080),但是bitmap的宽高在drawable-mdpi,drawable-xhdpi文件夹下却保持了图片本身的宽高(分辨率)。猜想,这个可能是Android在高系统版本做的一个优化。在小米note1中,图片自身分辨率是412 * 538,放在低dpi的drawable文件夹下,图片加载后的bitmap宽高大于图片自身的分辨率,但对于图片显示清晰度并没有提升,而Android中Bitmap的内存占用大小跟Bitmap的宽高是正比关系,这反而增加了内存占用。所以完全可以只缩放ImageView的宽高,而bitmap的宽高保持和图片自身一致,这样可以大大节省内存占用。如上面试验,小米note1在三个文件夹下的内存占用比是1:2.25:9,而在小米8上,内存占用是保持不变的。
另外,假设手机对应的drawable文件夹下没有该图片资源,又该怎样加载呢?这个比较好验证,只要在所有drawable文件夹下都放置同名称图片,然后依次删掉验证即可。得到的结论是:如果在对应的dpi文件夹下没有找到图片资源,系统会优先去更高密度的文件夹下找这张图片。例如,小米note1在drawable-xxhdpi没有找到该图片资源,会依次从drawable-xxxhdpi->drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi->drawable-nodpi->drawable文件夹里面找。
总结:
在我们项目中,为了减少apk包大小,只用一套图片资源二倍图,放在drawable-xhdpi下,这样做明显是不好的,既达不到手机本身的显示效果,还可能增加内存占用。目前市面上Android手机的分辨率基本上都是1080以上,dpi值基本都达到了xxhdpi,所以最佳放置图片资源的文件夹就是drawable-xxhdpi,这样可以保证最佳的显示效果,也可以减少一些Bitmap的内存占用。虽然这样会增加apk的包大小,但android可以使用webp格式的图片,事实上从xhdpi升级到xxhdpi并不会增加太多的包大小。
以上有哪些地方写得不对的,欢迎指出。_

END

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

(2019.5.29)关于drawable你可能不知道的一些事 的相关文章

随机推荐

  • 从0开始的 TypeScriptの十四:内置工具类型

    序 在之前的 从0开始的TypeScript 十三 中 已经对typescript的工具类型中的关键字infer extends keyof typeof in这些有所了解了 那么接下来为了使用更加方便 可以对typescript中内置的工
  • 【MATLAB】【函数介绍】cp2tform函数从控制点对推断空间变换

    官方链接 https ww2 mathworks cn help images ref cp2tform html s tid doc ta cp2tform函数的作用是根据传入的控制点对的坐标推断空间变换参数 函数语法如下 从控制点对推断
  • 【华为OD机试】服务失效判断【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 某系统中有众多服务 每个服务用字符串 只包含字母和数字 长度 lt 10 唯一标识 服务间可能有依赖关系 如A依赖B 则当B故障时导致A也故障 依赖具有传递性 如A依
  • QSettings修改配置文件的bug

    QT 使用QSettings类的bug 问题描述 原因分析 解决方案 问题描述 settings beginGroup mainwindow settings setValue size win gt size settings setVa
  • [转]shell中wait命令详解

    wait命令介绍 wait 作业指示或进程号 1 等待作业号或者进程号制定的进程退出 返回最后一个作业或进程的退出状态状态 如果没有制定参数 则等待所有子进程的退出 其退出状态为0 2 如果是shell中等待使用wait 则不会等待调用函数
  • Python 百分比计算

    遇到计算百分比的情况 查了一下 有两种方式 具体实现方式见下面代码 方式1 格式化为float 然后 处理成 格式 需要对分子 分母 100如下 percentList append 2f format member denominator
  • 51单片机蓝桥杯学习记录2独立按键

    51单片机蓝桥杯学习记录 独立按键这一块是比较简单的 就是读一下io口的高低电平 用普通函数写和用中断写还是有些不一样的 要注意的还是消抖和重复触发的问题 不说了 上码 author 风行天 time 2020 01 16 include
  • 初识云计算————虚拟化背景

    VM 虚拟机 Virtual Machine 虚拟化背景 美国环境保护署 EPA 报告的一组有趣的统计数据就证明了其好处 EPA 研究服务器和数据中心的能源效率时发现 实际上服务器只有 5 的时间是在工作的 在其他时间 服务器都处于 休眠
  • 字节和比特简单介绍

    字节 byte 字节为Byte 多数用B表示 字节为计算机中数据处理的基本单位 比特 bit 又称位 表示二进制位 为计算内部数据存储的最小单位 关系 1Byte 8bit 其他单位 1B Byte 字节 8bit 1KB Kilobyte
  • redis的数据类型string和list

    学习笔记记录 方便以后查看 一 Redis简介 1 关于关系型数据库和nosql数据库 关系型数据库是基于关系表的数据库 最终会将数据持久化到磁盘上 而nosql数据 库是基于特殊的结构 并将数据存储到内存的数据库 从性能上而言 nosql
  • 文献精讲-弱小目标检测-ADMD方法

    分享一下个人对于近期读过的一篇文献 Fast and robust small infrared target detection using absolute directional mean difference algorithm 的
  • Python打包的艺术(二)- Packaging Tools的昨天,今天,明天

    本文转载至 http blog chinaunix net uid 15174104 id 4036539 html 截至今年3月 Python的打包工具链处于的状态可以用 非常混乱 来形容 除了官方的distutils 第三方有setup
  • Python 包发布

    包结构 准备工作 twine 安装 pip3 install twine pypi 账号注册 https pypi org account register 注册成功后 记得到邮箱激活 否则无法使用 三个文件 setup py import
  • 【华为OD机试真题】MVP争夺战(python)100%通过率 超详细代码注释 代码解读

    华为OD机试真题 2022 2023 真题目录 点这里 华为OD机试真题 信号发射和接收 试读 点这里 华为OD机试真题 租车骑绿道 试读 点这里 MVP争夺战 知识点DFS搜索 时间限制 1s 空间限制 256MB 限定语言 不限 题目描
  • 【Java数据结构】Map和Set的使用

    章节目标 掌握Map Set及实际实现类 HashMap TreeMap HashSet TreeSet的使用 掌握 TreeMap 和 TreeSet 背后的数据结构搜索树的原理和简单实现 掌握 HashMap 和 HashSet 背后的
  • Ant Desgin Pro v6 正式版本企业级从零开始实现动态菜单与权限视频教程(28 个视频)

    Ant Desgin Pro v6 正式版本企业级从零开始实现动态菜单与权限视频教程 28 个视频 欢迎加入我们的Ant Design Pro v6视频教程 如果您正在寻找一门教您如何从零开始构建企业级动态菜单和权限管理的课程 那么这个视频
  • spring boot学习(十二)整合Druid

    Druid简介 Java程序很大一部分要操作数据库 为了提高性能操作数据库的时候 又不得不使用数据库连接池 Druid 是阿里巴巴开源平台上一个数据库连接池实现 结合了 C3P0 DBCP 等 DB 池的优点 同时加入了日志监控 Druid
  • linux上zookeeper单机搭建伪集群

    Zookeeper 一 下载zookeeper 解压到指定文件夹下 tar zxvf apache zookeeper 3 5 8 bin tar gz C home zk 改个名字 这个随意 mv apache zookeeper 3 5
  • OpenCV单目标跟踪:实现目标追踪的简易指南

    OpenCV单目标跟踪 实现目标追踪的简易指南 了解如何使用OpenCV库实现单目标跟踪 本指南介绍了OpenCV中主要的目标跟踪算法 包括CSRT KCF MIL GOTURN和DaSiamRPN 通过选择初始目标区域并使用适当的跟踪器
  • (2019.5.29)关于drawable你可能不知道的一些事

    转载公司内部论坛本人文章2019 5 29 导读 Android项目中 官方建议将不同分辨率的资源图片分别放在drawable mdpi drawable hdpi drawable xhdpi等不同的drawable文件夹下 以便不同分辨