(转载公司内部论坛本人文章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文件夹,然后布局引用这张图片。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210715131922885.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0pheWRlbng=,size_16,color_FFFFFF,t_70)
<?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>
代码很简单,运行一下程序,得到显示效果如下所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210715131952101.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0pheWRlbng=,size_16,color_FFFFFF,t_70)
小米note1的屏幕宽度分辨率是1080px,图片宽度是412px,所以图片宽度大约占据屏幕宽度的2/5左右,跟预期效果差不多。
接着,我将ic_android图片移动到drawable-xhdpi。注意,是移动不是复制。运行程序看一下效果。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210715132020726.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0pheWRlbng=,size_16,color_FFFFFF,t_70)
图片反而变大了?接着,再将图片移动到drawable-mdpi。运行程序,效果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210715132239307.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0pheWRlbng=,size_16,color_FFFFFF,t_70)
此时图片已经铺满了整个屏幕。将图片放在低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](https://img-blog.csdnimg.cn/20210715132303791.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0pheWRlbng=,size_16,color_FFFFFF,t_70)
![xhdpi](https://img-blog.csdnimg.cn/20210715132341462.png)
![mdpi](https://img-blog.csdnimg.cn/2021071513240456.png)
可以看到,图片放在低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](https://img-blog.csdnimg.cn/20210715132444117.png)
![xhdpi](https://img-blog.csdnimg.cn/2021071513250575.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0pheWRlbng=,size_16,color_FFFFFF,t_70)
![mhdpi](https://img-blog.csdnimg.cn/20210715132523542.png)
果然不同的系统版本还真有点不同。发现一个有趣的现象,图片显示后,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