Android的碎片化已经被喷了好多年,随着国内手机厂商的崛起,碎片化也越来越严重,根据OpenSignal的最新调查,2014年市面上有18796种不同的Android设备,作为开发者,一个无法回避的难题就是需要适配各种各样奇奇怪怪的机型。
设备机型不同必然也会导致屏幕大小和分辨率(Resolution)的不同,但是无论分辨率有多大,屏幕有多大,我们手指触控范围的大小不会发生变化,所以最优的适配方式应该是指定大小的控件在所有的设备上的显示都一样。
Android的官方文档对此也有明确的说明
When adding support for multiple screens, applications do not work directly with resolution; applications should be concerned only with screen size and density, as specified by the generalized size and density groups.
所以,适配应该与分辨率无关,只与屏幕大小和屏幕密度相关,以下是与单位相关的术语:
(1) Screen size 屏幕的尺寸,即对角线长度(单位inch-英寸)
(2) Resolution 分辨率,即屏幕的总像素点数(width * height)
(3) Screen Density屏幕密度,即每单位英寸包含的像素点数(dots/inches)
(4)Density-independent pixel (dp或dip) 密度无关像素,或者说是与屏幕密度无关的像素。标准是160dip,即1dp对应1个pixel,计算公式如:px = dp * (dpi / 160),屏幕密度越大,1dp对应的像素点越多。
一般表示是手机的实际物理尺寸,屏幕尺寸指屏幕的对角线的长度,单位是英寸,1英寸=2.54厘米。比如常见的屏幕尺寸有3.5、3.7、4.2、5.0、5.5、6.0等。
屏幕上显示的像素个数,单位尺寸内像素点越多,显示的图像就越清楚。单位是px,1px=1个像素点。
分辨率720*1280表示手机水平方向的像素为720,垂直方向为1280.
市场上主流分辨率有:480*800、 720*1280、 1080*1920(其他的早该淘汰了,忽略不计)。
表示屏幕每英寸(inch)的物理长度内包含的像素点数(dots),即屏幕像素密度。 单位是dpi( Dots Per Inch)
DPI(Dots Per Inch)是印刷行业中用来度量空间点密度用的,这个值是打印机每英寸可以喷的墨汁点数。计算机显示设备从打印机中借鉴了DPI的概念,由于计算机显示设备中的原子单位不是墨汁点而是像素,所以就创造了PPI(Pixels Per Inch),这个值是屏幕每英寸的像素数量,即像素密度(Screen density)。由于各种原因,目前PPI(主要是iOS)和DPI(主要在Android中)都会用在计算机显示设备的参数描述中,不过二者的意思是一样的,都是代表像素密度。
Android设备用DPI来表示屏幕密度(Density),屏幕密度大就表示一个Inch包含的Dot比较多。160DPI的屏幕就表示一个Inch包含160个Dot,320DPI的屏幕表示一个Inch有320个Dot,所以说Dot的大小是不固定的。高DPI屏幕显示的元素会比较精细(看起来会比较小),低DPI屏幕显示的元素相对来说就比粗糙(看起来会比较大)。
通常我们说一个设备是多少寸时,指的是屏幕对角线(Diagonal)长度是多少inch,所以用对角线的像素值(px)除以对角线长度(inch),就可以计算出PPI。
PPI 计算公式
为了简化适配工作,Android根据屏幕大小(Inch)和屏幕密度(DPI)对设备做了如下划分:
你需要把对应dpi的资源放到对应的目录就可以了,Android会根据dpi自动选择资源,目录规则如下:
drawable-mdpi/asset.png
drawable-hdpi/asset.png
drawable-xhdpi/asset.png
...
可以看出Android中mdpi与iOS中的1x multiplier所代表的PPI是一样的,xhdpi与iOS的2x multiplier所代表的PPI一样,如图:
既然有那么多不同分辨率、不同大小的屏幕,使用PX必然会导致适配困难,为了进一步简化适配工作,Android为我们提供了一个虚拟的像素单位 - DP 或者 DIP (Density-Independent pixel),当然也可以理解为 Device-Independent Pixel,即与设备屏幕密度无关的像素。为什么说是虚拟呢,因为它的大小不是一个物理(Phisical)值,而是由操作系统根据屏幕大小和密度动态渲染出来的。
PX跟DP之间的换算关系很简单:
px = dp * (dpi / 160)
举例来说,小米Pad的屏幕密度为326dpi,如果需要显示的图片大小为20dp,那么就需要提供一个 20*(326/160)=40px
的图片才能达到最佳显示效果,如果还要适配一个163dpi的屏幕,那么还需要再提供一个20*(163/160)=20px
的图片。
那么一个20dp的图片,在不同设备上的显示效果如何呢?我们以iPad为例来说明。
iPad 屏幕参数
iPad2 和 iPad Retina的物理尺寸都是 9.7 inch,不同的是分辨率和PPI,一个是1024x768 / 132ppi,另一个是2048x1536 / 264ppi,分别计算一下20dp对应多少inch
ipad2 = 20 * (132 / 160) * (7.9 / (math.sqrt(1024 * 1024 + 768 * 768)))
ipad_retina = 20 * (264 / 160) * (7.9 / (math.sqrt(2048 * 2048 + 1536 * 1536)))
计算结果都是0.1018359375,这就是dp的功能,它能保证在所有的设备上显示的大小都一样。
SP 全称是 Scale-independent Pixels,用于字体大小,其概念与DP是一致的,也是为了保持设备无关。因为Android用户可以根据喜好来调整字体大小,所以要使用sp来表示字体大小。
res目录下存放项目中的所有动画、图片、布局、字符串等资源。通常图标放在mipmap目录下,图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下, 下面是详细描述。
定义预先确定的动画资源。
Tween动画保存在res/anim/
目录下,通过R.anim
类来访问.
Frame动画保存在res/drawable/
目录下,通过 R.drawable
类来访问.
备注:动画分为两种,一种是Tween动画、还有一种是Frame动画。Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化;Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。
定义随着View状态而改变的颜色资源。
保存在 res/color/
目录下,通过 R.color
类来访问.
定义应用程序使用的图标资源。
保存在res/mipmap目录下,通过R.mipmap类来访问.
定义bitmap图像或通过XML来定义的图像资源.
保存在res/drawable/
目录下,通过R.drawable
类来访问.
定义应用程序UI的布局。
保存在res/layout/
目录下,通过R.layout
类来访问.
定义应用程序菜单的内容。
保存在res/menu/
目录下,通过R.menu
类来访问.
定义串、串数组、和数量字符串(zero, one, two, few, many, other)plurals (并且包含串的格式化以及风格).
保存在res/values/
目录下,通过R.string
, R.array
, 以及R.plurals
类来访问.
定义UI元素的外表与格式(look and format).
保存在res/values/
目录下,通过R.style
类来访问.
定义诸如布尔值、颜色、维度、ID、整数值、整数数组、TypedArray的值.
保存在res/values/
目录下,但每一种是通过不同的 R
子类(诸如 R.bool
, R.color, R.dimen
, R.id, R.integer
, R.array等)来访问.
参考资料:
[1] 详解Android开发中常用的 DPI/DP/SP, http://www.jianshu.com/p/913943d25829
[2] 移动开发需要知道的像素知识, http://weizhifeng.net/you-should-know-about-dpi.html
[3] 闲话Android 之 屏幕大小、pixel、分辨率、dpi、dip, http://www.cnblogs.com/zhangxinyan/p/3510604.html
[4] Andriod界面设计的分辨率和尺寸适配全攻略, http://www.25xt.com/appdesign/8693.html
[5] Resource Types, http://developer.android.com/intl/zh-cn/guide/topics/resources/available-resources.html
[6] More Resource Types, http://developer.android.com/intl/zh-cn/guide/topics/resources/more-resources.html
[7] Managing Projects Overview, http://developer.android.com/intl/zh-cn/tools/projects/index.html#ApplicationModules
开源做为Android优点的同时也是它的缺点,各种产商不同的硬件配置、不同程度对Framework层接口或实现的修改,早已让很多应用开发者头疼。做好兼容一直是Android应用开发的一件头等要事。想想在你的开发机上跑得欢的APK,在老板的手机上莫名地崩溃了,老板会是什么脸色?
因为某种“你懂的”原因,Android设备在国内是无法使用Google提供的服务的,这也致使很多手机产商肆无忌惮对Android Framework层大修特修,而且还不做CTS测试,反正通过测试也用不了Google商店。所以国内手机产商生产的某些手机会存在某些奇怪的兼容性问题,包括三星也有类似的问题。
在很多项目中,开发的大部份工作量并不是写功能代码,而是调式和修改兼容性问题。在面试中,分辨一个开发是否有丰富的项目经验,且在这些项目中是否承担主要责任,并不一定要问深入的知识,往往通过简单的细节问题会更能确认对方是否名副其实。
面试题:Android资源目录的读取顺序?
Android资源文件可以定义在不同分辨率、屏幕方向、语言等(甚至还有夜间模式),当我们的应用需要使用一个资源,这个资源(图片、Layout或者别的)可能在很多res下的子目录中都存在,那么Android系统是如何确认使用哪一个资源呢?
可以先看一下官方文档提供资源(https://developer.android.com/guide/topics/resources/providing-resources.html),了解清楚资源目录的配置和命名规则。
这就是一个Android应用的资源查找的顺序问题,其实简单说,在查找时会先去掉有冲突的资源目录(上图第1步),然后再按MCC、MNC、语言等指定的优先级进行查找,直到确认一个匹配资源。根据屏幕尺寸限定符选择资源时,如果没有更好的匹配资源,则系统将使用专为小于当前屏幕的屏幕而设计的资源。
图片放错目录会产生的问题吗?
这一点可能很多人都不会注意,觉得只要往一个drawable目录中放了需要的资源就好了。而我们可以自己做一个简单的测试,把同一个图片资源放在不同的dpi目录,会发现它们使用的内存是不一样的。简单说就是高密度(density)的系统去使用低密度目录下的图片资源时,会将图片长宽自动放大以去适应高密度的精度,当然图片占用的内存会更大。
所以如果能提各种dpi的对应资源那是最好,可以达到较好内存使用效果。如果提供的图片资源有限,那么图片资源应该尽量放在高密度文件夹下,这样可以节省图片的内存开支。
mipmap
在使用Android Studio(应该是从1.1版本开始)创建Android应用项目时,常常会看到系统把ic_launcher.png图标放在了mipmap-xxhdpi目录下了。那么这个mipmap是什么意思呢?和drawable的对应dpi目录有什么区别呢?
我们知道,drawable文件夹是存放一些xml(如selector)和图片,Android会根据设备的屏幕密度(density)自动去对应的drawable文件夹匹配资源文件。
那么mipmap这个目录有什么用呢?
MIP来源于拉丁文中的multum in parvo,意为在一个小空间里的多数。MIP map(有时候拼写成mipmap)是一种电脑图形图像技术,用于在三维图像的二维代替物中达到立体感效应。
Android对放在mipmap目录的图标会忽略屏幕密度,会去尽量匹配大一点的,然后系统自动对图片进行缩放,从而优化显示和节省资源(使用上面说的mipmap技术)。就目前的版本来说,mipmap也没有完全取代drawable的意思,为了更好的显示效果,官方建议如下类型的图片资源可以放到mipmap目录。
Launcher icons.
Action bar and tab icons.
Notification icons
drawable-nodpi文件夹
这个文件夹是一个密度无关的文件夹,放在这里的图片系统就不会对它进行自动缩放,原图片是多大就会实际展示多大。但是要注意一个加载的顺序,drawable-nodpi文件夹是在匹配密度文件夹和更高密度文件夹都找不到的情况下才会去这里查找图片的,因此放在drawable-nodpi文件夹里的图片通常情况下不建议再放到别的文件夹里面。
res/raw和assets的区别
这两个目录下的文件都会被打包进APK,并且不经过任何的压缩处理。
assets与res/raw不同点在于,assets支持任意深度的子目录,这些文件不会生成任何资源ID,只能使用AssetManager按相对的路径读取文件。如需访问原始文件名和文件层次结构,则可以考虑将某些资源保存在assets目录下。
记得之前的版本(Android 2.2)对放在这两个目录的文件还有大小的限制,1M这样吧,之后的版本没有这个限制了。
和美工的关系
很多时候,UI设计师并不太了解Android的DPI,也不太清楚每种DPI对应的ICON规格。这时Android的开发就需要告诉他们一些规范,并结合自己的在真机上的测试经验给予符合项目的ICON尺寸。对于每种密度下的ICON应该设计成什么尺寸其实Android也是给出了最佳建议,ICON的尺寸最好不要随意设计,因为过低的分辨率会造成图标模糊,而过高的分辨率只会徒增APK大小。
密度 |
建议尺寸 |
mipmap-mdpi |
48 * 48 |
mipmap-hdpi |
72 * 72 |
mipmap-xhdpi |
96 * 96 |
mipmap-xxhdpi |
144 * 144 |
mipmap-xxxhdpi |
192 * 192 |
(1)drawable-hdpi里面存放高分辨率的图片,如WVGA (480x800),FWVGA (480x854)
(2)drawable-mdpi里面存放中等分辨率的图片,如HVGA (320x480)
(3)drawable-ldpi里面存放低分辨率的图片,如QVGA (240x320)
ldpi:240x320