本文首发于个人微信公众号:Myoung。 
   欢迎大家订阅,点赞、留言。 
 mipmap图片
  如果做 App 开发的话,AS 会在项目的 res 目录下,创建几个以 mipmap 开头的文件夹: 
  
      根据 Android 官方描述: 
 - Mipmap 仅仅用于存放 App 启动图标。
- AS 提供了一个快捷工具 Image Asset Studio 用于生成图标。
- Image Asset Studio会生成mdpi、hdpi、xhdpi、xxhdpi、xxxhdpi五种尺寸的图标。
  图标最好不要随意定义尺寸,分辨率过低会模糊,过高徒增APK包大小。各种密度下的图标建议尺寸为 
 |         密度        |         建议尺寸        | 
|         mdpi        |         48*48        | 
|         hdpi        |         72*72        | 
|         xhdpi        |         96*96        | 
|         xxhdpi        |         144*144        | 
|         xxxhdpi        |         192*192        | 
  如果要上传到Google Play,还需要一张512*512的图片用于Google Play Store。 
 drawable图片
相关概念
- dpi
  每英寸点数,全称dots per inch。用来表示屏幕密度,即屏幕物理区域中的像素量。高密度屏幕比低密度屏幕在给定物理区域的像素要多。 
 - dp
  即dip,全称device independent pixel。设备独立像素,是一种虚拟像素单位,用于以密度无关方式表示布局维度或位置,以确保在不同密度的屏幕上正常显示UI。在160dpi的设备上,1dp=1px。 
 - density
  设备的逻辑密度,是dip的缩放因子。以160dpi的屏幕为基线,density=dpi/160。 
   getResources().getDisplayMetrics().density 
 - sp
  缩放独立像素,全称scale independent pixel。类似于dp,一般用于设置字体大小,可以根据用户设置的字体大小偏好来缩放。 
 六种通用密度
  Android系统为了简化开发者为多种屏幕设计用户界面的方式,Android将实际屏幕尺寸和范围作了通用规定,称作“根据可用屏幕宽度管理屏幕尺寸的新技术”。 
   六种通用密度为: 
 |         密度        |         dpi范围        | 
|         ldpi(低)        |         ~120dpi        | 
|         mdpi(中)        |         ~160dpi        | 
|         hdpi(高)        |         ~240dpi        | 
|         xhdpi(超高)        |         ~320dpi        | 
|         xxhdpi(超超高)        |         ~480dpi        | 
|         xxxhdpi(超超超高)        |         ~640dpi        | 
  通用密度是以mdpi(中)为基线配置的,此基线基于第一代Android设备(T-Mobile G1)的屏幕配置。 
 Android系统适配原则
  目的: 
 - Android 为了更好地优化应用在不同屏幕密度下的用户体验。
  提供的方法: 
 - 在项目的res目录下可以创建 drawab-[density]目录。
- density为6种通用密度名。
- Android系统会依据特定的原则来查找各drawable目录下的图片。
  开发者: 
 - 在进行APP开发时,针对不同的屏幕密度,将图片放置于对应的drawable-[density]目录。
  原则: 
  
      总结: 
   优先匹配最适合的图片 → 查找密度高的目录(升序)→ 查找nodpi目录 → 查找密度低的目录(降序)。 
   Android 在查找到图片后会根据当前设备的dpi对drawable-[density]目录中的图片进行缩放,那么什么情况下图片被放大,什么情况下图片被缩小呢? 
 图片的缩放
缩放规律
- 匹配目录
  符合当前设备dpi的drawable目录。比如: 
 - dpi=320,匹配目录为drawable-xhdpi。
- dpi=150,匹配目录为drawable-mdpi。
- 图片的缩放规律
- 如果图片所在目录为匹配目录,则图片会根据设备dpi做适当的缩放调整。
- 如果图片所在目录dpi低于匹配目录,那么该图片被认为是为低密度设备需要的,现在要显示在高密度设备上,图片会被放大。
- 如果图片所在目录dpi高于匹配目录,那么该图片被认为是为高密度设备需要的,现在要显示在低密度设备上,图片会被缩小。
- 如果图片所在目录为drawable-nodpi,则无论设备dpi为多少,保留原图片大小,不进行缩放。
缩放倍数
- 缩放倍数
  以mdpi为基线,各密度目录下的放大倍数(即缩放因子density)如下: 
 |         密度        |         放大倍数        | 
|         ldpi        |         0.75        | 
|         mdpi        |         1.0        | 
|         hdpi        |         1.5        | 
|         xhdpi        |         2.0        | 
|         xxhdpi        |         3.0        | 
|         xxxhdpi        |         4.0        | 
- 更通用的缩放倍数计算公式
  对于很多设备,其dpi并不刚好是六种通用密度最大dpi,这种情况下,各drawable-[density]目录下的图片放大倍数的计算公式: 
  
    验证
- 验证缩放
  设备:1080×1920 - 420dpi 
  
    - 验证倍数
  材料:在Sketch里简单绘制一张图,分别导出一倍图(1x)和三倍图(3x)。大小如下: 
  
      设备:1080×1920 - 420dpi 
   方法:分别把1倍图和3倍图置于drawable-mdpi和drawable-xxhdpi目录下 
   结果: 
  
    - drawable-mdpi
  scale = 420/160 
   1x:98 × scale = 98 × 420/160 = 257 
   3x:294 × scale = 294 × 420/160 = 772 
 - drawable-xxhdpi
  scale = 420/480 
   1x:98 × scale = 98 × 420/480 = 86 
   3x:294 × scale = 294 × 420/480 = 257 
   像素没有小数。 
   drawable-xxhdpi目录下,图片的宽高更接近于图片实际大小。 
   那么问题来了,Android上图片的缩放算法是怎样的呢? 
 Android图片压缩
质量压缩
  更改图片的显示质量的一种压缩方式。 
 基本概念
- 三原色:RGB
- RGB通道
- alpha通道
- 位深
- 8位:2^8 = 2^2(B) 2^3(G) 2^3(R) = 256
- 16位:2^16 = 2^5(B) 2^6(G) 2^5(R) = 65536
- 24位:2^24 = 2^8(B) 2^8(G) 2^8(R) = 16777216
- 32位:alpha透明度 + 24位
  位深越高,色彩越逼真。 
  
    压缩原理
  不改变像素的前提下,通过改变位深来改变图片文件大小。 
 - 色彩逼真度下降,即失真(质量下降)。
- 压缩比 1~100。
- 压缩有三种格式:.JPEG、.PNG、.WEBP。
  Android实现质量压缩: 
 //quality 为0~100,0表示最小体积,100表示最高质量,对应体积也是最大 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
  其中,PNG无法压缩。 
   Android代码流程: 
   Bitmap(Java)→ Bitmap_compress(cpp)→ Skia 图形引擎 → 第三方库 
 - libpng
- libjpeg
- libgif
  再进一步的话,Android使用的其实是 Standard Huffman 算法。 
 - Huffman table
- optimize_coding
尺寸压缩
  通过改变图片的像素尺寸来压缩图片的方式,即重新采样。 
 - 上采样:方法图像
- 下采样:缩小图像
  Android中图像采用的两种方式: 
 - 邻近采样(Nearest Neighbour Resampling)
- 双线性采样(Bilinear Resampling)
邻近采样
  邻近采样主要就是 inSampleSize 的使用: 
 BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap compressBitmap = BitmapFactory.decodeFile("/sdcard/sample.png", options);   Google官方对 inSampleSize 的注释: 
 /** * If set to a value > 1, requests the decoder to subsample the original * image, returning a smaller image to save memory. The sample size is * the number of pixels in either dimension that correspond to a single * pixel in the decoded bitmap. For example, inSampleSize == 4 returns * an image that is 1/4 the width/height of the original, and 1/16 the * number of pixels. Any value <= 1 is treated the same as 1. Note: the * decoder uses a final value based on powers of 2, any other value will * be rounded down to the nearest power of 2. */
  简单说,就是多个像素采样为一个像素。 
   举个栗子,如果 inSampleSize = 2,直接采样其中一个像素,另一个像素舍弃。 
  
    双线性采样
  Android中双线性采样主要是通过 Matrix 来使用的: 
 Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/sample.png");
Bitmap compress = Bitmap.createScaledBitmap(bitmap, bitmap.getWidth()/2, bitmap.getHeight()/2, true); Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/sample.png");
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bitmap, 0, 0, bit.getWidth(), bit.getHeight(), matrix, true);   双线性采样内部使用的是双线性内插值算法。 
   每个位置像素使用 2✖️2 的源像素按相对位置进行两个方向的线性插值,根据位置取对应的权重,计算后得到目标像素。 
  
      x方向: 
  
      y方向: 
  
      最终插值结果: 
  
    对比
- 邻近采样
- 简单粗暴,效率高
- 失真较高
- 因为是像素舍弃,会导致锯齿明显
- 双线性采样
- 计算量较大,效率相对低些
- 保真相对较好一些
- 有一定的抗锯齿能力
- 具有低通滤波性质
其他算法
- Bicubic Resampling
- Lanczos Resampling



 京公网安备 11010502036488号
京公网安备 11010502036488号