现在的位置: 首页 > 综合 > 正文

屏幕密度掺入BitmapFactory里decodeFile与decodeResource的差异

2018年09月26日 ⁄ 综合 ⁄ 共 5397字 ⁄ 字号 评论关闭

如果想要decodeFile与decodeResource一样,我这里只需要改变Options的参数,将

op.inDensity = TypedValue.DENSITY_NONE;
op.inTargetDensity = TypedValue.DENSITY_NONE ;

设置成上面的就可以了。代表不进行像素的

inDensity——用于位图的像素压缩比
inTargetDensity——用于目标位图的像素压缩比(要生成的位图)

最近在项目中遇到的问题,一种是放在drawable下的资源文件,一种为下载后复制到data/data目录下的文件,同样的分辨率,放入相同layout配置的ImageView中,显示的大小却不一样。查看代码后,逻辑并无不同的地方,唯一的区别是:读取图片文件资源的时候,读drawable下用的decodeResource方法,读data/data下用的decodeFile方法。用分辨率320px*569px的手机执行的效果是,decodeResource方法读出的图片比decodeFile大一点,debug出的为放大了1.5倍。就现象而言,decodeResource方法自己似乎做了scale处理。

于是翻看源码,得到执行两种方法时的调用次序:

decodeFile(String pathName) ->decodeFile(pathName, null) -> decodeStream(stream, null, opts)

decodeResource(res, id) -> decodeResource(res, id, null) -> decodeResourceStream(res, value, is, null, opts) -> decodeStream(is, pad, opts)

对比各自处理,不同点发生在decodeResource会调用的decodeResourceStream方法中

  1. /** 
  2.  * Decode a new Bitmap from an InputStream. This InputStream was obtained from 
  3.  * resources, which we pass to be able to scale the bitmap accordingly. 
  4.  */  
  5. public static Bitmap decodeResourceStream(Resources res, TypedValue value,  
  6.         InputStream is, Rect pad, Options opts) {  
  7.   
  8.     if (opts == null) {  
  9.         opts = new Options();  
  10.     }  
  11.   
  12.     if (opts.inDensity == 0 && value != null) {  
  13.         final int density = value.density;  
  14.         if (density == TypedValue.DENSITY_DEFAULT) {  
  15.             opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;  
  16.         } else if (density != TypedValue.DENSITY_NONE) {  
  17.             opts.inDensity = density;  
  18.         }  
  19.     }  
  20.       
  21.     if (opts.inTargetDensity == 0 && res != null) {  
  22.         opts.inTargetDensity = res.getDisplayMetrics().densityDpi;  
  23.     }  
  24.       
  25.     return decodeStream(is, pad, opts);  
  26. }  


这段逻辑,比decodeFile方法多了个Options属性值的设置,自己手头手机的实验结果来看,

opts.inDensity设成了DisplayMetrics.DENSITY_DEFAULT    为160

opts.inTargetDensity设成res.getDisplayMetrics().densityDpi    为240

就字面意思,inTargetDensity正好是inDensity的1.5倍,查了SDK如下

opts.inDensity的解释

  1. The pixel density to use for the bitmap. This will always result in the returned bitmap having a density set for it (see Bitmap.setDensity(int)).   
  2. In addition, if inScaled is set (which it is by default} and this density does not match inTargetDensity, then the bitmap will be scaled to the target   
  3. density before being returned.  

opts.inTargetDensity的解释

  1. The pixel density of the destination this bitmap will be drawn to. This is used in conjunction with inDensity and inScaled to determine if and how to   
  2. scale the bitmap before returning it.   


总之

inDensity——用于位图的像素压缩比
inTargetDensity——用于目标位图的像素压缩比(要生成的位图)

inScaled——设置为true时进行图片压缩,从inDensity到inTargetDensity。

由上可知,decodeResourceStream方法根据手机屏幕的密度有一个缩放图片的过程,而decodeFile不会自动处理。为了避开这种差异性,将decodeFile方法的调用替换掉,先得到图片文件的InputStream,再直接调用decodeResourceStream,模拟decodeResource的操作。

其实,decodeResource中对图片文件的缩放可以理解成图片像素px与屏幕dip显示的转换,可为什么decodeFile没有做这一步?暂时不知道原因,可能是decodeResource多用于drawable下本app资源的读取,涉及到不同屏幕密度的区分吧。

另外关于DisplayMetrics的屏幕密度Density,官方的解释:

The logical density of the display. This is a scaling factor for the Density Independent Pixel unit, where one DIP is one pixel on an approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen), providing the baseline of the system's display. Thus
on a 160dpi screen this density value will be 1; on a 120 dpi screen it would be .75; etc.

This value does not exactly follow the real screen size (as given by xdpi andydpi,
but rather is used to scale the size of the overall UI in steps based on gross changes in the display dpi. For example, a 240x320 screen will have a density of 1 even if its width is 1.8", 1.3", etc. However, if the screen resolution is increased to 320x480
but the screen size remained 1.5"x2" then the density would be increased (probably to 1.5).

上面一段不是太明白,自己的理解:屏幕密度与手机硬件有关,而这个Density是一个倍率值,相同的分辨率屏幕尺寸越小,密度就越大,dip与密度有关系,官方解释也说了“在160dip的屏幕上,1dip=1px”。它常用于程序中dip,px之间的转换

  1. public static int px2dip(Context context, float pxValue){  
  2.     final float scale = context.getResources().getDisplayMetrics().density;  
  3.     return (int)(pxValue / scale + 0.5f);  
  4. }  
  5.   
  6. public static int dip2px(Context context, float dipValue){  
  7.     final float scale = context.getResources().getDisplayMetrics().density;  
  8.     return (int)(dipValue * scale + 0.5f);  
  9. }  



关于dip,px,屏幕密度的理解,可结合下面的内容理解(注:以下内容均转载自 http://blog.csdn.net/cgaanns/article/details/6180618

  1. px(pixels)——像素:不同的设备显示效果相同,一般我们HVGA代表320x480像素,这个用的比较多。  
  2.   
  3. dip(device independent pixels)——设备独立像素:这个和设备硬件有关,一般哦我们为了支持WCGA、HVGA和QVGA推荐使用这个,不依赖于像素。等同于dp。  
  4.   
  5. sp(scaled pixels—best for text size)——带比例的像素。  
  6.   
  7. pt(points)——磅:1pt = 1/72英寸  
  8.   
  9. in(inches)——英寸  
  10.   
  11. mm(millimeters)——毫米  
  12.   
  13. sp由于是放大像素,主要是用于字体显示,由此根据google的建议,TextView的字体大小最好用sp做单位,而且查看TextView的源码可知Android默认使用水平作为字号单位。  
  14.   
  15. 在Android中最常用到的还是px和dip。但是这两个之间到底有什么区别呢?  
  16.   
  17. 在HVGA屏density=160;QVGA屏density=120;WVGA屏density=240;WQVGA屏density=120 density值表示每英寸有多少个显示点,与分辨率是两个概念。  
  18. 不同density下屏幕分辨率信息,以480dip*800dip的 WVGA(density=240)为例。  
  19.   
  20.   
  21. density=120时 屏幕实际分辨率为240px*400px (两个点对应一个分辨率)  
  22. 状态栏和标题栏高各19px或者25dip   
  23. 横屏是屏幕宽度400px 或者800dip,工作区域高度211px或者480dip  
  24. 竖屏时屏幕宽度240px或者480dip,工作区域高度381px或者775dip  
  25.   
  26. density=160时 屏幕实际分辨率为320px*533px (3个点对应两个分辨率)  
  27. 状态栏和标题栏高个25px或者25dip   
  28. 横屏是屏幕宽度533px 或者800dip,工作区域高度295px或者480dip  
  29. 竖屏时屏幕宽度320px或者480dip,工作区域高度508px或者775dip  
  30.   
  31. density=240时 屏幕实际分辨率为480px*800px (一个点对于一个分辨率)  
  32. 状态栏和标题栏高个38px或者25dip   
  33. 横屏是屏幕宽度800px 或者800dip,工作区域高度442px或者480dip  
  34. 竖屏时屏幕宽度480px或者480dip,工作区域高度762px或者775dip  
  35.   
  36. apk的资源包中,当屏幕density=240时使用hdpi 标签的资源  
  37. 当屏幕density=160时,使用mdpi标签的资源  
  38. 当屏幕density=120时,使用ldpi标签的资源。  
  39. 不加任何标签的资源是各种分辨率情况下共用的。  

抱歉!评论已关闭.