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

Android开机动画启动流程

首语

在Android设备开机启动时,会展示Android开机动画,用于增加用户体验和展示设备品牌等信息。它也是Android系统启动的一部分。开机动画是由bootanimation负责的,因此首先先了解下bootanimation是如何启动的。

bootanimation 启动脚本分析

init进程中第二阶段(SecondStageMain)的主要工作有初始化属性服务,加载启动脚本,解析init.rc文件等。

源码路径:system/core/init/init.cpp

int SecondStageMain(int argc, char** argv) {...LoadBootScripts(am, sm);...
}
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {Parser parser = CreateParser(action_manager, service_list);std::string bootscript = GetProperty("ro.boot.init_rc", "");if (bootscript.empty()) {parser.ParseConfig("/system/etc/init/hw/init.rc");if (!parser.ParseConfig("/system/etc/init")) {late_import_paths.emplace_back("/system/etc/init");}// late_import is available only in Q and earlier release. As we don't// have system_ext in those versions, skip late_import for system_ext.parser.ParseConfig("/system_ext/etc/init");if (!parser.ParseConfig("/vendor/etc/init")) {late_import_paths.emplace_back("/vendor/etc/init");}if (!parser.ParseConfig("/odm/etc/init")) {late_import_paths.emplace_back("/odm/etc/init");}if (!parser.ParseConfig("/product/etc/init")) {late_import_paths.emplace_back("/product/etc/init");}} else {parser.ParseConfig(bootscript);}
}

在init.rc文件中,可以看到通过class_start来启动 classname 为 core 的 Service。在bootanimation.rc文件中,可以清楚看到Service name为bootanim,执行程序路径为:/system/bin/bootanimation,类名:core。disabled表示系统启动时,不会自动启动bootanimation。那是谁启动bootanimation呢?

SurfaceFlinger它负责管理图形内容的渲染,并将多个图层(包括应用程序窗口、系统UI元素和硬件覆盖层)合成到设备的屏幕上。所以首先需要启动SurfaceFlinger,开机动画的渲染和合成是它完成的,继续分析SurfaceFlinger启动流程。

源码路径:system/core/rootdir/init.rc

...# Start standard binderized HAL daemonsclass_start halclass_start core
...

源码路径:frameworks/base/cmds/bootanimation/bootanim.rc

service bootanim /system/bin/bootanimationclass core animationuser graphicsgroup graphics audiodisabledoneshotioprio rt 0task_profiles MaxPerformance

SurfaceFlinger启动流程

而surfaceflinger.rc文件中,可以清楚看到Service name为surfaceflinger,执行程序路径为:/system/bin/surfaceflinger,类名:core。

因此,可以知道SurfaceFlinger是在init进程启动第二阶段进行启动的。

源码路径:frameworks/native/services/surfaceflinger/surfaceflinger.rc

service surfaceflinger /system/bin/surfaceflingerclass core animationuser systemgroup graphics drmrpc readproccapabilities SYS_NICEonrestart restart --only-if-running zygotetask_profiles HighPerformancesocket pdx/system/vr/display/client     stream 0666 system graphics u:object_r:pdx_display_client_endpoint_socket:s0socket pdx/system/vr/display/manager    stream 0666 system graphics u:object_r:pdx_display_manager_endpoint_socket:s0socket pdx/system/vr/display/vsync      stream 0666 system graphics u:object_r:pdx_display_vsync_endpoint_socket:s0

看下SurfaceFlinger的main函数,创建了SurfaceFlinger并且初始化,调用StartPropertySetThread的Start函数。

源码路径:frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp

int main(int, char**) {...// instantiate surfaceflingersp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();...// initialize before clients can connectflinger->init();...
}

源码路径:frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp

void SurfaceFlinger::init() {...mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);if (mStartPropertySetThread->Start() != NO_ERROR) {ALOGE("Run StartPropertySetThread failed!");}...
}

可以看到将系统属性service.bootanim.exit/service.bootanim.progress设置为0,并将ctl.start设置为bootanim,当系统属性发生改变时,init进程就会接收到一个系统属性变化通知,这个通知最终是由在init进程中的函数handle_property_set_fd来处理的。设置ctl.start表示启动一个服务,这样bootanimation就被启动了。

源码路径:frameworks/native/services/surfaceflinger/StartPropertySetThread.cpp

status_t StartPropertySetThread::Start() {return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}bool StartPropertySetThread::threadLoop() {// Set property service.sf.present_timestamp, consumer need check its readinessproperty_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");// Clear BootAnimation exit flagproperty_set("service.bootanim.exit", "0");property_set("service.bootanim.progress", "0");// Start BootAnimation if not startedproperty_set("ctl.start", "bootanim");// Exit immediatelyreturn false;
}

bootanimation启动流程

分析bootanimation的main函数,首先判断是否禁用了启动动画,没有则创建一个binder线程池,再创建BootAnimation,等待SurfaceFlinger启动完成。

源码路径:frameworks/base/cmds/bootanimation/bootanimation_main.cpp

int main()
{setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);//是否禁用了启动动画bool noBootAnimation = bootAnimationDisabled();ALOGI_IF(noBootAnimation,  "boot animation disabled");if (!noBootAnimation) {//创建binder线程池sp<ProcessState> proc(ProcessState::self());ProcessState::self()->startThreadPool();// create the boot animation object (may take up to 200ms for 2MB zip)sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());waitForSurfaceFlinger();boot->run("BootAnimation", PRIORITY_DISPLAY);ALOGV("Boot animation set up. Joining pool.");IPCThreadState::self()->joinThreadPool();}return 0;
}

源码路径:frameworks/base/cmds/bootanimation/BootAnimationUtil.cpp

bool bootAnimationDisabled() {char value[PROPERTY_VALUE_MAX];//启动动画调试模式property_get("debug.sf.nobootanimation", value, "0");if (atoi(value) > 0) {return true;}property_get("ro.boot.quiescent", value, "0");if (atoi(value) > 0) {// Only show the bootanimation for quiescent boots if this system property is set to enabled//禁用启动动画if (!property_get_bool("ro.bootanim.quiescent.enabled", false)) {return true;}}return false;
}void waitForSurfaceFlinger() {// TODO: replace this with better waiting logic in future, b/35253872int64_t waitStartTime = elapsedRealtime();sp<IServiceManager> sm = defaultServiceManager();const String16 name("SurfaceFlinger");const int SERVICE_WAIT_SLEEP_MS = 100;const int LOG_PER_RETRIES = 10;int retry = 0;while (sm->checkService(name) == nullptr) {retry++;if ((retry % LOG_PER_RETRIES) == 0) {ALOGW("Waiting for SurfaceFlinger, waited for %" PRId64 " ms",elapsedRealtime() - waitStartTime);}usleep(SERVICE_WAIT_SLEEP_MS * 1000);};int64_t totalWaited = elapsedRealtime() - waitStartTime;if (totalWaited > SERVICE_WAIT_SLEEP_MS) {ALOGI("Waiting for SurfaceFlinger took %" PRId64 " ms", totalWaited);}
}

BootAnimation类有几个重要函数

  • onFirstRef(),属于父类RefBase,它用于实现引用计数的对象管理,新增引用计数时调用。
  • binderDied() ,Binder结束时,就会回调binderDied()方法。
  • readyToRun() ,Thread执行前的初始化工作。
  • threadLoop() ,线程根据逻辑是否循环执行。
  • android(),显示系统默认的开机画面。
  • movie(),显示用户自定义的开机动画。
  • loadAnimation(),加载动画。
  • playAnimation(),播放动画。
  • checkExit(),检查是否退出动画。

源码路径:frameworks/base/cmds/bootanimation/BootAnimation.h

private:virtual bool        threadLoop();virtual status_t    readyToRun();virtual void        onFirstRef();virtual void        binderDied(const wp<IBinder>& who);...//系统默认的开机画面bool android();//用户自定义的开机动画bool movie();...//加载动画Animation* loadAnimation(const String8&);//播放动画bool playAnimation(const Animation&);void releaseAnimation(Animation*) const;bool parseAnimationDesc(Animation&);bool preloadZip(Animation &animation);void findBootAnimationFile();bool findBootAnimationFileInternal(const std::vector<std::string>& files);bool preloadAnimation();EGLConfig getEglConfig(const EGLDisplay&);ui::Size limitSurfaceSize(int width, int height) const;void resizeSurface(int newWidth, int newHeight);void projectSceneToWindow();bool shouldStopPlayingPart(const Animation::Part& part, int fadedFramesCount,int lastDisplayedProgress);//检查是否退出动画void checkExit();

BootAnimation构造函数中,创建了SurfaceComposerClient,mSession用来和SurfaceFlinger执行Binder进程间通信,执行linkToComposerDeath方法用于获取SurfaceFlinger死亡通知,preloadAnimation方法开始加载动画,首先去查询动画文件,动画文件的存放位置如代码中定义所示。动画文件是按照指定位置顺序读取,如果读取到当前位置动画文件,则不读取后续动画文件。

源码路径:frameworks/base/cmds/bootanimation/BootAnimation.cpp

static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip";
static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip";
static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip";
static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip";
static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip";
static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip";
static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip";
static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip";
static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip";
static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip";static constexpr const char* PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE = "/product/media/userspace-reboot.zip";
static constexpr const char* OEM_USERSPACE_REBOOT_ANIMATION_FILE = "/oem/media/userspace-reboot.zip";
static constexpr const char* SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE = "/system/media/userspace-reboot.zip";BootAnimation::BootAnimation(sp<Callbacks> callbacks): Thread(false), mLooper(new Looper(false)), mClockEnabled(true), mTimeIsAccurate(false),mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) {mSession = new SurfaceComposerClient();std::string powerCtl = android::base::GetProperty("sys.powerctl", "");if (powerCtl.empty()) {mShuttingDown = false;} else {mShuttingDown = true;}ALOGD("%sAnimationStartTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",elapsedRealtime());
}
void BootAnimation::onFirstRef() {//接收SurfaceFlinger死亡通知status_t err = mSession->linkToComposerDeath(this);SLOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));if (err == NO_ERROR) {// Load the animation content -- this can be slow (eg 200ms)// called before waitForSurfaceFlinger() in main() to avoid waitALOGD("%sAnimationPreloadTiming start time: %" PRId64 "ms",mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());preloadAnimation();ALOGD("%sAnimationPreloadStopTiming start time: %" PRId64 "ms",mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());}
}
bool BootAnimation::preloadAnimation() {findBootAnimationFile();if (!mZipFileName.isEmpty()) {//加载动画mAnimation = loadAnimation(mZipFileName);return (mAnimation != nullptr);}return false;
}
void BootAnimation::findBootAnimationFile() {// If the device has encryption turned on or is in process// of being encrypted we show the encrypted boot animation.char decrypt[PROPERTY_VALUE_MAX];property_get("vold.decrypt", decrypt, "");bool encryptedAnimation = atoi(decrypt) != 0 ||!strcmp("trigger_restart_min_framework", decrypt);if (!mShuttingDown && encryptedAnimation) {static const std::vector<std::string> encryptedBootFiles = {PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE,};if (findBootAnimationFileInternal(encryptedBootFiles)) {return;}}const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1;static const std::vector<std::string> bootFiles = {APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE,OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE};static const std::vector<std::string> shutdownFiles = {PRODUCT_SHUTDOWNANIMATION_FILE, OEM_SHUTDOWNANIMATION_FILE, SYSTEM_SHUTDOWNANIMATION_FILE, ""};static const std::vector<std::string> userspaceRebootFiles = {PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE, OEM_USERSPACE_REBOOT_ANIMATION_FILE,SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE,};if (android::base::GetBoolProperty("sys.init.userspace_reboot.in_progress", false)) {findBootAnimationFileInternal(userspaceRebootFiles);} else if (mShuttingDown) {findBootAnimationFileInternal(shutdownFiles);} else {findBootAnimationFileInternal(bootFiles);}
}
bool BootAnimation::findBootAnimationFileInternal(const std::vector<std::string> &files) {for (const std::string& f : files) {if (access(f.c_str(), R_OK) == 0) {mZipFileName = f.c_str();return true;}}return false;
}
BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) {if (mLoadedFiles.indexOf(fn) >= 0) {SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",fn.string());return nullptr;}ZipFileRO *zip = ZipFileRO::open(fn);if (zip == nullptr) {SLOGE("Failed to open animation zip \"%s\": %s",fn.string(), strerror(errno));return nullptr;}ALOGD("%s is loaded successfully", fn.string());Animation *animation =  new Animation;animation->fileName = fn;animation->zip = zip;animation->clockFont.map = nullptr;mLoadedFiles.add(animation->fileName);//解析动画文件parseAnimationDesc(*animation);if (!preloadZip(*animation)) {releaseAnimation(animation);return nullptr;}mLoadedFiles.remove(fn);return animation;
}
bool BootAnimation::parseAnimationDesc(Animation& animation)  {String8 desString;if (!readFile(animation.zip, "desc.txt", desString)) {return false;}char const* s = desString.string();std::string dynamicColoringPartName = "";bool postDynamicColoring = false;// Parse the description file...
}
bootanimation.zip

动画文件的压缩包里都存在一个动画配置文件desc.txt,它是描述开机动画是如何显示的。我们以device/google/atv/products/bootanimations/bootanimation.zip动画压缩包为例进行分析,它是AndroidTV存储动画文件的路径。desc.txt内容如下:

第1行用来描述开机动画在屏幕显示的大小及帧率。这个定义指示 bootanimation 的播放分辨率为 512x416 像素,帧率为 60 帧/秒。分辨率定义了动画的宽度和高度,而帧率定义了动画播放的流畅程度,即每秒播放的帧数。

第2行c:表示清除命令。1:表示清除的起始帧。0:表示清除的结束帧。part0:表示需要清除的动画帧所在的文件夹路径。这个定义指示在播放动画时,从指定的文件夹 part0 中清除第 1 帧。这样可以控制在播放过程中是否清除特定的帧,以实现动画效果的变化或平滑的过渡效果。3-5行同理。

最后一行f:表示循环命令。0:表示循环的起始帧。0:表示循环的结束帧。part4:表示需要循环的动画帧所在的文件夹路径。10:表示循环次数。该行指示在播放动画时,从指定的文件夹中的起始帧到结束帧之间的帧进行循环播放,重复播放 10 次。

512 416 60
c 1 0 part0
c 1 15 part1
c 1 0 part2
c 1 0 part3
f 0 0 part4 10

动画配置文件还有指定播放顺序的,例如如下的配置,p:表示播放顺序命令。1:表示播放的顺序。0:表示播放的循环次数。0 表示无限循环。folder1:表示动画帧所在的文件夹路径。根据这个定义,folder1 是一个目录,包含了一组 bootanimation 动画帧文件。该行指示在播放动画时,按照顺序从 folder1 目录中加载帧并进行播放。

p 1 0 folder1 

加载动画执行完成后,接下来会执行主体函数threadLoop,首先判断自定义开机动画文件是否存在,如果不存在则执行Android方法,否则执行自定义动画Movie方法。Android和Movie方法最后都返回false。因此threadloop也返回false,代表代码只执行一次。最后获取service.bootanim.exit属性,如果值为1,循环就会退出,开机动画就会结束。service.bootanim.exit属性是在AMS中被修改为1的,在后面AMS中会讲到。

static const char EXIT_PROP_NAME[] = "service.bootanim.exit";
bool BootAnimation::threadLoop() {bool result;initShaders();// We have no bootanimation file, so we use the stock android logo// animation.if (mZipFileName.isEmpty()) {ALOGD("No animation file");//系统默认开机画面result = android();} else {//自定义开机动画显示result = movie();}mCallbacks->shutdown();eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);eglDestroyContext(mDisplay, mContext);eglDestroySurface(mDisplay, mSurface);mFlingerSurface.clear();mFlingerSurfaceControl.clear();eglTerminate(mDisplay);eglReleaseThread();IPCThreadState::self()->stopProcess();return result;
}bool BootAnimation::android() {glActiveTexture(GL_TEXTURE0);SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",elapsedRealtime());//android-logo-mask.png和android-logo-shine.png保存在frameworks/base/core/res/assets/images/路径下initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");mCallbacks->init({});...return false;
}
bool BootAnimation::movie() {if (mAnimation == nullptr) {mAnimation = loadAnimation(mZipFileName);}if (mAnimation == nullptr)return false;...playAnimation(*mAnimation);releaseAnimation(mAnimation);mAnimation = nullptr;return false;
}
bool BootAnimation::playAnimation(const Animation& animation) {...checkExit();
}
void BootAnimation::checkExit() {// Allow surface flinger to gracefully request shutdownchar value[PROPERTY_VALUE_MAX];property_get(EXIT_PROP_NAME, value, "0");int exitnow = atoi(value);if (exitnow) {requestExit();}
}

总结

init进程是Android系统中的第一个用户空间进程。它负责启动各个系统服务和应用程序。在init进程启动过程中,SurfaceFlinger也被启动,SurfaceFlinger是Android中的显示系统服务,负责管理屏幕显示和图形渲染。开机动画需要使用SurfaceFlinger来显示。然后bootanimation也启动,进行开机动画的播放。bootanimation.zip中包含动画文件和动画配置文件。最终,当所有系统服务和应用程序启动完毕,开机动画结束,进入系统主界面。

相关文章:

Android开机动画启动流程

首语 在Android设备开机启动时&#xff0c;会展示Android开机动画&#xff0c;用于增加用户体验和展示设备品牌等信息。它也是Android系统启动的一部分。开机动画是由bootanimation负责的&#xff0c;因此首先先了解下bootanimation是如何启动的。 bootanimation 启动脚本分析…...

react_13

React Router //-dom代表给浏览器应用使用的 npm install react-router-dom 目前版本是 "react-router-dom": "^6.18.0" 使用 新建文件 src/router/MyRouter.tsx import { Navigate, RouteObject, useRoutes } from "react-router-dom"; imp…...

vscode git提交

...

LangChain+LLM实战---实用Prompt工程讲解

原文&#xff1a;Practical Prompt Engineering 注&#xff1a;本文中&#xff0c;提示和prompt几乎是等效的。 这是一篇非常全面介绍Prompt的文章&#xff0c;包括prompt作用于大模型的一些内在机制&#xff0c;和prompt可以如何对大模型进行“微调”。讲清楚了我们常常听到的…...

虚拟机备份中的CBT技术

虚拟机备份的CBT&#xff08; Changed Block Tracking&#xff09;模式是一种备份模式&#xff0c;它能够识别和跟踪自上次备份后虚拟机中被修改过的块&#xff0c;这些修改会被存放到日志文件中。在启用CBT模式之后&#xff0c;备份软件会利用这个功能进行增量备份。 启用CBT…...

云服务器哪家便宜靠谱 | 简单了解亚马逊云科技发展史

云服务器哪家便宜又靠谱呢&#xff1f;为什么说亚马逊云科技在这道题答案的第一行&#xff0c;一篇故事告诉你。 1994年&#xff0c;杰夫贝索斯在西雅图创建了亚马逊&#xff0c;最初只是一个在线书店。 1997年&#xff0c;亚马逊在纳斯达克交易所上市&#xff0c;成为一家公…...

【LeetCode】每日一题 2023_11_6 最大单词长度乘积

文章目录 刷题前唠嗑题目&#xff1a;最大单词长度乘积题目描述代码与解题思路偷看大佬题解 结语 刷题前唠嗑 LeetCode? 启动&#xff01;&#xff01;&#xff01; 题目&#xff1a;最大单词长度乘积 题目链接&#xff1a;318. 最大单词长度乘积 题目描述 代码与解题思路…...

【小白专用】PHP中的JSON转换操作指南 23.11.06

一、JSON的基础知识 1.1JSON数据格式 JSON数据格式是一组键值对的集合&#xff0c;通过逗号分隔。键值对由“键”和“值”组成&#xff0c;中间使用冒号分隔。JSON数据格式可以嵌套&#xff0c;而且可以使用数组 二、PHP中的JSON函数 JSON的操作需要使用编程语言进行处理&am…...

Web3游戏的十字路口:沿用传统IP还是另起炉灶?

人们经常问我对 Web3 游戏有什么看法。因此&#xff0c;我想以书面形式概述一下我目前的想法。 让我先澄清一下&#xff1a;我不是专家。这不是一篇深入探讨游戏世界精细指标如 MAU 或 D14 等的全面分析。请把这看作是我根据个人交流和研究&#xff0c;这反映我在游戏领域关注…...

【系统架构设计】架构核心知识:4 系统可靠性分析与设计

目录 1 可靠性 2 系统可靠性 2.1 可靠性指标 2.2 可靠性计算 2.2.1 串联系统 2.2.2 并联系统</...

什么是站群服务器的优点?

什么是站群服务器的优点&#xff1f; 1. 主要作用 在网站数量增多的今天&#xff0c;对于站群服务器的需求自然也会变得越来越多&#xff0c;那么&#xff0c;它的主要作用都是什么呢&#xff1f;站群服务器&#xff0c;就好像它的名字一样&#xff0c;是一个群体&#xff0c…...

图数据库Neo4j——SpringBoot使用Neo4j 简单增删改查 复杂查询初步

前言 图形数据库是专门用于存储图形数据的数据库&#xff0c;它使用图形模型来存储数据&#xff0c;并且支持复杂的图形查询。常见的图形数据库有Neo4j、OrientDB等。 Neo4j是用Java实现的开源NoSQL图数据库&#xff0c;本篇博客介绍如何在SpringBoot中使用Neo4j图数据库&…...

-- Could NOT find livox_ros_driver (missing: livox_ros_driver_DIR)

原因 缺少livox_ros_driver 包 解决办法如下 livox_ros_driver 地址 https://github.com/Livox-SDK/livox_ros_driver 下载下来放入ros的工作目录...

山东餐饮类行业可以办理那些认证?

在招投标中很多企业会因为缺少一些证书从而丢失加分项&#xff0c;所以很多行业都会关注那些针对性比较强的企业认证&#xff0c;今天就来讲一下餐饮类行业招投标有哪些证书可以帮助企业中标&#xff1f; 一、ISO三体系认证 ISO9001 质量管理体系 质量是取得成功的关键。由…...

【抖音自动评论的软件】评论888无偿分享,和其开发技术与开发流程的分享

先来看成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 短视频作为互联网时代的重要产物&#xff0c;已经成为人们生活中不可或缺的一部分。那么&#xff0c;如何通过短视频平台进行有效的运营和评论呢&#xff1f;本文将为您详细解析。 一…...

挑战100天 AI In LeetCode Day02(2)

挑战100天 AI In LeetCode Day02&#xff08;2&#xff09; 一、LeetCode介绍二、LeetCode 热题 HOT 100-42.1 题目2.2 题解 三、面试经典 150 题-43.1 题目3.2 题解 一、LeetCode介绍 LeetCode是一个在线编程网站&#xff0c;提供各种算法和数据结构的题目&#xff0c;面向程序…...

《尚医通》Vue3 项目+TypeScript 前端项目(持续更新,附带源码)

尚硅谷vue项目实战《尚医通》&#xff0c;Vue3项目TypeScript前端项目_哔哩哔哩_bilibili尚硅谷vue项目实战《尚医通》&#xff0c;Vue3项目TypeScript前端项目共计71条视频&#xff0c;包括&#xff1a;001_开篇介绍、002_尚医通项目的简介、003_Vite构建化工具初始化项目等&a…...

仪表盘 gauge

option {tooltip: {formatter: {a} <br/>{b} : {c}%},series: [{name: Pressure,type: gauge,startAngle: 225, // 起始角度&#xff0c;同极坐标endAngle: -45, // 终止角度&#xff0c;同极坐标// axisLine: {// //坐标轴轴线// show: false// },// splitLine: {//…...

常见面试题-MySQL专栏(三)MVCC、BufferPool

typora-copy-images-to: imgs 了解 MVCC 吗&#xff1f; 答&#xff1a; MVCC&#xff08;Multi-Version Concurrency Control&#xff09; 是用来保证 MySQL 的事务隔离性的&#xff0c;对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性&#xff0c;避免了频…...

CDN加速:国内外价格与企业云服务最佳搭配方案

随着互联网的快速发展&#xff0c;CDN&#xff08;内容分发网络&#xff09;已经成为了企业提供高质量、高速度内容传递的不可或缺的工具。CDN通过将内容分发到离用户更近的服务器上&#xff0c;提高了网站性能&#xff0c;减少了加载时间&#xff0c;改善了用户体验。在本文中…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

Admin.Net中的消息通信SignalR解释

定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error

在前端开发中&#xff0c;JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作&#xff08;如 Promise、async/await 等&#xff09;&#xff0c;开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝&#xff08;r…...

LangFlow技术架构分析

&#x1f527; LangFlow 的可视化技术栈 前端节点编辑器 底层框架&#xff1a;基于 &#xff08;一个现代化的 React 节点绘图库&#xff09; 功能&#xff1a; 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...

论文阅读:Matting by Generation

今天介绍一篇关于 matting 抠图的文章&#xff0c;抠图也算是计算机视觉里面非常经典的一个任务了。从早期的经典算法到如今的深度学习算法&#xff0c;已经有很多的工作和这个任务相关。这两年 diffusion 模型很火&#xff0c;大家又开始用 diffusion 模型做各种 CV 任务了&am…...