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一样,如图:
第四、Andriod中的单位DP与SP
4.1 DP
既然有那么多不同分辨率、不同大小的屏幕,使用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的功能,它能保证在所有的设备上显示的大小都一样。
4.2 SP
SP 全称是 Scale-independent Pixels,用于字体大小,其概念与DP是一致的,也是为了保持设备无关。因为Android用户可以根据喜好来调整字体大小,所以要使用sp来表示字体大小。
第五、Andriod Studio中的资源
res目录下存放项目中的所有动画、图片、布局、字符串等资源。通常图标放在mipmap目录下,图片放在drawable目录下,布局放在layout目录下,字符串放在values目录下, 下面是详细描述。
5.1 动画资源(Animation Resources)
定义预先确定的动画资源。
Tween动画保存在res/anim/
目录下,通过R.anim
类来访问.
Frame动画保存在res/drawable/
目录下,通过 R.drawable
类来访问.
备注:动画分为两种,一种是Tween动画、还有一种是Frame动画。Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化;Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。
5.2 颜色状态列表资源
定义随着View状态而改变的颜色资源。
保存在 res/color/
目录下,通过 R.color
类来访问.
5.3 icon资源
定义应用程序使用的图标资源。
保存在res/mipmap目录下,通过R.mipmap类来访问.
5.4 Drawable资源
定义bitmap图像或通过XML来定义的图像资源.
保存在res/drawable/
目录下,通过R.drawable
类来访问.
5.5 布局资源
定义应用程序UI的布局。
保存在res/layout/
目录下,通过R.layout
类来访问.
5.6 菜单资源
定义应用程序菜单的内容。
保存在res/menu/
目录下,通过R.menu
类来访问.
5.7 String资源
定义串、串数组、和数量字符串(zero, one, two, few, many, other)plurals (并且包含串的格式化以及风格).
保存在res/values/
目录下,通过R.string
, R.array
, 以及R.plurals
类来访问.
5.8 样式(Style)资源
定义UI元素的外表与格式(look and format).
保存在res/values/
目录下,通过R.style
类来访问.
5.9 更多的资源类型
定义诸如布尔值、颜色、维度、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
1.1分辨率
是指屏幕上有横竖各有多少个像素
1.2屏幕尺寸
指的是手机实际的物理尺寸,比如常用的2.8英寸,3.2英寸,3.5英寸,3.7英寸
android将屏幕大小分为四个级别(small,normal,large,and extra large)。
1.3屏幕密度
每英寸像素数
手机可以有相同的分辨率,但屏幕尺寸可以不相同,
Diagonal pixel表示对角线的像素值(=),DPI=933/3.7=252
android将实际的屏幕密度分为四个通用尺寸(low,medium,high,and extra high)
一般情况下的普通屏幕:ldpi是120dpi,mdpi是160dpi,hdpi是240dpi,xhdpi是320dpi
对于屏幕来说,dpi越大,屏幕的精细度越高,屏幕看起来就越清楚
1.4密度无关的像素(Density-independent pixel——dip)
dip是一种虚拟的像素单位
dip和具体像素值的对应公式是dip/pixel=dpi值/160,也就是px = dp * (dpi / 160)
当你定义应用的布局的UI时应该使用dp单位,确保UI在不同的屏幕上正确显示。
手机屏幕分类和像素密度的对应关系如表1所示
手机尺寸分布情况(http://developer.android.com/resources/dashboard/screens.html)如图所示,
目前主要是以分辨率为800*480和854*480的手机用户居多
从以上的屏幕尺寸分布情况上看,其实手机只要考虑3-4.5寸之间密度为1和1.5的手机
2、android多屏幕支持机制
Android的支持多屏幕机制即用为当前设备屏幕提供一种合适的方式来共同管理并解析应用资源。
Android平台中支持一系列你所提供的指定大小(size-specific),指定密度(density-specific)的合适资源。
指定大小(size-specific)的合适资源是指small, normal, large, and xlarge。
指定密度(density-specific)的合适资源,是指ldpi (low), mdpi (medium), hdpi (high), and xhdpi (extra high).
Android有个自动匹配机制去选择对应的布局和图片资源
1)界面布局方面
根据物理尺寸的大小准备5套布局:
layout(放一些通用布局xml文件,比如界面顶部和底部的布局,不会随着屏幕大小变化,类似windos窗口的title bar),
layout-small(屏幕尺寸小于3英寸左右的布局),
layout-normal(屏幕尺寸小于4.5英寸左右),
layout-large(4英寸-7英寸之间),
layout-xlarge(7-10英寸之间)
2)图片资源方面
需要根据dpi值准备5套图片资源:
drawable:主要放置xml配置文件或者对分辨率要求较低的图片
drawalbe-ldpi:低分辨率的图片,如QVGA (240x320)
drawable-mdpi:中等分辨率的图片,如HVGA (320x480)
drawable-hdpi:高分辨率的图片,如WVGA (480x800),FWVGA (480x854)
drawable-xhdpi:至少960dp x 720dp
Android有个自动匹配机制去选择对应的布局和图片资源。
系统会根据机器的分辨率来分别到这几个文件夹里面去找对应的图片。
在开发程序时为了兼容不同平台不同屏幕,建议各自文件夹根据需求均存放不同版本图片。
3、AndroidManifest.xml 配置
android从1.6和更高,Google为了方便开发者对于各种分辨率机型的移植而增加了自动适配的功能
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true"/>
3.1是否支持多种不同密度的屏幕
android:anyDensity=["true" | "false"]
如果android:anyDensity="true"
指应用程序支持不同密度,会根据屏幕的分辨率自动去匹配。
如果android:anyDensity="false"
应用程序支持不同密度,系统自动缩放图片尺寸和这个图片的坐标。具体解释一下系统是如何自动缩放资源的。
例如我们在hdpi,mdpi,ldpi文件夹下拥有同一种资源,那么应用也不会自动地去相应文件夹下寻找资源,这种情况都是出现在高密度,以及低密度的手机上,比如说一部240×320像素的手机,
如果设置android:anyDensity="false",Android系统会将240 x 320(低密度)转换为 320×480(中密度),这样的话,应用就会在小密度手机上加载mdpi文件中的资源。
3.2是否支持大屏幕
android:largeScreens=["true" | "false"]
如果在声明不支持的大屏幕,而这个屏幕尺寸是larger的话,系统使用尺寸为("normal")和密度为("medium)显示,
不过会出现一层黑色的背景。
3.3是否支持小屏幕
android:smallScreens=["true" | "false"]
如果在声明不支持的小屏幕,而当前屏幕尺寸是smaller的话,系统也使用尺寸为("normal")和密度为("medium)显示
如果应用程序能在小屏幕上正确缩放(最低是small尺寸或最小宽度320dp),那就不需要用到本属性。否则,就应该为最小屏幕宽度标识符设置本属性
来匹配应用程序所需的最小尺寸。
4、Android提供3种方式处理屏幕自适应
4.1预缩放的资源(基于尺寸和密度去寻找图片)
1)如果找到相应的尺寸和密度,则利用这些图片进行无缩放显示。
2)如果没法找到相应的尺寸,而找到密度,则认为该图片尺寸为 "medium",利用缩放显示这个图片。
3)如果都无法匹配,则使用默认图片进行缩放显示。默认图片默认标配 "medium" (160)。
4.2自动缩放的像素尺寸和坐标(密度兼容)
1)如果应用程序不支持不同密度android:anyDensity="false",系统自动缩放图片尺寸和这个图片的坐标。
2)对于预缩放的资源,当android:anyDensity="false",也不生效。
3)android:anyDensity="false",只对密度兼容起作用,尺寸兼容没效果
4.3兼容更大的屏幕和尺寸(尺寸兼容)
1)对于你在声明不支持的大屏幕,而这个屏幕尺寸是normal的话,系统使用尺寸为 ("normal")和密度为("medium)显示。
2.)对于你在声明不支持的大屏幕,而这个屏幕尺寸是larger的话,系统同样使用尺寸为("normal")和密度为("medium)显示,
不过会出现一层黑色的背景。
5、Android系统自动适配技巧
Android系统采用下面两种方法来实现应用的自动适配:
1)布局文件中定义长度的时候,最好使用wrap_content,fill_parent, 或者dp 进行描述,这样可以保证在屏幕上面展示的时候有合适的大小
2)为不同屏幕密度的手机,提供不同的位图资源,可以使得界面清晰无缩放。
对应bitmap 资源来说,自动的缩放有时会造成放大缩小后的图像变得模糊不清,这是就需要应用为不同屏幕密度配置提供不同的资源:为高密度的屏幕提供高清晰度的图像等。
3)不要使用AbsoluteLayout
4)像素单位都使用DIP,文本单位使用SP
6、在代码中获取屏幕像素、屏幕密度
DisplayMetrics metric = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metric);
int width = metric.widthPixels; // 屏幕宽度(像素)
int height = metric.heightPixels; // 屏幕高度(像素)
float density = metric.density; // 屏幕密度(0.75 / 1.0 / 1.5)
int densityDpi = metric.densityDpi; // 屏幕密度DPI(120 / 160 / 240)
7、 一般多分辨率处理方法及其缺点
7.1 图片缩放
基于当前屏幕的精度,平台自动加载任何未经缩放的限定尺寸和精度的图片。如果图片不匹配,平台会加载默认资源并且在放大或者缩小之后可以满足当前界面的显示要求。例如,当前为高精度屏幕,平台会加载高精度资源(如HelloAndroid中drawable-hdpi 中的位图资源),如果没有,平台会将中精度资源缩放至高精度,导致图片显示不清晰。
7.2 自动定义像素尺寸和位置
如果程序不支持多种精度屏幕,平台会自动定义像素绝对位置和尺寸值等,这样就能保证元素能和精度160 的屏幕上一样能显示出同样尺寸的效果。例如,要让WVGA 高精度屏幕和传统的HVGA 屏幕一样显示同样尺寸的图片,当程序不支持时,系统会对程序慌称屏幕分辨率为320×480,在(10,10)到(100,100)的区域内绘制图形完成之后,系统会将图形放大到(15,15)到(150,150)的屏幕显示区域。
7.3 兼容更大尺寸的屏幕
当前屏幕超过程序所支持屏幕的上限时,定义supportsscreens元素,这样超出显示的基准线时,平台在此显示黑色的背景图。例如,WVGA 中精度屏幕上,如程序不支持这样的大屏幕,系统会谎称是一个320×480 的,多余的显示区域会被填充成黑色。
7.4 采用OpenGL 动态绘制图片
Android 底层提供了OpenGL 的接口和方法,可以动态绘制图片,但是这种方式对不熟悉计算机图形学的开发者来讲是一个很大的挑战。一般开发游戏,采用OpenGL 方式。
7.5 多个apk 文件
Symbian 和传统的J2ME 就是采用这种方式,为一款应用提供多个分辨率版本,用户根据自己的需求下载安装相应的可执行文件。针对每一种屏幕单独开发应用程序不失为一种好方法,但是目前Google Market 对一个应用程序多个分辨率版本的支持还不完善,开发者还是需要尽可能使用一个apk 文件适应多个分辨率。
本文欢迎转载,但请注明作者与出处:
作者:流星
出处:http://blog.sina.com.cn/staratsky