Android SurfaceFlinger导读(03)MessageBase

2023-09-11 23:54:30

该系列文章总纲链接:Android GUI系统之SurfaceFlinger 系列文章目录

说明:

  • 关于导读:导读部分主要是方便初学者理解SurfaceFlinger代码中的机制,为后面分析代码打下一个更好的基础,这样就可以把更多的精力放在surfaceFlinger的业务逻辑分析上。
  • 关于代码分支:以下代码分析均在android5.1.1_r3分支上 目录frameworks/native/services/surfaceflinger为root目录

1 MessageBase解读

1.1 源码实现分析

MessageBase源码实现如下:

//头文件部分MessageQueue.h
class MessageBase : public MessageHandler
{
public:
    MessageBase();
    virtual bool handler() = 0;
    void wait() const { barrier.wait(); } //等待handle处理消息
protected:
    virtual ~MessageBase();
private:
    virtual void handleMessage(const Message& message);
    mutable Barrier barrier;
};

//实现部分MessageQueue.cpp
MessageBase::MessageBase(): MessageHandler() {}
MessageBase::~MessageBase() {}
void MessageBase::handleMessage(const Message&) {
    this->handler();
    barrier.open();//处理完消息,打开屏障
};

1.2 理解Barrier机制

C++中的Barrier机制形象解读如下:

当涉及到多线程的同步问题时,这里用一个小故事来形象解读Barrier的原理。假设有一群小朋友在进行一场集体活动,他们需要在一个关键点上同步行动。这个关键点是一个大门,只有当所有小朋友都到达大门时,才能一起进入下一个阶段的活动。

  • 开始时,所有的小朋友都在不同的地方,他们要在指定的时间内集合到大门前。每个小朋友都代表一个线程,他们需要同时到达大门,才能继续进行下一步的活动。
  • 当活动开始时,大门是关闭的,表示屏障状态为关闭(CLOSED)。小朋友们在各自的位置启动,开始向大门前进。每个小朋友到达大门时,他们会等待其他小朋友到达。
  • 当最后一个小朋友到达大门时,屏障状态变为开放(OPENED)。大门打开了,所有小朋友都能看到大门已经开了,他们同时继续通过大门进入下一个阶段的活动。
  • 这个故事中的大门就是 Barrier,它提供了一个同步点,确保所有小朋友(线程)都到达了同一个位置后才能继续进行下一步的活动。当屏障状态为关闭时,小朋友们会等待,直到最后一个小朋友到达,屏障状态变为开放,所有小朋友都能继续执行后续的操作。

通过这个故事,可以更形象地理解 Barrier 的原理,它在多线程环境中起到类似于大门的作用,让线程们在同一个关键点上同步等待,以保证并发操作的正确执行和数据的一致性。
android源码中使用lock和condition机制 构建了一个Barrier的机制,实现如下:

#include <stdint.h>
#include <sys/types.h>
#include <utils/threads.h>

namespace android {
class Barrier
{
public:
    inline Barrier() : state(CLOSED) { }
    inline ~Barrier() { }

    //释放处于等待状态的线程,将屏障状态设置为开放(OPENED),并唤醒所有等待的线程。
    void open() {
        Mutex::Autolock _l(lock);
        state = OPENED;
        cv.broadcast();
    }

    //重置屏障,将状态设置为关闭(CLOSED),以便 wait() 方法可以将线程阻塞。
    void close() {
        Mutex::Autolock _l(lock);
        state = CLOSED;
    }

    //等待屏障状态变为开放(OPENED)。如果屏障状态为关闭(CLOSED),则线程将在此处阻塞等待,直到 open() 方法被调用。
    void wait() const {
        Mutex::Autolock _l(lock);
        while (state == CLOSED) {
            cv.wait(lock);
        }
    }
private:
    enum { OPENED, CLOSED };
    mutable     Mutex       lock;
    mutable     Condition   cv;
    volatile    int         state;
};
}; // namespace android

#endif // ANDROID_BARRIER_H

根据以上实现,在android的surfaceFlinger中,并没有使用barrier的close操作。那么这是为什么呢?

  • 因为它的设计并不需要在每次使用之前显式地进行关闭。barrier在SurfaceFlinger中被用作线程之间的同步机制,用于控制消息的处理和执行顺序。它的目的是等待其他线程完成一定操作后再继续执行,而不是重复使用。在SurfaceFlinger的消息处理中,barrier被用作同步点,以确保特定操作在特定时机执行。一旦消息处理线程到达barrier,它会等待其他线程(通常是主线程或 GPU 线程)完成特定的操作,然后继续执行。
  • 由于barrier的使用场景是等待其他线程完成操作,而不是重复使用,因此在SurfaceFlinger中的MessageBase中没有使用close()操作进行显式关闭。在每个消息的处理过程中,barrier只需等待其他线程完成操作即可,而无需重置为初始状态。

1.3 SurfaceFlinger中同步/异步消息实现

在MessageBase的代码中handleMessage中每次处理完消息后会执行barrier.open();来打开屏障,而在SurfaceFlinger中把消息放入消息队列时候,如果采用同步操作,等待上一个消息处理完毕,这里的msg的类型就是继承了MessageBase。这里的wait就是MessageBase中的wait实现,调用的就是barrier的wait操作,这样,当上一个消息处理完执行了barrier的open操作,wait操作才会解除阻塞。同步消息方法postMessageSync详细代码如下:

status_t SurfaceFlinger::postMessageSync(const sp<MessageBase>& msg,
        nsecs_t reltime, uint32_t /* flags */) {
    status_t res = mEventQueue.postMessage(msg, reltime);
    if (res == NO_ERROR) {
        msg->wait();
    }
    return res;
}

而对于异步消息操作,则不需要wait操作,因而实现反而简单的多,不需要等待处理结果。异步消息方法postMessageAsync详细代码如下所示:

status_t SurfaceFlinger::postMessageAsync(const sp<MessageBase>& msg,
        nsecs_t reltime, uint32_t /* flags */) {
    return mEventQueue.postMessage(msg, reltime);
}

2 MessageBase在SurfaceFlinger中的用法

2.1 原理说明

这里要注意的是MessageBase是继承MessageHandler的,这意味着执行SurfaceFlinger中的postMessageSync方法时,会将对应的msg放入消息队列当中,等待SurfaceFlinger处理消息并发送回应信号。这里处理消息会直接调用对应MessageBase对象的handleMessage方法,进而执行对应MessageBase对象的handler()方法。

2.2 使用案例参考

上面对MessageBase的机制有了一个具体的了解,接下来看看SurfaceFlinger中一般是怎么使用这个机制的。

2.2.1 Client::createSurface方法实现参考

createSurface 方法主要用于创建surface,具体功能如下:

  • 创建Surface对象:在客户端应用程序中调用createSurface方法时,SurfaceFlinger会创建一个Surface对象,用于表示应用程序中的窗口或图形内容。
  • 配置Surface属性:createSurface方法接受一组参数,用于配置Surface的属性,如宽度、高度、格式等。这些属性决定了Surface的显示特性和渲染方式。
  • 添加到图层层级结构:SurfaceFlinger会将创建的Surface对象添加到图层层级结构中的适当位置。这样,SurfaceFlinger就能够管理多个Surface,并控制它们的显示顺序、位置、透明度等。

这里代码参考了Client.cpp中createSurface方法的实现,如下所示:

status_t Client::createSurface(
        const String8& name,
        uint32_t w, uint32_t h, PixelFormat format, uint32_t flags,
        sp<IBinder>* handle,
        sp<IGraphicBufferProducer>* gbp)
{
    class MessageCreateLayer : public MessageBase {
        SurfaceFlinger* flinger;
        Client* client;
        sp<IBinder>* handle;
        sp<IGraphicBufferProducer>* gbp;
        status_t result;
        const String8& name;
        uint32_t w, h;
        PixelFormat format;
        uint32_t flags;
    public:
        //构造函数实现
        MessageCreateLayer(SurfaceFlinger* flinger,const String8& name, Client* client,
                uint32_t w, uint32_t h, PixelFormat format, uint32_t flags,
                sp<IBinder>* handle,sp<IGraphicBufferProducer>* gbp)
            : flinger(flinger), client(client),handle(handle), gbp(gbp),
              name(name), w(w), h(h), format(format), flags(flags) {
        }
        status_t getResult() const { return result; }
        //消息处理实现,handle message机制会调用handleMessage方法,进而会调用到handler方法
        virtual bool handler() {
            //这里的flinger指的就是SurfaceFlinger实例,通过构造函数传入
            result = flinger->createLayer(name, client, w, h, format, flags,handle, gbp);
            return true;
        }
    };
    //
    sp<MessageBase> msg = new MessageCreateLayer(mFlinger.get(),
            name, this, w, h, format, flags, handle, gbp);
    mFlinger->postMessageSync(msg);
    return static_cast<MessageCreateLayer*>( msg.get() )->getResult();
}

这里的使用流程简要成两步,说明如下:

  1. 构建一个MessageBase子类MessageCreateLayer,实现MessageCreateLayer的构造方法和handler方法
  2. 创建MessageCreateLayer对象并将msg消息加入消息队列等待处理,msg对应的处理方法为handler。

这里思考一个问题:可以看到Client::createSurface最终是调用了SurfaceFlinger::createLayer方法。那么为什么不直接调用呢?

在给出的代码中,Client::createSurface 方法通过创建一个名为 MessageCreateLayer 的自定义消息类来间接调用 SurfaceFlinger::createLayer 方法,而不是直接调用。这种间接调用的方式有几个目的和好处:

  • 解耦和模块化:通过使用消息的方式进行通信,Client 类和 SurfaceFlinger 类之间的耦合性降低。Client 类只需要创建消息并将其发送给 SurfaceFlinger,而不需要直接依赖于 SurfaceFlinger 的具体实现。这样可以提高代码的灵活性和可维护性。
  • 异步处理:通过使用消息队列和异步处理机制,Client 类可以继续执行后续的操作,而不需要等待 SurfaceFlinger::createLayer 方法的完成。这样可以避免因某个操作阻塞而导致整个应用程序的响应性下降。
  • 线程安全:由于消息队列通常是在多线程环境下使用的,通过将操作封装为消息并由消息处理循环执行,可以确保对 SurfaceFlinger 的访问是线程安全的。这可以避免多个线程同时访问 SurfaceFlinger 导致的竞态条件和数据不一致性。

总结起来,通过间接调用的方式,即通过创建自定义消息并发送给 SurfaceFlinger 的消息队列,Client::createSurface 方法可以实现解耦和模块化、异步处理和线程安全等优势。这样的设计可以提高代码的可维护性和系统的性能。

2.2.2 setActiveConfig方法实现参考

setActiveConfig方法主要用于设置当前显示设备的活动配置。具体功能如下:

  • 切换配置:通过调用该方法,SurfaceFlinger可以切换到指定的配置,将显示设备的输出调整为该配置所定义的特性。这可能包括更改分辨率、刷新率或其他显示参数。
  • 确保兼容性:在切换到新的活动配置之前,setActiveConfig方法会进行一些兼容性检查。例如,它可能会检查新配置是否受支持,是否与其他正在显示的图层兼容等。这有助于确保切换到新配置不会导致显示问题或不稳定的情况。
  • 屏幕重启:在某些情况下,切换活动配置可能需要重新初始化显示设备,这可能会导致屏幕短暂黑屏或重启。setActiveConfig方法会涉及与硬件或底层系统交互,以便正确处理这些情况。

这里代码参考了SurfaceFlinger.cpp中setActiveConfig方法的实现,如下所示:

status_t SurfaceFlinger::setActiveConfig(const sp<IBinder>& display, int mode) {
    class MessageSetActiveConfig: public MessageBase {
        SurfaceFlinger& mFlinger;
        sp<IBinder> mDisplay;
        int mMode;
    public:
        MessageSetActiveConfig(SurfaceFlinger& flinger, const sp<IBinder>& disp,
                               int mode) :
            mFlinger(flinger), mDisplay(disp) { mMode = mode; }
        virtual bool handler() {
            Vector<DisplayInfo> configs;
            mFlinger.getDisplayConfigs(mDisplay, &configs);
            if (mMode < 0 || mMode >= static_cast<int>(configs.size())) {
                ALOGE("Attempt to set active config = %d for display with %zu configs",
                        mMode, configs.size());
            }
            sp<DisplayDevice> hw(mFlinger.getDisplayDevice(mDisplay));
            if (hw == NULL) {
                ALOGE("Attempt to set active config = %d for null display %p",
                        mMode, mDisplay.get());
            } else if (hw->getDisplayType() >= DisplayDevice::DISPLAY_VIRTUAL) {
                ALOGW("Attempt to set active config = %d for virtual display",
                        mMode);
            } else {
                mFlinger.setActiveConfigInternal(hw, mMode);
            }
            return true;
        }
    };
    sp<MessageBase> msg = new MessageSetActiveConfig(*this, display, mode);
    postMessageSync(msg);
    return NO_ERROR;
}

这里的使用流程简要成两步,说明如下:

  1. 构建一个MessageBase子类MessageSetActiveConfig,实现MessageSetActiveConfig的构造方法和handler方法,与2.2.1中的createSurface相比handler逻辑相对复杂。
  2. 创建MessageSetActiveConfig对象并将msg消息加入消息队列等待处理,msg对应的处理方法为handler。

当然,在SurfaceFlinger中还有其他的使用案例,大同小异,这里就不再赘述了。

更多推荐

【STM32】基础知识 第十一课 sys, delay & usart 文件夹

【STM32】基础知识第十一课sys,delay&usart文件夹sys文件介绍delay文件夹函数简介SysTickSysTick工作原理SysTick寄存器介绍delay_init()函数delay_us()函数usart文件夹介绍printf的使用常用输出控制符表常用转椅字符表半主机模式简介sys文件介绍函数分类

苹果从成熟到落地,Apple Newton 背后的工程师们 | 历史上的今天

整理|王启隆透过「历史上的今天」,从过去看未来,从现在亦可以改变未来。1983年3月的最后一个星期日,史蒂夫·乔布斯(SteveJobs)和时任百事公司总裁约翰·斯卡利(JonSculley)坐在阳台上俯瞰纽约中央公园。在深思熟虑之后,斯卡利对着眼前年轻人说:“我们已经互相了解彼此,但是,史蒂夫,我已经考虑过了,我不会

Linux系统使用(超详细)

目录Linux操作系统简介Linux和windows区别Linux常见命令Linux目录结构Linux命令提示符常用命令lscdpwdtouchcatechomkdirrmcpmvvimvim的基本使用grepnetstatLinux面试题Linux操作系统简介Linux操作系统是和windows操作系统是并列的关系。

STM32H5开发(3)----电源控制&RCC

STM32H5开发----3.电源控制&RCCSTM32H503供电样品申请STM32H562/563/573LDO供电STM32H562/563/573SMPS供电LDO/SMPS供电PWR特性电源电压监测温度监测低功耗模式低功耗模式-SLEEP模式低功耗模式-STOP模式低功耗模式-STANDBY模式低功耗模式监控

2023年中职组“网络安全”赛项吉安市竞赛任务书

2023年中职组“网络安全”赛项吉安市竞赛任务书一、竞赛时间总计:360分钟竞赛阶段竞赛阶段任务阶段竞赛任务竞赛时间分值A模块A-1登录安全加固180分钟200分A-2本地安全策略配置A-3流量完整性保护A-4事件监控A-5服务加固A-6防火墙策略B模块B-1Windows操作系统渗透测试400分B-2隐藏信息探索B-

STM32 ADC基础知识讲解

文章目录前言一、ADC的基本介绍二、STM32ADC讲解1.ADC分辨率2.ADC通道讲解3.ADC转换模式单次转换模式连续转换模式4.扫描模式5.数据对齐方式左对齐右对齐总结前言在正式的学习如何编写ADC代码时我们先来学习一下ADC的基础知识部分,只有掌握好了这些基础知识才能顺利的进行后面的代码编写。一、ADC的基本

.NET网络编程——TCP通信

一、网络编程的基本概念:1.网络就是将不同区域的电脑连接到一起,组成局域网、城域网或广域网。把分部在不同地理区域的计算机于专门的外部设备用通信线路互联成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息,共享硬件、软件、数据信息等资源。2.计算机网络通过传输介质、通信设施和网络通信协议,将地理位置相

git 常用命令有哪些

Git是我们开发工作中使用频率极高的工具,下面总结下他的基本指令有哪些,顺便温习一下。前言一般项目中长存2个分支:主分支(master)和开发分支(develop)项目存在三种短期分支:功能分支(featurebranch)补丁分支(hotfixbranch)预发分支(releasebranch)一旦完成开发,它们就会

Spring MVC拦截器和跨域请求

一、拦截器简介SpringMVC的拦截器(Interceptor)也是AOP思想的一种实现方式。它与Servlet的过滤器(Filter)功能类似,主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。拦截器和过滤器的区别拦截器是SpringMVC组件,而过滤器是Ser

docker系列(8) - docker网络

文章目录8.docker网络8.1四种网络模式8.2常用命令8.3桥接网络模式8.3.1桥接网络模式说明8.3.2桥接网络模式案例8.4host网络模式8.4.1host网络模式说明8.4.2host模式案例8.5none网络模式8.5container网络模式8.5.1container网络模式说明8.5.2cont

Android面试题汇总(二)

一、Java集合1、谈谈Java中List、Set以及Map的区别?List:有序的,数据可以重复。。Set:无序的,数据不能重复。Map:键值对存储。键是唯一的,值不是唯一的。2、谈谈ArrayList和LinkedList的区别?ArrayList:底层是基于数组的,数组占用的是一个连续的内存空间。在新增和删除的时

热文推荐