Android图形显示系统——一张图片的显示流程

Android设备上一张图片的显示过程

应用示例

假如我们现在有一张这样的风景照
Android图形显示系统——一张图片的显示流程_第1张图片
想在Android设备(比如一个小米pad)上显示出来。首先想到的是写一个应用,用一个ImageView,把这张照片附到ImageView上显示,如下面的demo。
Android图形显示系统——一张图片的显示流程_第2张图片

MainActivity.java

package com.example.pictureshow;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

activity_main.xml:

"http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.pictureshow.MainActivity" >

    "@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/background" />
</RelativeLayout>

这个demo的显示效果是这样的:
Android图形显示系统——一张图片的显示流程_第3张图片

显示流程

其过程是这样的
Android图形显示系统——一张图片的显示流程_第4张图片
整个过程可分为三步:
第一步,得到位图(Bitmap)的内存数据,即从相应的图片文件解码,得到数据放并放到内存。
第二步,使用某种2D引擎,将位图内存按一定方式,渲染到可用于显示的图形内存(GraphicBuffer)上。
第三步,由一个中心显示控制器(Surfaceflinger),将相应的图形内存投放到显示屏(LCD)。

从图片文件到位图

1、找到合适的图片文件
当把风景图片放在drawable目录时,Android系统中会根据设备的分辨率,去相应分辨率的目录选择图片文件。然而,我们把图片放在asset目录下,用 asset manager 去打开时,或者放在sdcard下,就不存在这个一个适配过程,系统会忠实地读取指定路径的文件。
2、将图片文件解码到内存,形成位图(Bitmap)
解码器一般使用系统的,Android中是以Skia主导解码的过程。具体往下用硬件解码还是软件解码,就由芯片厂商决定了。不过,绝大部分Android设备的硬解码和硬编码都用于自家的应用(比如拍照),对第三方应用开放的只有软件编解码。
图像编解码都是很复杂的算法,google提供的Android版本里对此也是不断进行着优化,导致系统的编解码库性能一般来说是很优秀的。如果没有专业的算法优化人员,就不要指望靠移植第三方库打败系统的解码器了。
Android图形显示系统——一张图片的显示流程_第5张图片

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

从位图到图形内存

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

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

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

2、渲染,把位图画上图形内存
不考虑SurfaceView的非主线渲染,只考虑主线渲染的两个分支:软件绘制(skia)与硬件加速绘制(hwui)。
软件绘制下,由位图直接画上去。
硬件加速绘制,实际上是GPU绘制的情况下,需要先把位图上传到GPU形成纹理,再基于纹理采样,渲染到图形内存上。
Android图形显示系统——一张图片的显示流程_第6张图片
看上去,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时如何做这些处理也是个问题。

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

你可能感兴趣的:(Android图形显示)