显示流程

整个过程可分为三步

  1. 得到位图(Bitmap)的内存数据,即从相应的图片文件解码,得到数据放并放到内存。
  2. 使用某种2D引擎,将位图内存按一定方式,渲染到可用于显示的图形内存(GraphicBuffer)上。
  3. 由一个中心显示控制器(Surfaceflinger),将相应的图形内存投放到显示屏(LCD)。
    显示流程
    显示流程

从图片文件到位图

  • 找到合适的图片文件
    当把风景图片放在drawable目录时,Android系统中会根据设备的分辨率,去相应分辨率的目录选择图片文件。然而,我们把图片放在asset目录下,用 asset manager 去打开时,或者放在sdcard下,就不存在这个一个适配过程,系统会忠实地读取指定路径的文件。

  • 将图片文件解码到内存,形成位图(Bitmap)
    解码器一般使用系统的,Android中是以Skia主导解码的过程。具体往下用硬件解码还是软件解码,就由芯片厂商决定了。不过,绝大部分Android设备的硬解码和硬编码都用于自家的应用(比如拍照),对第三方应用开放的只有软件编解码。

图像编解码都是很复杂的算法,google提供的Android版本里对此也是不断进行着优化,导致系统的编解码库性能一般来说是很优秀的。如果没有专业的算法优化人员,就不要指望靠移植第三方库打败系统的解码器了。

load
load

就更一般的场景而言,图片原始来源于磁盘、网络或图形内存(Android部分系统图片预加载)。如何在导入大量图片时不致出现内存溢出,如何快速导入一个页面的图片以免用户产生等待感,是个复杂的事情。许多开源图片缓存框架(如imageloader)就在这一过程做文章,不多述。

从位图到图形内存

Android向应用开发者开放的惟一一条显示的路径是View。不管想显示什么,都必须先弄个布局,然后把要显示的内容和布局中的View关联起来。

  • 布局,确定View本身位置
    在上面的UI显示图中,我们发现,图并没有横向填满手机屏幕,这是由于布局中设置了留白造成的:

    1
    2
    3
    4
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"

    布局就是每个View的measure和layout过程,Android图形系统的framework层解析布局文件,去计算每个View的位置。
    本文的例子中,是 RelativeLayout —— ImageView 的解析过程。

    布局解析的一般流程参考以下文章:
    http://blog.csdn.net/qinjuning/article/details/8074262

  • 渲染,把位图画上图形内存
    不考虑SurfaceView的非主线渲染,只考虑主线渲染的两个分支:软件绘制(skia)与硬件加速绘制(hwui)。
    软件绘制下,由位图直接画上去。
    硬件加速绘制,实际上是GPU绘制的情况下,需要先把位图上传到GPU形成纹理,再基于纹理采样,渲染到图形内存上。

    draw
    draw

    看上去,GPU绘图需要先上传为Texture,明显多了一步,但请考虑如下的问题:

  1. 大部分情况,把位图画上图形内存,不是简单的memcpy。
    如本处的风景照,很明显就需要缩小。这种情况下,CPU绘制需要先采样,这是很慢的。
  2. 绘制不是一次性的工作,它是要刷帧的。
    使用GPU绘制时,纹理上传固然在第一次时增加了启动时间,但后续并不需要重做,可以继续使用GPU强大的渲染能力。

从图形内存到显示屏

这一套流程便是Android的下层显示内容。
SurfaceFlinger汇集所有图层的信息,采用离线合成或在线合成的方式,将图形内存的内容送达显示屏LCD。

参见 http://blog.csdn.net/jxt1234and2010/article/details/46057267

更好的方案?

1. 既然到图形内存才能显示,为什么不直接解码到图形内存?
当然可以,开机动画就是这么搞的,解到图形内存直接显示。
不过,由于图形内存是一种进程间共享内存,需要作很多额外的同步、映射等管理工作。对于一般的应用场景,在自己的进程空间申请内存,无疑是更方便管理的,也不会造成系统的额外负担。

2. 解码图片需要时间,可不可以一开始就用原始位图,跳过这一过程?
当然可以,Android的系统图库会为每一张照片创建一张缩略图,这一张缩略图就是无压缩的位图格式,以便用户快速看到预览的照片。
但当像素值比较大时,原始位图实在是太大了,相机拍出来一张800w像素的图片接近32M,存储设备和网络传输都吃不消。

一种更好的方法是使用压缩纹理,这样既能省去解码图片的时间,又能减少渲染时所需要的读取内存量。但这个存在如下问题:
第一个问题是,Android的硬件加速机制是基于软件绘制流程修改而来的,它需要与软件绘制有相同的上层接口,这就意味着,软件绘制也要设法支持这种模式。让软件绘制支持压缩纹理的解压是不大现实的,不过呢,可以考虑副本的模式,即同时提供jpg/png和压缩纹理的资源。
第二个问题更加棘手,移动设备的GPU是分散的,所提供的压缩纹理格式也互不相同,虽然有ETC1和ETC2作为标准格式,但其显示效果差强人意。

3. 就显示一张图片,为什么要合成,直接传给LCD不可以么?
当然可以,这就是所谓的Overlay技术,进一步,是在线合成。据说,ios的图库显示照片时正是这么做的。
不过,使用这个技术有很多坑。
第一个坑是Buffer循环机制,处理不好,就容易出现裂屏。
第二个坑是千奇百怪的布局位置,单个图层辅满显示屏好处理,但纯大部分情况是多个图层各占屏幕的一部分,另外还会有重叠的情况。
第三个坑则是后处理需求,有时候缩放和旋转会延迟到合成阶段做处理,直接传给LCD时如何做这些处理也是个问题。

在理解清楚原理后,找到性能瓶颈并发现优化点,并不是件难事,但怎么针对性地实施优化并兼容原有系统这么一个宏大的体系,就不是那么好做了 。不过,解决问题的关键还是原理要正确,要相信,只要原理是正确的,流程上即便千般阻碍,最终也能达到目标的。