android下图片资源从源图到显示需要经历三个步骤:使用aapt工具处理,图片解码,绘制。让我们按顺序一一探讨。

android开发指南对位图资源有如下描述:

Bitmap files may be automatically optimized with lossless image compression by theaapttool during the build process. For example, a true-color PNG that does not require more than 256 colors may be converted to an 8-bit PNG with a color palette. This will result in an image of equal quality but which requires less memory. So be aware that the image binaries placed in this directory can change during the build. If you plan on reading an image as a bit stream in order to convert it to a bitmap, put your images in theres/raw/folder instead, where they will not be optimized.

这段描述很模糊,我们关注的抖动处理更是完全没有提及,但它隐约提到这里的处理是无损压缩。

google搜索发现国外有位老大对此问题进行了探讨,它说android根据图片是否带alpha通道进行不同处理,没有alpha通道的24位图片转换成565格式,并在转换过程中进行抖动处理,带alpha通道的图片则统一转换为32位格式。

人言不可尽信,何况那位老大完全没有提到他这信息是哪来的,也没提到他在哪个版本上测试的,咱们还是自己研究一下aapt源码。

aapt工具源码位于frameworks/base/tools/aapt目录下,其中images.cpp负责处理图片,具体处理函数是preProcessImage,它目前只处理png图片,analyze_image函数对源图片进行格式分析并设定目标格式,它的工作在注释中描述得很清楚,如下:

// Scan the entire image and determine if:
// 1. Every pixel has R == G == B (grayscale)
// 2. Every pixel has A == 255 (opaque)
// 3. There are no more than 256 distinct RGBA colors

很明显,这里的操作都是为压缩服务的,设置好相关参数后,它还是调用libpng的相关函数重新压缩成png格式,而png格式本身并不支持565格式,也没有看到哪里设置了抖动选项,而libpng并不是专门为aapt服务的,说它默认设置抖动选项未免太过牵强附会。至此基本可以确定至少我这个版本的aapt并不会进行那位老大说的抖动处理和格式转换,但write_png函数中这段注释还是让人有点不安。

// If the image is a 9-patch, we need to preserve it as a ARGB file to make
// sure the pixels will not be pre-dithered/clamped until we decide they are

为以防万一,我祭出了最后法宝,写一段测试程序进行实际测试。

首先用photoshop制作一张渐变的图片,以保证抖动处理会有明显的变化,存为24位bmp格式,再转存一张png格式的,放入android工程中作为目标资源,再用openRawResource函数将该图片资源取出来,存入sd卡,因png格式中不同压缩方案对同一张图会产生不同结果,无法直接比较,我们再在pc上用画图程序将它转存为bmp格式,然后跟最初的bmp文件比较。果然,结果一字不差。

反正接下来要研究解码和绘制,顺便用下述代码将上述图片解码出来,

其中mPaint完全采用默认参数创建。我的机器是16位的,结果带状效果很明显,绘制速度也明显经历了格式转换,比使用565格式位图慢了近一个数量级。

下面研究第二步,解码。老规矩,先看开发文档。没发现系统描述这个问题的文档,只好看看解码参数BitmapFactory.Options,相关参数有两个,inDither决定是否进行抖动,inPreferredConfig决定了采用565格式还是ARGB,具体描述如下:

If this is non-null, the decoder will try to decode into this internal configuration. If it is null, or the request cannot be met, the decoder will try to pick the best matching config based on the system's screen depth, and characteristics of the original image such as if it has per-pixel alpha (requiring a config that also does). Image are loaded with the ARGB_8888 config by default.

看来,最佳的解码方式应该是像下面这样,让系统决定最佳格式:

在我的16位机器上一试,效果不错,加载时进行了抖动和格式转换,显示又快又好。
文档看过了,该看源码了,android的文档不太完善,没源码还真不行。先看看BitmapFactory.java,首先是Options,inDither默认为false,inPreferredConfig默认为ARGB_8888。再看decodeStream函数,它调用了nativeDecodeStream函数,一搜,它在BitmapFactory.cpp中定义的,这个函数很简单,主要是调用doDecode,该函数定义了各参数的默认值,如下:先看看抖动,它调用setDitherImage保存在fDitherImage中,该成员是私有的,仅在getDitherImage函数中有使用。用getDitherImage一搜,在SkImageDecoder_libpng.cpp的onDecode函数中有使用,它取出抖动设置后,又调用getBitmapConfig根据图片格式进行了调整,如果源图低于565格式,则不进行抖动,调整好后就交给了SkScaledBitmapSampler::begin函数,该函数根据抖动和格式设置选取不同的采样函数,抖动选项至此真正使用起来。图片格式的使用稍微复杂一些,搜索过程咱就不啰嗦了,直接讲结果。getBitmapConfig函数中抖动调整之后就是格式设置,它调用了getPrefConfig函数,并将位深和是否透明作为参数传递过去,该函数代码如下:
SkBitmap::Config SkImageDecoder::getPrefConfig(SrcDepth srcDepth,                                               bool srcHasAlpha) const {    SkBitmap::Config config;    if (fUsePrefTable) {        int index = 0;        switch (srcDepth) {            case kIndex_SrcDepth:                index = 0;                break;            case k16Bit_SrcDepth:                index = 2;                break;            case k32Bit_SrcDepth:                index = 4;                break;        }        if (srcHasAlpha) {            index += 1;        }        config = fPrefTable[index];    } else {        config = fDefaultPref;    }    if (SkBitmap::kNo_Config == config) {        config = SkImageDecoder::GetDeviceConfig();    }    return config;}

它优先使用表格方式,而表格仅仅在处理.9.png格式时会设置,fDefaultPref即最早的doDecode函数中设置的,默认为SkBitmap::kNo_Config,这时取用系统配置,系统配置可在C++中通过SkImageDecoder::SetDeviceConfig设置,也可在JAVA中通过BitmapFactory.setDefaultConfig(隐藏方法)设置。在我的系统中,仅仅AndroidRuntime初始化时有调用,设成了565格式。

小结一下,系统定制者应该在AndroidRuntime初始化时根据系统位深和内存配置调用SkImageDecoder::SetDeviceConfig设置好系统默认配置,并在BitmapFactory.cpp的doDecode函数中设置好抖动的默认值。系统内置应用可以在启动时调用BitmapFactory.setDefaultConfig修改本应用的默认配置。应用程序在decodeStream时不传递或传递null参数将使用系统默认配置,传递一个new BitmapFactory.Options()则使用不抖动的ARGB_8888格式。

其实,我还有一个疑问,比如一张红色的渐变图,因为它的颜色总数少于256,在大小足够时会被aapt压缩成使用调色板的256色图片,在getBitmapConfig时会否因颜色低于565而不进行抖动?这个问题需要时我再行验证,哪位同学知道或有空验证后还望不吝赐教。

第三步,绘制,文档只有Paint的DITHER_FLAG,控制是否进行抖动,还是直接看源码吧。skia的绘制流程相当复杂,我看过很多次了,但没有系统整理过,依然没有任何头绪。不过没关系,咱们还是定点突破。很容易就可以发现Paint.java中的DITHER_FLAG通过setFlags最终传递给了SkPaint.h中的SkPaint类的fFlags成员,对应的标识为kDither_Flag,搜索一下,SkBlitter_RGB16.cpp的SkRGB16_Shader_Blitter函数有使用,它根据该标识设置自己的flags,然后传递给SkBlitRow::Factory,该函数根据flags查表选择不同的具体绘制函数,表格为gDefault_565_Procs,其内容明显地分为抖动和不抖动两组。问题很快理清了,没有任何干扰,Paint中的DITHER_FLAG完全决定了绘制时抖动与否。而只有SkBlitter_RGB16.cpp和SkBlitter_4444.cpp使用了抖动标识,可见只有这两种目标格式支持抖动。

接下来,我们再探讨一下常见的应用程序使用图片资源的方式。探讨过程就省略了,不外乎那三板斧。还是看结论吧,有不完整的请同学们提出。

或取得不抖动的ARGB_8888格式。或


采用系统默认配置,通常是抖动的565格式。

解码为不抖动的ARGB_8888格式,绘制时进行抖动。直接在xml中指定背景也是如此。

总结一下,系统默认采用ARGB_8888格式,并在绘制时进行抖动,只有直接使用BitmapFactory才可能按机器配置自动选择合适的方式。

基本工作已经完成了,但问题还有很多。Android中的View为了保证动画流畅,设置了灵活的cache,以避免不必要的重画,这张图片又是什么格式呢?它对动画性能的影响可不一般。Android的显示管理基于Surface,普通窗口的Surface又是什么格式的呢?它决定了最终用户所见的画质。

View的cache比较简单,直接看源码,它取决于View是否透明和mUse32BitDrawingCache参数,而此参数仅仅在窗口透明时设置,可见大部分情况下cache都是565格式的,在16位机器和32位机器上分别测试,结果都是一样。

窗口的Surface理论上应该跟系统位深一致,否则修改系统位深就成了一个笑话,起不到任何作用,速度测试也间接证实了这一点。咱们再从代码中确认一下,我在窗口管理的文章中提到过,Surface的创建在WindowSession的relayoutWindow中进行,找到这个函数,它调用WindowState的createSurfaceLocked,最终通过Surface的构造函数创建,(其实分析一下Surface方面也知道,这是Java中唯一的入口)格式参数由WindowManager.LayoutParams确定,它的默认值是OPAQUE。继续分析Surface的创建,它通过ISurfaceComposerClient::createSurface创建,搜索createSurface,可以发现它的真正实现在SurfaceFlinger中,它最终调用createNormalSurface创建,对于OPAQUE格式,它会根据DisplayHardware的格式决定用565还是8888。OK,猜测成立。

更多相关文章

  1. Android弹出软键盘布局是否上移问题
  2. [Android]attrs.xml文件中属性类型format值的格式
  3. Rexsee API介绍:Android屏幕锁定,Keyguard函数与扩展源码
  4. Android中处理崩溃异常
  5. android 4.0.1 webkit Event 事件处理过程分析
  6. android启动过程
  7. Android(安卓)NDK的基本使用,这一篇就够了
  8. Android初步笔记
  9. 箭头函数的基础使用

随机推荐

  1. android 打开移动数据流程
  2. Android:giraffeplayer2 ConnectExceptio
  3. 设置环境变量
  4. android中实现截屏的三种思路
  5. android常见错误-Installation error: IN
  6. 2011.06.21——— android GridView的拖
  7. 实现Android(安卓)滑动退出Activity的功
  8. ImageView的属性android:scaleType,即Imag
  9. Android去掉状态栏和标题栏的两种方式
  10. Android(安卓)studio 工具中的“Attach D