当前位置: 首页 > news >正文

【surfaceflinger源码分析】surfaceflinger进程的消息驱动模型

概述

对于surfaceflinger大多数人都知道它的功能是做图形合成的,用英语表示就是指composite。其大致框图如下:

  • 各个Android app将自己的图形画面通过surface为载体通过AIDL接口(Binder IPC)传递到surfaceflinger进程
  • surfaceflinger进程中的composition engine与HWC协商,哪些图层HWC可以直接显示,哪些图层需要自己合成为一个图层后再送给HWC显示。
  • surfaceflinger与HWC把合成策略协商完后,再将合成后的图层和独立显示的图层分别传递给HWC,HWC再操作硬件显示在屏幕上。

本系列文章会深入分析surfaceflinger的各个方面,力争弄懂下列问题:

  1. surfaceflinger做合成的动作是如何触发的?进程的消息模型是什么样的? 如何驱动循环做合成动作的?
  2. surface是什么? surface与图形数据buffer有什么关系?
  3. surface对应在surfaceflinger进程内用什么东西表示?各个app的图形数据buffer怎么传递过来的?
  4. 图形数据在surfaceflinger进程内部流转过程是什么样的?
  5. 既然涉及到跨进程传递,图形buffer的生产和消费是如何同步的?Fence是什么玩意?
  6. Vsync是个什么意思?有什么用?
    surfaceflinger
    本篇文章先来阅读源码分析下第一个问题。

surfaceflinger进程的main函数

int main(int, char**) {signal(SIGPIPE, SIG_IGN);hardware::configureRpcThreadpool(1 /* maxThreads */,false /* callerWillJoin */);startGraphicsAllocatorService();// When SF is launched in its own process, limit the number of// binder threads to 4.ProcessState::self()->setThreadPoolMaxThreadCount(4);// start the thread poolsp<ProcessState> ps(ProcessState::self());ps->startThreadPool();// instantiate surfaceflingersp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);set_sched_policy(0, SP_FOREGROUND);// Put most SurfaceFlinger threads in the system-background cpuset// Keeps us from unnecessarily using big cores// Do this after the binder thread pool initif (cpusets_enabled()) set_cpuset_policy(0, SP_SYSTEM);// initialize before clients can connectflinger->init();// publish surface flingersp<IServiceManager> sm(defaultServiceManager());sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);startDisplayService(); // dependency on SF getting registered aboveif (SurfaceFlinger::setSchedFifo(true) != NO_ERROR) {ALOGW("Couldn't set to SCHED_FIFO: %s", strerror(errno));}// run surface flinger in this threadflinger->run();return 0;
}

上面的代码大致可以缩减为三句话:

int main(int, char**) {..............................;sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();..............................;flinger->init();..............................;flinger->run();return 0;
}

下面再分别看下这三个函数分别做了什么?

surfaceflinger::createSurfaceFlinger

sp<SurfaceFlinger> createSurfaceFlinger() {static DefaultFactory factory;return new SurfaceFlinger(factory);
}

surfaceflinger::init

主要是对子模块mCompositionEngine,Displays, RenderEngine等模块的初始化,貌似和消息传递关系不大,先放着不深入分析。

void SurfaceFlinger::init() {ALOGI(  "SurfaceFlinger's main thread ready to run. ""Initializing graphics H/W...");Mutex::Autolock _l(mStateLock);mCompositionEngine->setRenderEngine(renderengine::RenderEngine::create(renderengine::RenderEngineCreationArgs::Builder().setPixelFormat(static_cast<int32_t>(defaultCompositionPixelFormat)).setImageCacheSize(maxFrameBufferAcquiredBuffers).setUseColorManagerment(useColorManagement).setEnableProtectedContext(enable_protected_contents(false)).setPrecacheToneMapperShaderOnly(false).setSupportsBackgroundBlur(mSupportsBlur).setContextPriority(useContextPriority? renderengine::RenderEngine::ContextPriority::HIGH: renderengine::RenderEngine::ContextPriority::MEDIUM).build()));mCompositionEngine->setTimeStats(mTimeStats);LOG_ALWAYS_FATAL_IF(mVrFlingerRequestsDisplay,"Starting with vr flinger active is not currently supported.");mCompositionEngine->setHwComposer(getFactory().createHWComposer(getBE().mHwcServiceName));mCompositionEngine->getHwComposer().setConfiguration(this, getBE().mComposerSequenceId);// Process any initial hotplug and resulting display changes.processDisplayHotplugEventsLocked();const auto display = getDefaultDisplayDeviceLocked();LOG_ALWAYS_FATAL_IF(!display, "Missing internal display after registering composer callback.");LOG_ALWAYS_FATAL_IF(!getHwComposer().isConnected(*display->getId()),"Internal display is disconnected.");................................................................;// initialize our drawing statemDrawingState = mCurrentState;// set initial conditions (e.g. unblank default device)initializeDisplays();char primeShaderCache[PROPERTY_VALUE_MAX];property_get("service.sf.prime_shader_cache", primeShaderCache, "1");if (atoi(primeShaderCache)) {getRenderEngine().primeCache();}// Inform native graphics APIs whether the present timestamp is supported:const bool presentFenceReliable =!getHwComposer().hasCapability(hal::Capability::PRESENT_FENCE_IS_NOT_RELIABLE);mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);if (mStartPropertySetThread->Start() != NO_ERROR) {ALOGE("Run StartPropertySetThread failed!");}ALOGV("Done initializing");
}

surfaceflinger::run

看到死循环了,哈哈,这应该就是本篇文章要找的死循环。看来要重点看下这个mEventQueue在wait什么Message了,哪里来的Message。

void SurfaceFlinger::run() {while (true) {mEventQueue->waitMessage();}
}

std::unique_ptr mEventQueue创建

SurfaceFlinger::SurfaceFlinger(Factory& factory, SkipInitializationTag): mFactory(factory),mInterceptor(mFactory.createSurfaceInterceptor(this)),mTimeStats(std::make_shared<impl::TimeStats>()),mFrameTracer(std::make_unique<FrameTracer>()),mEventQueue(mFactory.createMessageQueue()),mCompositionEngine(mFactory.createCompositionEngine()),mInternalDisplayDensity(getDensityFromProperty("ro.sf.lcd_density", true)),mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)) {}std::unique_ptr<MessageQueue> DefaultFactory::createMessageQueue() {return std::make_unique<android::impl::MessageQueue>();
}

MessageQueue 的定义
可以看到MessageQueue类中还定义了一个Handler类。现在还不知道这些类中的成员变量和函数的作用。那么就从waitMessage()函数入手,一探究竟。

class MessageQueue final : public android::MessageQueue {class Handler : public MessageHandler {enum { eventMaskInvalidate = 0x1, eventMaskRefresh = 0x2, eventMaskTransaction = 0x4 };MessageQueue& mQueue;int32_t mEventMask;std::atomic<nsecs_t> mExpectedVSyncTime;public:explicit Handler(MessageQueue& queue) : mQueue(queue), mEventMask(0) {}virtual void handleMessage(const Message& message);void dispatchRefresh();void dispatchInvalidate(nsecs_t expectedVSyncTimestamp);};friend class Handler;sp<SurfaceFlinger> mFlinger;sp<Looper> mLooper;sp<EventThreadConnection> mEvents;gui::BitTube mEventTube;sp<Handler> mHandler;static int cb_eventReceiver(int fd, int events, void* data);int eventReceiver(int fd, int events);public:~MessageQueue() override = default;void init(const sp<SurfaceFlinger>& flinger) override;void setEventConnection(const sp<EventThreadConnection>& connection) override;void waitMessage() override;void postMessage(sp<MessageHandler>&&) override;// sends INVALIDATE message at next VSYNCvoid invalidate() override;// sends REFRESH message at next VSYNCvoid refresh() override;
};

MessageQueue

waitMessage源码分析

void MessageQueue::waitMessage() {do {IPCThreadState::self()->flushCommands();int32_t ret = mLooper->pollOnce(-1);switch (ret) {case Looper::POLL_WAKE:case Looper::POLL_CALLBACK:continue;case Looper::POLL_ERROR:ALOGE("Looper::POLL_ERROR");continue;case Looper::POLL_TIMEOUT:// timeout (should not happen)continue;default:// should not happenALOGE("Looper::pollOnce() returned unknown status %d", ret);continue;}} while (true);
}

看来要重点看下IPCThreadState和这个mLooper了
IPCThreadState这玩意是和Binder相关啊,看着这意思是要把Binder中的消息刷出来,看看各个app进程有没有通过Binder发消息过来。先放在着,后面回过头再来分析。

void IPCThreadState::flushCommands()
{if (mProcess->mDriverFD < 0)return;talkWithDriver(false);if (mOut.dataSize() > 0) {talkWithDriver(false);}if (mOut.dataSize() > 0) {ALOGW("mOut.dataSize() > 0 after flushCommands()");}
}

再来看看这个mLooper的实现。

class Looper : public RefBase {
protected:virtual ~Looper();
publicLooper(bool allowNonCallbacks);bool getAllowNonCallbacks() const;int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);inline int pollOnce(int timeoutMillis) {return pollOnce(timeoutMillis, nullptr, nullptr, nullptr);}int pollAll(int timeoutMillis, int* outFd, int* outEvents, void** outData);inline int pollAll(int timeoutMillis) {return pollAll(timeoutMillis, nullptr, nullptr, nullptr);}void wake();int addFd(int fd, int ident, int events, Looper_callbackFunc callback, void* data);int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data);int removeFd(int fd);void sendMessage(const sp<MessageHandler>& handler, const Message& message);void sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,const Message& message);void sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,const Message& message);void removeMessages(const sp<MessageHandler>& handler);void removeMessages(const sp<MessageHandler>& handler, int what);bool isPolling() const;static sp<Looper> prepare(int opts);static void setForThread(const sp<Looper>& looper);static sp<Looper> getForThread();
private:struct Request {int fd;int ident;int events;int seq;sp<LooperCallback> callback;void* data;void initEventItem(struct epoll_event* eventItem) const;};struct Response {int events;Request request;};struct MessageEnvelope {MessageEnvelope() : uptime(0) { }MessageEnvelope(nsecs_t u, const sp<MessageHandler> h,const Message& m) : uptime(u), handler(h), message(m) {}nsecs_t uptime;sp<MessageHandler> handler;Message message;};const bool mAllowNonCallbacks; // immutableandroid::base::unique_fd mWakeEventFd;  // immutableMutex mLock;Vector<MessageEnvelope> mMessageEnvelopes; // guarded by mLockbool mSendingMessage; // guarded by mLock// Whether we are currently waiting for work.  Not protected by a lock,// any use of it is racy anyway.volatile bool mPolling;android::base::unique_fd mEpollFd;  // guarded by mLock but only modified on the looper threadbool mEpollRebuildRequired; // guarded by mLock// Locked list of file descriptor monitoring requests.KeyedVector<int, Request> mRequests;  // guarded by mLockint mNextRequestSeq;// This state is only used privately by pollOnce and does not require a lock since// it runs on a single thread.Vector<Response> mResponses;size_t mResponseIndex;nsecs_t mNextMessageUptime; // set to LLONG_MAX when noneint pollInner(int timeoutMillis);int removeFd(int fd, int seq);void awoken();void pushResponse(int events, const Request& request);void rebuildEpollLocked();void scheduleEpollRebuildLocked();static void initTLSKey();static void threadDestructor(void *st);static void initEpollEvent(struct epoll_event* eventItem);
};

先来看看这个pollOnce(-1)函数是在干啥 ?

    int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);inline int pollOnce(int timeoutMillis) {return pollOnce(timeoutMillis, nullptr, nullptr, nullptr);}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {int result = 0;for (;;) {//因为参数outFd, outEvents, outData都为null,所以此while循环不用太关注,//如果mResponses中有事件,就将mResponseIndex加一返回,没有事件就执行下面的pollInner//下面重点关注mResponses里面的item在哪里push进去的while (mResponseIndex < mResponses.size()) {const Response& response = mResponses.itemAt(mResponseIndex++);int ident = response.request.ident;if (ident >= 0) {int fd = response.request.fd;int events = response.events;void* data = response.request.data;if (outFd != nullptr) *outFd = fd;if (outEvents != nullptr) *outEvents = events;if (outData != nullptr) *outData = data;return ident;}}if (result != 0) {if (outFd != nullptr) *outFd = 0;if (outEvents != nullptr) *outEvents = 0;if (outData != nullptr) *outData = nullptr;return result;}//重点关注下这个实现result = pollInner(timeoutMillis);}
}

pollInner干了什么 ?

int Looper::pollInner(int timeoutMillis) {//计算出timeoutMillis作为epoll_wait的timeout时长if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);if (messageTimeoutMillis >= 0&& (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {timeoutMillis = messageTimeoutMillis;}}// Poll.int result = POLL_WAKE;mResponses.clear();  //清空mResponses,我擦,看来mResponses就是在这里push和clear的mResponseIndex = 0;mPolling = true;struct epoll_event eventItems[EPOLL_MAX_EVENTS];//epoll_wait 等待事件的到来,后面再具体看下这个epoll中都有哪些被监控的文件句柄?int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);mPolling = false;mLock.lock();...........................;for (int i = 0; i < eventCount; i++) {int fd = eventItems[i].data.fd;uint32_t epollEvents = eventItems[i].events;if (fd == mWakeEventFd.get()) {............................;} else {ssize_t requestIndex = mRequests.indexOfKey(fd);if (requestIndex >= 0) {int events = 0;if (epollEvents & EPOLLIN) events |= EVENT_INPUT;if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;if (epollEvents & EPOLLERR) events |= EVENT_ERROR;if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;//将epoll监控到的event push到mResponses中pushResponse(events, mRequests.valueAt(requestIndex));} else {ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is ""no longer registered.", epollEvents, fd);}}}
Done: mNextMessageUptime = LLONG_MAX;//处理mMessageEnvelopes中具体message,这里面的message哪里来的,现在还不知道while (mMessageEnvelopes.size() != 0) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);if (messageEnvelope.uptime <= now) {{ // obtain handlersp<MessageHandler> handler = messageEnvelope.handler;Message message = messageEnvelope.message;mMessageEnvelopes.removeAt(0);mSendingMessage = true;mLock.unlock();handler->handleMessage(message); //需要重点分析的函数} // release handlermLock.lock();mSendingMessage = false;result = POLL_CALLBACK;} else {// The last message left at the head of the queue determines the next wakeup time.mNextMessageUptime = messageEnvelope.uptime;break;}}mLock.unlock();//处理epoll监控到的事件,通过提前注册的callback来处理此event,这些callback是什么时候注册的,现在还不知道for (size_t i = 0; i < mResponses.size(); i++) {Response& response = mResponses.editItemAt(i);if (response.request.ident == POLL_CALLBACK) {int fd = response.request.fd;int events = response.events;void* data = response.request.data;int callbackResult = response.request.callback->handleEvent(fd, events, data);if (callbackResult == 0) {removeFd(fd, response.request.seq);}response.request.callback.clear();result = POLL_CALLBACK;}}return result;
}

通过上面的代码分析pollInner主要逻辑如下:

  1. 通过epoll_wait监控获取event,将获取到的event封装成response结构push到mResponses中。
  2. 处理mMessageEnvelopes中的message:
    sp handler = messageEnvelope.handler;
    Message message = messageEnvelope.message;
    handler->handleMessage(message);
  3. 处理mResponses中每一个response:
    response.request.callback->handleEvent(fd, events, data)

现在新疑问来了,

  1. mMessageEnvelopes中message哪里来的?
  2. epoll中被监控的fd是什么后添加到epoll中的 ?
void MessageQueue::setEventConnection(const sp<EventThreadConnection>& connection) {if (mEventTube.getFd() >= 0) {mLooper->removeFd(mEventTube.getFd());}mEvents = connection;mEvents->stealReceiveChannel(&mEventTube);//往epoll中添加mEventTube的fd,其中MessageQueue::cb_eventReceiver是epoll监控到事件后执行的callbackmLooper->addFd(mEventTube.getFd(), 0, Looper::EVENT_INPUT, MessageQueue::cb_eventReceiver,this);
}
//找到答案了: "epoll中被监控的fd是什么后添加到epoll中的"
int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {....................;{ // acquire lockAutoMutex _l(mLock);Request request;request.fd = fd;request.ident = ident;request.events = events;request.seq = mNextRequestSeq++;request.callback = callback;request.data = data;struct epoll_event eventItem;request.initEventItem(&eventItem);ssize_t requestIndex = mRequests.indexOfKey(fd);if (requestIndex < 0) {int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, fd, &eventItem);mRequests.add(fd, request);} else {........................;}} // release lockreturn 1;
}int MessageQueue::cb_eventReceiver(int fd, int events, void* data) {MessageQueue* queue = reinterpret_cast<MessageQueue*>(data);//执行MessageQueue中的eventReceiver函数return queue->eventReceiver(fd, events);
}int MessageQueue::eventReceiver(int /*fd*/, int /*events*/) {ssize_t n;DisplayEventReceiver::Event buffer[8];//监控到mEventTube中有事件后,从mEventTube中读取事件,处理事件while ((n = DisplayEventReceiver::getEvents(&mEventTube, buffer, 8)) > 0) {for (int i = 0; i < n; i++) {if (buffer[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {mHandler->dispatchInvalidate(buffer[i].vsync.expectedVSyncTimestamp);break;}}}return 1;
}void MessageQueue::Handler::dispatchInvalidate(nsecs_t expectedVSyncTimestamp) {if ((android_atomic_or(eventMaskInvalidate, &mEventMask) & eventMaskInvalidate) == 0) {mExpectedVSyncTime = expectedVSyncTimestamp;//从mEventTube读取VSYNC事件后,先Looper中发一个MessageQueue::INVALIDATE类型的消息mQueue.mLooper->sendMessage(this, Message(MessageQueue::INVALIDATE));}
}void Looper::sendMessage(const sp<MessageHandler>& handler, const Message& message) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);sendMessageAtTime(now, handler, message);
}void Looper::sendMessageDelayed(nsecs_t uptimeDelay, const sp<MessageHandler>& handler,const Message& message) {nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);sendMessageAtTime(now + uptimeDelay, handler, message);
}void Looper::sendMessageAtTime(nsecs_t uptime, const sp<MessageHandler>& handler,const Message& message) {{ // acquire lockAutoMutex _l(mLock);size_t messageCount = mMessageEnvelopes.size();while (i < messageCount && uptime >= mMessageEnvelopes.itemAt(i).uptime) {i += 1;}MessageEnvelope messageEnvelope(uptime, handler, message);//找到答案了: "mMessageEnvelopes中message哪里来的?"mMessageEnvelopes.insertAt(messageEnvelope, i, 1);if (mSendingMessage) {return;}} // release lockif (i == 0) {wake();}
}

上面的问题解答后,又来两个新疑问:

  1. MessageQueue::setEventConnection(…) 什么时候有谁调用的 ?
  2. mEventTube是个什么玩意?

下一篇文章继续深入分析,然后画图…

相关文章:

【surfaceflinger源码分析】surfaceflinger进程的消息驱动模型

概述 对于surfaceflinger大多数人都知道它的功能是做图形合成的&#xff0c;用英语表示就是指composite。其大致框图如下: 各个Android app将自己的图形画面通过surface为载体通过AIDL接口(Binder IPC)传递到surfaceflinger进程surfaceflinger进程中的composition engine与HW…...

「架构师」001计算机组成与体系结构

文章目录 前言一、计算机结构1.1 计算机组成结构1.2 CPU组成1.3 冯诺依曼结构与哈佛结构二、存储结构2.1 层次化存储结构2.2 Cache三、数据传输控制方式四、总线五、CISC与RISC六、流水线七、校验码八、嵌入式前言 本文主要介绍计算机组成与体系结构。 一、计算机结构 1.1 计…...

既然有HTTP协议,为什么还要有RPC

既然有HTTP协议&#xff0c;为什么还要有RPC&#xff1f; 从TCP聊起 作为一个程序员&#xff0c;假设我们需要在A电脑的进程发一段数据到B电脑的进程&#xff0c;我们一般会在代码里使用socket进行编程。 这时候&#xff0c;我们可选项一般也就TCP和UDP二选一。TCP可靠&…...

【新2023】华为OD机试 - 选座位(Python)

华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 选座位 题目 疫情期间需要大家保证一定的社交距离 公司组织开交流会议,座位有一排共N个座位 编号分别为[0...n-1] 要求员工一个接着一个进入会议室 并且还可以在任何时候离开会议室 每当一个员工进入时…...

数据分析与SAS学习笔记4

INPUT语句&#xff1a;格式修饰符&#xff1a; “:” 修饰符。表示从下一个非空格列读入数据&#xff0c;直到:1 遇到再下一个空格列&#xff1b; 2 读到预先定义的变量长度&#xff1b; 3 数据行结束。哪个先出现就在哪儿结束。 “&” 修饰符。表示从下一个非空格列读入…...

Xepor:一款针对逆向工程和安全分析的Web路由框架

关于Xepor Xepor是一款专为逆向分析工程师和安全研究专家设计的Web路由框架&#xff0c;该工具可以为研究人员提供类似Flask API的功能&#xff0c;支持以人类友好的方式拦截和修改HTTP请求或HTTP响应信息。 该项目需要与mitmproxy一起结合使用&#xff0c;用户可以使用Xepor…...

Hadoop核心组成和生态系统简介

一、Hadoop的概念 Hadoop是一个由Apache基金会所开发的分布式系统基础架构。用户可以在不了解分布式底层细节的情况下&#xff0c;开发分布式程序。充分利用集群的威力进行高速运算和存储。Hadoop实现了一个分布式文件系统&#xff08; Distributed File System&#xff09;&am…...

Flutter-Charts_painter大数据量绘制性能优化-数据收敛

Flutter-Charts_painter大数据量绘制性能优化-数据收敛 1、背景介绍 HRV测量仪器上传的数据&#xff0c;每秒有250个数据&#xff0c;业务上需要测量180秒&#xff0c;预计有3w-5w个数据点需要绘制到折线图上去。Charts_painter绘制这么大的数据是时候会有些卡顿&#xff0c;…...

使用 GeForce Experience 更新 NVIDIA GPU 显卡驱动

使用 GeForce Experience 更新 NVIDIA GPU 显卡驱动1. NVIDIA GeForce Experience 2. 驱动程序 -> 检查更新文件 3. 下载 如果有可用的新版驱动的话&#xff0c;点击后方的 [下载] 按钮即可。 4. 安装 [快速安装] 按照默认设置安装驱动&#xff0c;[自定义安装] 可以自行…...

Java泛型的<? super T>,<? extend T>的区别

&#xff1f; extends T ? extends T 描述了通配符上界, 即具体的泛型参数需要满足条件: 泛型参数必须是 T 类型或它的子类, 例如: List<? extends Number> numberArray new ArrayList<Number>(); // Number 是 Number 类型的 List<? extends Number>…...

如何做出好看的Excel可视化图表?

可视化死磕excel是不行的&#xff0c;作为数据分析行业的偷懒大户&#xff0c;分享一些我在可视化工具上的使用心得&#xff0c;总结了三大类&#xff1a;快速出图类、专业图表类、高端大屏类。个人经验&#xff0c;大家按需采纳&#xff1a; 一、快速出图类 如果你只是因为偶…...

智能吸吹一体式方案设计特点

一、家用吸吹一体吸尘器方案研发设计要素&#xff1a; 1.小巧的机身设计&#xff0c;一手掌握&#xff0c;无论是床底、沙发下还是家具缝隙之中都能够使用。 2.无线&#xff0c;插电两用&#xff0c;在家方便可插电使用。内置可充电锂电池&#xff0c;充满电也可无线使用。 3.采…...

CSDN 编辑器 Marddown 语法备忘

原文链接&#xff1a;https://blog.csdn.net/blogdevteam/article/details/103478461 本文对其二次加工&#xff0c;增加渲染样式、补充例程、添加未收录的常用语法。 CSDN Markdown 编辑器遵循 CommonMark spec 语法规范。 快捷键 撤销&#xff1a;Ctrl/Command Z 重做&…...

回归预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络多输入单输出回归预测

回归预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络多输入单输出回归预测 目录回归预测 | MATLAB实现NGO-BiLSTM北方苍鹰算法优化双向长短期记忆网络多输入单输出回归预测预测效果基本介绍程序设计参考资料预测效果 基本介绍 Matlab实现NGO-BiLSTM北方苍鹰算法…...

Linux——操作系统安装

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…...

AFLNET lightftp项目报错解决方法

在学习AFLNET的时候&#xff0c;本人尝试对示例项目中的lightftp进行fuzz,而后出现如下报错&#xff1a; AFLNet - the states hashtable should always contain an entry of the initial state 在github项目issue里看到了有人的问题和我一摸一样&#xff0c;Stack Overflow里…...

av 146 003

121. 团队章程的目标是什么? A. 使团队正规化&#xff0c;以便能够清楚地了解资源分配和参与情况 B. 创造一个团队可以自我管理和自我指导的环境 C. 创造一个环境&#xff0c;使团队成员能够尽其所能地工作 D. 创造一种团队归属感&#xff0c;促进包容性和协作性的行为 12…...

干了1年“点点点”,自己辞职了,下一步是继续干测试还是转开发?

最后后台有个粉丝向我吐槽&#xff0c;不知道怎么选择了....下面就他的情况说说怎么选择&#xff1f; 目前已经提桶跑路&#xff0c;在大工厂里混了半年初级低级功能测试经验&#xff0c;并没有什么用。测试培训班来的。从破山村贫困户贫困专项出去的&#xff0c;学校上海的。…...

国产技术迎来突破,14nm芯片横空出世,低代码也有好消息

芯片&#xff0c;被称为工业时代的“粮食”&#xff0c;小到手机手环&#xff0c;大到飞机轮船&#xff0c;几乎各个行业都不离开芯片的支持&#xff0c;其重要性不言而喻。而我国在这一领域一直较为薄弱。 一、“芯片之路坎坷” 由于国内半导体芯片市场底子薄弱、没有主动权…...

使用clickhouse-backup工具备份clickhouse数据库

工具官网&#xff1a;https://github.com/AlexAkulov/clickhouse-backup/dockerhub工具官网&#xff1a;https://hub.docker.com/r/alexakulov/clickhouse-backup注意&#xff1a;这个工具只支持MergeTree 系列表引擎一、clickhouse在容器外的备份和恢复若clickhouse装在容器外…...

python cartopy绘制扇形区域图/cartopy绘制北极部分区域

问题 当绘图时&#xff0c;往往并不需要绘制整块区域&#xff0c;而是想聚焦于局部地区&#xff0c;此时我们需要绘制扇形图。 在cartopy中&#xff0c;只提供普通正方形的框架&#xff0c;如果我们需要其他&#xff0c;边界&#xff0c;需要自己去绘制&#xff0c;最常见的是…...

如何设置股票接口版交易软件的指标涨跌家数?

如何设置股票接口版交易软件指标涨跌家数&#xff1f;今天小编就以通达信为例给大家介绍一下&#xff0c;很多人其实不知道通达信里面有个很厉害的股票情绪的指标&#xff0c;叫做通达信涨跌家数&#xff0c;打开在通达信软件k线界面&#xff0c;然后输入880005就可以找到了。下…...

C++之lambda函数(匿名函数)

lambda函数简介lambda函数是C11标准新增的语法&#xff0c;也称为lambda表达式或匿名函数。lambda函数的特点是&#xff1a;距离近、简洁、高效和功能强大。优点声明式编程风格&#xff1a;就地匿名定义目标函数或函数对象&#xff0c;有更好的可读性和可维护性。简洁&#xff…...

WGCNA | 值得你深入学习的生信分析方法!~(网状分析-第四步-模块的功能注释)

1写在前面 前面我们用WGCNA分析得到多个模块&#xff0c;其中有一些模块和我们感兴趣的表型或者临床特征是相关的。&#x1f973; 接着就是要做模块的富集分析了&#xff0c;帮助我们了解这些模块的基因都有哪些已知的功能&#xff0c;涉及到哪些通路&#xff0c;在哪些疾病中最…...

如何看待年轻人躺平式生活观?

theme: smartblue 如何看待年轻人躺平式生活观&#xff1f; 躺平&#xff1a;网络流行词。指无论对方做出什么反应&#xff0c;你内心都毫无波澜&#xff0c;对此不会有任何反应或者反抗&#xff0c;表示顺从心理。另外在部分语境中表示为&#xff1a;瘫倒在地&#xff0c;…...

JS 设计模式 - 怎么让你的代码提示一个档次

设计模式是我们在解决一些问题的时候 &#xff0c;针对特定的问题给出的简介并且优化的处理方案 这篇文章说提及到的 JavaScript 设计模式将围绕着封装方法类来展开叙述 构造器模式 构造器模式本质就是我们平常在编码中常用的封装方法&#xff0c;重复利用构造函数 // 这是…...

遮挡贴图(Occlusion Map)和微表面贴图(Microsurface Map)

遮挡贴图&#xff08;Occlusion Map&#xff09; 在3D图形学中&#xff0c;遮挡&#xff08;Occlusion&#xff09;是指光被物体挡住。即便是在PBR中&#xff0c;环境光在某些应该被遮挡的地方&#xff0c;也会以古怪的方式被反射。遮挡贴图&#xff08;Occlusion Map&#xff…...

【Vue】基本交互指令

Vue el挂载点 <div id"app">{{message}} </div> <script>var app new Vue({el:"#app",data:{message:"hello"}}) </script>Vue实例的作用范围&#xff1a;管理el选项命中的元素及其内部的后代元素使用其他的选择器&a…...

MySQL 中的 distinct 和 group by 哪个效率更高?

结论先说大致的结论&#xff08;完整结论在文末&#xff09;&#xff1a;在语义相同&#xff0c;有索引的情况下group by和distinct都能使用索引&#xff0c;效率相同。在语义相同&#xff0c;无索引的情况下&#xff1a;distinct效率高于group by。原因是distinct 和 group by…...

Spring 框架源码(六) Bean的生命周期全流程源码解析

Spring框架作为Java王国的地基&#xff0c;我觉得它包含了很多精妙的设计&#xff0c;例如Bean工厂设计、Bean的生命周期、tx、aop、web、mvc等&#xff0c;最核心基本的Bean设计是Spring 的框架的灵魂&#xff0c;本文就Bean的生命周期全流程做源码程度上的解析&#xff0c;欢…...

芜湖市建设工程质量监督站官方网站/公关公司排名

Redis分布式锁锁的特点为了保证数据的最终一致性&#xff0c;使用Redis分布式锁 当多个进程不在同一个系统中&#xff0c;用分布式锁控制多个进程对资源的访问。 锁的特点 首先&#xff0c;为了确保分布式锁可用&#xff0c;我们至少要确保锁的实现同时满足以下四个条件&…...

做网站泰安/青岛网站建设培训学校

C/C预处理指令常见的预处理指令如下&#xff1a; #空指令&#xff0c;无任何效果#include包含一个源代码文件#define定义宏#undef取消已定义的宏#if如果给定条件为真&#xff0c;则编译下面代码#ifdef如果宏已经定义&#xff0c;则编译下面代码#ifndef如果宏没有定义&#xff0…...

永久免费企业网站建设/深圳seo招聘

props.load(new FileInputStream("db.properties ")); 是读取当前目录的db.properties 文件 getClass.getResourceAsStream("db.properties "); 是读取当前类所在位置一起的db.properties 文件 getClass.getResourceAsStream("/db.properties ")…...

做日本机械零件的外贸网站/seo优化的网站

线扫相机的原理&#xff1a;线扫相机一般一次只拍摄一条线&#xff08;线宽通常是1个像素&#xff09;&#xff0c;在机构运动的过程中&#xff0c;线扫相机不断地拍摄线&#xff0c;于是“聚线成面”&#xff0c;这就是线扫相机成像的原理。 线扫相机的原理决定了&#xff0c;…...

广州商城网站建设/百度关键词查询工具

问题&#xff1a; 有位来自日本的用户发邮件&#xff0c;请求我们在Spotfire报表&#xff08;趋势分析报告&#xff09; 网页上添加一个“Edit”按钮。说是以前是有的&#xff0c;现在的版本中找不到了。她需要对Spotifre的报表做一些优化&#xff0c;再报告给相关试验的CRA们…...

wordpress如何添加首页图片/抖音黑科技引流推广神器

今天调试代码的过程中&#xff0c;F8失效 解决办法 关掉有道词典&#xff01;...