深入Android系统(十二)Android图形显示系统-1-显示原理与Surface

本文详细剖析了Android图形显示过程中Surface的创建、管理及与SurfaceFlinger的交互,涉及Framebuffer、VSYNC信号、SurfaceFlinger、HardwareComposer等关键概念,阐述了图像数据从应用到屏幕显示的完整流程。

关于图形显示部分可以参考官网:Graphics

图像显示原理

Linux通常使用Framebuffer来用作显示输出(Framebuffer知识详解在这里),Framebuffer就是一块内存区域,它通常是显示驱动的内部缓冲区在内存中的映射。

一旦用户进程把图像数据复制到Framebuffer中,显示驱动会一个像素一个像素地扫描整个Framebuffer,并根据其中的值更新屏幕上像素点的颜色。

驱动中这个更新屏幕的动作是固定的,它的周期就是我们常说的刷新率

Android关于图像渲染显示的架构图如下:
image

结合这张图,我们需要重点关注的是:

  • Native Framework中的Surface

    • 无论开发者使用什么渲染API,一切内容都会渲染到Surface
    • Surface中会关联一个BufferQueue用于提供图像数据缓存
    • 大多数客户端使用OpenGL ESVulkan渲染到Surface
    • 有些客户端使用Canvas渲染到Surface
  • Image Stream Producer的定义是能够生成图形缓冲区以供消耗的任何对象。每个Producer都会关联一个Surface,例如

    • Canvas 2DJava层主要是通过View中创建的Surface对象来进行操作

      • Surface对象会与SurfaceFlinger进行关联,并通过lockCanavas()接口获取Canvas对象
      • lockCanvas()会将CPU渲染程序连接到BufferQueue的生产方,直到Surface被销毁时才会断开连接
    • mediaserver视频解码器:通过MediaPlayersetSurfaceHolder()接口与SurfaceView中的Surface进行绑定

  • 对于Image Stream Consumer来说,主要是SurfaceFlinger,该系统服务会消耗当前可见的Surface,并使用WindowManager中提供的信息将它们合成到显示部分。

    • SurfaceFlinger是可以修改所显示部分内容的唯一服务。SurfaceFlinger使用OpenGLHardware Composer 来合成一组Surface
    • 当应用进入前台时,它会从WindowManager请求缓冲区。然后,WindowManager会从SurfaceFlinger请求layer
      • layersurface(包含BufferQueue)和SurfaceControl(包含屏幕框架等层元数据)的组合。
    • SurfaceFlinger创建layer并将其发送至WindowManager
    • 然后,WindowManagerSurface发送至应用,但会保留SurfaceControl来操控应用在屏幕上的外观。
  • Window Positioning中的WindowManager主要是用来控制Window对象

    • Window对象是用来存放View对象的容器,每个Window对象都会关联Surface对象
    • WindowManager监视Window对象的生命周期、输入和焦点事件、屏幕方向、转换、动画、位置、变换、z顺序等
    • 然后将所有Window元数据发送给SurfaceFlingerSurfaceFlinger利用这些元数据把自己管理的所有Surface组合成layer
    • 然后交给Hardware Composer做进一步处理
  • HAL层中的Hardware Composer(HWC)会根据当前硬件来进一步进行缓冲区的组合,它的具体实现依赖于特定的显示设备

    • 官网关于HWC的数据流如下:
      image
    • SurfaceFlinger作为clientHWC提供一个完整的layer列表,然后询问HWC计划如何处理
    • HWC会将这些layer标记为client合成或者device合成并告知SurfaceFlinger
    • SurfaceFlinger将处理标记为client合成layer,然后通过BufferQueue传递给HWC
    • 余下的layerHWC自行处理

网上一篇很有趣的渲染总结(文中有些错误,但瑕不掩瑜):Android渲染原理

VSYNC信号

前面提到Linux使用Framebuffer来用作显示输出。但是,如果在屏幕更新到一半时,用户进程更新了Framebuffer中的数据,将导致屏幕上画面的上半部分是前一帧的画面,下半部分变成了新的画面。当然这种异常会在下次刷新时纠正过来,但是在用户感知上画面会出现闪烁感

  • 针对这种情况,早期的解决方法是使用双缓冲机制,双缓冲就是提供两块Framebuffer,一块用于显示,另一块用于数据更新,数据准备好后,通过ioctl操作告诉显示设备切换用于显示的Framebuffer,这样图像就能快速的显示出来了
  • 但是双缓冲并没有完全解决问题,虽然双缓冲切换的速度很快,但是如果切换的时间点不对,在画面更新一半的时候进行切换,还是会出现单缓冲区遇到的闪烁问题
  • 当然,可以在底层进行控制,当收到切换请求后内部并不马上执行,而是等到刷新完成后再切换,这样可以完全避免画面重叠的问题
  • 但是,这样做会带来新的问题,如果ioctl操作完成后缓冲区没有切换,应用就不能确定何时可以再使用缓存区,只能通过ioctl不停地查询缓冲区状态,直到切换完成。这种CPU主动查询的方式很低效

为此Android让底层固定地发送信号给用户进程,通知进程切换的时机,这样就避免了用户进程主动查询的操作。而这个信号就是VSYNC信号

官方传送门:VSYNC

官方描述如下:VSYNC信号用来同步整个显示流程(Display Pipeline)显示流程包括app渲染、SurfaceFlinger合成、HWC(硬件渲染)组成

(这部分感觉原文更容易理解)VSYNC synchronizes the time apps wake up to start rendering, the time SurfaceFlinger wakes up to composite the screen, and the display refresh cycle.

VSYNC信号应该由显示驱动产生,这样才能达到最佳效果。但是Android为了能运行在不支持VSYNC机制的设备上,也提供了用软件来模拟产生VSYNC信号的手段

官网描述:通过HWC来产生VSYNC信号,并通过接口回调将事件进行发送(主要是SurfaceFlinger进行事件接收)

基础知识铺垫完成,我们先来看看Surface

Surface

官网对Surface的描述是:A surface is an interface for a producer to exchange buffers with a consumer.

上面描述的是一种生产者-消费者的模式,而Surface充当了中间衔接的角色。

ActivityUI显示为例:

  • 生产者的任务就是把图形绘制在Surface对象上

    • 比较出名的生产者就是SurfaceView组件了
  • SurfaceFlinger作为消费者会把所有Surface对应的图像层混合在一起

  • 最后消费者将其输出到FrameBuffer中,这样在屏幕上就看到最后合成的图像了

下面我们从Java层开始分析Surface

应用中Surface的创建过程

应用开发中很少直接使用Surface,因为每个Activity中都已经创建好了各自的Surface对象,通常只有一些特殊的应用才需要在Activity之外再去创建Surface,例如相机、视频播放应用。

不过,通常这些应用也是通过创建SurfaceView来使用Surface

需要注意的是,在应用中不能直接去创建一个可用的Surface对象(也可以说直接创建出的对象没什么实际用途),因为这样创建出的Surface对象和SurfaceFlinger之间没有任何关联。

该如何创见一个可用的Surface对象呢?
我们看下Surface类的定义:

public class Surface implements Parcelable {
   
   
    long mNativeObject;
    // 一个无参构造,空实现
    public Surface() {
   
   
    }
    public Surface(SurfaceTexture surfaceTexture) {
   
   
        if (surfaceTexture == null) {
   
   
            throw new IllegalArgumentException("surfaceTexture must not be null");
        }
        mIsSingleBuffered = surfaceTexture.isSingleBuffered();
        synchronized (mLock) {
   
   
            mName = surfaceTexture.toString();
            setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
        }
    }
}

Surface类对外有两个构造方法:

  • 一个是无参构造,实现也是空的

    • 注释中说这个主要是给readFromParcel()反序列化用的
    • 那我们看下readFromParcel()方法
    public void readFromParcel(Parcel source) {
         
         
        if (source == null) {
         
         
            throw new IllegalArgumentException("source must not be null");
        }
        synchronized (mLock) {
         
         
            mName = source.readString();
            mIsSingleBuffered = source.readInt() != 0;
            setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
        }
    }
    
  • 另一个需要传递SurfaceTexture对象作为参数

    • 这就复杂了,还要准备一个SurfaceTexture对象

聪明的我们会发现,readFromParcel()new Surface(SurfaceTexture surfaceTexture)都会执行一个setNativeObjectLocked()方法,我们看下方法实现:

    private void setNativeObjectLocked(long ptr) {
   
   
        if (mNativeObject != ptr) {
   
   
            ...
            mNativeObject = ptr;
            ...
        }
    }

setNativeObjectLocked()方法很简单,只是更新了mNativeObject变量的数值,重点就是参数了:

  • setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
  • setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));

这两个setNativeObjectLocked()方法的调用从参数的命名来看是针对不同数据来源的处理。

看来要看下native的实现了,以nativeReadFromParcel()为例来看下:

static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject parcelObj) {
   
   
    Parcel* parcel = parcelForJavaObject(env, parcelObj);
    ...
    android::view::Surface surfaceShim;
    // 解析 Parcel 数据,并填充到 native层 的 Surface对象 surfaceShim
    surfaceShim.readFromParcel(parcel, /*nameAlreadyRead*/true);
    // 将传入的指针转换为 native层 的 Surface对象 self
    sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
    // 比对 surfaceShim 和 self 中的 Binder 对象 IGraphicBufferProducer
    if (self != nullptr
            && (IInterface::asBinder(self->getIGraphicBufferProducer()) ==
                    IInterface::asBinder(surfaceShim.graphicBufferProducer))) {
   
   
        //  判断是同一个 IGraphicBufferProducer ,直接返回当前指针
        return jlong(self.get());
    }
    sp<Surface> sur;
    if (surfaceShim.graphicBufferProducer != nullptr) {
   
   
        // IGraphicBufferProducer 不同
        // 且 surfaceShim 的 IGraphicBufferProducer 不为空
        // 创建一个新的 Surface 对象 sur
        sur = new Surface(surfaceShim.graphicBufferProducer, true);
        sur->incStrong(&sRefBaseOwner);
    }
    ...
    // 将 sur 的指针返回给 Java 层
    return jlong(sur.get());
}

到这里我们不难看出

  • Java层Surface对象最重要的数据是mNativeObject变量
    • mNativeObject是一个指针,指向的native层的Surface对象
  • native层在判断是否新建Surface对象的逻辑依赖的是IGraphicBufferProducer对象
    • IGraphicBufferProducer对象是一个Binder引用对象

那么接下来我们重点就是这个IGraphicBufferProducer了。

我们先看下native层Surface类的继承关系:

class Surface
    : public ANativeObjectBase<ANativeWindow, Surface, RefBase>

ANativeObjectBase的定义如下:

template <typename NATIVE_TYPE, typename TYPE, typename REF,
        typename NATIVE_BASE = android_native_base_t>
class ANativeObjectBase : public NATIVE_TYPE, public REF
{
   
   ...}

整理成继承关系图就是:
image

再看下Surface的构造方法:

Surface::Surface(const sp<IGraphicBufferProducer>& bufferProducer, bool controlledByApp)
      : mGraphicBufferProducer(bufferProducer),
        mCrop(Rect::EMPTY_RECT),
        mBufferAge(0),
        ...
        mFrameEventHistory(std::make_unique<ProducerFrameEventHistory>()) {
   
   
        ... // 初始化各种成员变量
}

从构造函数的参数可以看到,native层SurfaceIGraphicBufferProducer对象保存到了mGraphicBufferProducer变量中。

暂时还是不清楚mGraphicBufferProducer哪里来的,我们去WMS中看看

WMSSurface的创建过程

此处要从ActivityonResume()生命周期说起

onResume()WMS.relayoutWindow()

我们已经知道,当AMS触发onResume()生命周期时会调用到ActivityThread类的handleResumeActivity()方法,代码如下:

    public void handleResumeActivity(...) {
   
   
        ...
        // 此处会触发 onResume 声明周期回调
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        ...
        ViewManager wm = a.getWindowManager();
        ...// 省略很多 Window 处理逻辑
        wm.addView(decor, l);
        ...
    }

从方法中可以看到,执行完onResume()后调用了ViewManageraddView(decor, l)方法

知识点:onResume方法调用后才真正进行View的添加

ViewManager是一个接口类,真正的实现类是WindowManagerImpladdView()方法实现也很简单:

    public void addView(...) {
   
   
        applyDefaultToken(params);
        mGlobal.addView
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值