lvgl 界面管理器
lv_scr_mgr
lvgl 界面管理器
适配 lvgl 8.3
- 降低界面之间的耦合
- 使用较小的内存,界面切换后会自动释放内存
- 内存泄漏检测
使用方法
- 在lv_scr_mgr_port.h 中创建一个枚举,用于界面ID
- 为每个界面创建一个页面管理器句柄
- 将界面句柄添加到 lv_scr_mgr_port.c 数组中
- 在 lv_init() 后,对页面管理器进行初始化 lv_scr_mgr_init(NULL);
- 使用 lv_scr_mgr_switch 设置初始根界面
- 使用 lv_scr_mgr_push lv_scr_mgr_pop 对界面进行操作
git地址
git地址
gitee
/*********************************CopyRight ************************************* @file lv_scr_mgr.c* @author 不咸不要钱* @date 2023-10-11 13:4:36* @brief &#&* *******************************************************************************//* Includes ------------------------------------------------------------------*/
#include "lvgl.h"
#include "lv_scr_mgr.h"
#if !LV_SCR_MGR_REG_ENABLE
#include "lv_scr_mgr_port.c"
#endiftypedef struct
{uint32_t scr_cnt; void* param;lv_scr_mgr_handle_t **handles;
#if LV_SCR_MGR_PRINTF_MEMuint32_t *max_mem;
#endif
}scr_mgr_list_handle_t;static scr_mgr_list_handle_t mgr_list;
static lv_scr_mgr_stack_node_t* mgr_stack_top = NULL;
static lv_scr_mgr_stack_node_t* mgr_stack_root = NULL;static lv_scr_mgr_handle_t* find_handle_by_id(uint32_t id)
{for (int i = 0; i < mgr_list.scr_cnt; i++){if (mgr_list.handles[i]->scr_id == id){return mgr_list.handles[i];}}return NULL;
}
#if LV_SCR_MGR_PRINTF_MEM
static uint32_t* find_mem_addr_by_id(uint32_t id)
{for (int i = 0; i < mgr_list.scr_cnt; i++){if (mgr_list.handles[i]->scr_id == id){return &mgr_list.max_mem[i];}}return NULL;
}static void mem_max_printf(uint32_t id)
{static uint32_t mem_max = 0;lv_mem_monitor_t mon;lv_mem_monitor(&mon);if (mon.total_size - mon.free_size > mem_max){mem_max = mon.total_size - mon.free_size;LV_LOG_USER("used: %d (%d %%), frag: %d %%, biggest free: %d\n", mem_max,mon.used_pct,mon.frag_pct,(int)mon.free_biggest_size);}/* 当前界面最大使用内存 */uint32_t* page_max_mem = find_mem_addr_by_id(id);if (mon.total_size - mon.free_size > *page_max_mem){*page_max_mem = mon.total_size - mon.free_size;LV_LOG_USER("page id %d, used: %d (%d %%), frag: %d %%, biggest free: %d\n", id, *page_max_mem,mon.used_pct,mon.frag_pct,(int)mon.free_biggest_size);}
}static void anim_mem_max_printf(lv_event_t* e)
{lv_event_code_t event_code = lv_event_get_code(e);if (event_code == LV_EVENT_SCREEN_LOADED){mem_max_printf((uint32_t)lv_event_get_user_data(e));}
}
#endifstatic void scr_mgr_stack_free(void)
{lv_scr_mgr_stack_node_t* stack_node = NULL;/* 释放界面栈 */while (NULL != mgr_stack_top){stack_node = mgr_stack_top->prev;if(mgr_stack_top->handle->scr_destroy)mgr_stack_top->handle->scr_destroy();lv_mem_free((void*)mgr_stack_top);mgr_stack_top = stack_node;}mgr_stack_root = NULL;
}/*** @brief 入栈* @param tag 要入栈的句柄* @return 栈顶句柄
*/
static lv_scr_mgr_stack_node_t* scr_mgr_stack_push(lv_scr_mgr_handle_t* tag)
{lv_scr_mgr_stack_node_t* stack_node = NULL;stack_node = lv_mem_alloc(sizeof(lv_scr_mgr_stack_node_t));LV_ASSERT_MALLOC(stack_node);stack_node->handle = tag;stack_node->next = NULL;if (stack_node->handle->scr_first_create){stack_node->handle->scr_first_create();}if (tag->scr_create){stack_node->scr = tag->scr_create(stack_node->handle->scr_id, mgr_list.param);}else{LV_LOG_ERROR("no create fun!");}if (NULL == mgr_stack_top){stack_node->prev = NULL;mgr_stack_root = stack_node;}else{stack_node->prev = mgr_stack_top;mgr_stack_top->next = stack_node;}mgr_stack_top = stack_node;return stack_node;
}static int32_t scr_mgr_stack_pop(int32_t n)
{lv_scr_mgr_stack_node_t* stack_node = NULL;int32_t i = n;if ((NULL == mgr_stack_top) || (NULL == mgr_stack_top->prev)){return 0;}while (i){if ((NULL == mgr_stack_top) || (NULL == mgr_stack_top->prev)){break;}stack_node = mgr_stack_top->prev;if (mgr_stack_top->handle->scr_destroy){mgr_stack_top->handle->scr_destroy();}lv_mem_free((void*)mgr_stack_top);mgr_stack_top = stack_node;i--;}if (NULL != mgr_stack_top->handle->scr_create){mgr_stack_top->scr = mgr_stack_top->handle->scr_create(mgr_stack_top->handle->scr_id, mgr_list.param);}else{LV_LOG_ERROR("no create fun!");}if (i){LV_LOG_WARN("stack pop %d, but stack is %d", n, n-i);}return n - i;
}/*** @brief 切换界面* @param cur_scr 当前界面* @param stack_node 目标界面句柄* @param anim 切换界面动画开关* 关闭界面切换动画,切换界面时会先创建一个新的空界面,切换到空界面后,* 删除之前的界面,然后再创建切换到新界面,最后再删除中间界面。会节省内存。* 关闭界面切换动画,切换界面时直接创建新界面,然后再用动画切换到新界面。** @return true
*/
bool scr_mgr_switch(lv_obj_t* cur_scr, lv_scr_mgr_stack_node_t* stack_node, bool anim)
{lv_scr_load_anim_t load_anim = LV_SCR_MGR_LOAD_ANIM_DEFAULT;lv_obj_t* tmp_scr = NULL;if (anim){if ((stack_node->handle->anim_type != LV_SCR_LOAD_ANIM_NONE) && (LV_SCR_LOAD_ANIM_OUT_BOTTOM >= stack_node->handle->anim_type)){load_anim = stack_node->handle->anim_type;}#if LV_SCR_MGR_PRINTF_MEMlv_obj_add_event_cb(stack_node->scr, anim_mem_max_printf, LV_EVENT_SCREEN_LOADED, stack_node->handle->scr_id);
#endiflv_scr_load_anim(stack_node->scr, load_anim, LV_SCR_MGR_LOAD_ANIM_TIME, LV_SCR_MGR_LOAD_ANIM_DELAY, true);}else{if (NULL != cur_scr){tmp_scr = lv_obj_create(NULL);lv_scr_load(tmp_scr);lv_obj_del(cur_scr);cur_scr = NULL;}lv_scr_load(stack_node->scr);#if LV_SCR_MGR_PRINTF_MEMmem_max_printf(stack_node->handle->scr_id);
#endifif (NULL != tmp_scr){lv_obj_del(tmp_scr);}}return true;
}/*** @brief 初始化界面管理器* @param param 创建界面时的参数* @return
*/
bool lv_scr_mgr_init(void* param)
{mgr_list.param = param;
#if LV_SCR_MGR_REG_ENABLE#elsemgr_list.scr_cnt = sizeof(scr_mgr_handles) / sizeof(scr_mgr_handles[0]);mgr_list.handles = scr_mgr_handles;
#endif if (0 == mgr_list.scr_cnt){LV_LOG_ERROR("no screen!");return false;}#if LV_SCR_MGR_PRINTF_MEMmgr_list.max_mem = lv_mem_alloc(mgr_list.scr_cnt * sizeof(uint32_t*));LV_ASSERT(mgr_list.max_mem);memset(mgr_list.max_mem, 0, mgr_list.scr_cnt * sizeof(uint32_t*));
#endifreturn true;
}void lv_scr_mgr_deinit(void)
{mgr_list.param = NULL;
#if LV_SCR_MGR_PRINTF_MEMlv_mem_free(mgr_list.max_mem);
#endifscr_mgr_stack_free();
}void lv_scr_mgr_param_set(void* param)
{mgr_list.param = param;
}void* lv_scr_mgr_param_get(void)
{return mgr_list.param;
}/*** @brief 设置根界面* @param id 根界面序号* @param anim 动画开关* @return
*/
bool lv_scr_mgr_switch(uint32_t id, bool anim)
{lv_scr_mgr_handle_t* tag_handle = find_handle_by_id(id);lv_scr_mgr_handle_t* cur_handle = NULL;lv_scr_mgr_stack_node_t* stack_node = NULL;lv_obj_t* cur_scr = NULL;if (NULL == tag_handle){LV_LOG_ERROR("no screen, id %d", id);return false;}if (NULL != mgr_stack_top){/* 栈内有界面 */cur_handle = mgr_stack_top->handle;cur_scr = mgr_stack_top->scr;}else{cur_scr = lv_scr_act();}scr_mgr_stack_free();if ((NULL == cur_handle) || (tag_handle->scr_id == cur_handle->scr_id)){/* 没有界面切换,不使用动画效果 */anim = false;}stack_node = scr_mgr_stack_push(tag_handle);return scr_mgr_switch(cur_scr, stack_node, anim);
}/*** @brief 入栈一个新的界面* @param id * @param anim * @return
*/
bool lv_scr_mgr_push(uint32_t id, bool anim)
{lv_scr_mgr_handle_t* tag_handle = find_handle_by_id(id);lv_scr_mgr_stack_node_t* stack_node = NULL;lv_obj_t* cur_scr = NULL;if (NULL == tag_handle){LV_LOG_ERROR("no screen, id %d", id);return false;}if ((NULL == mgr_stack_top) || (NULL == mgr_stack_root)){LV_LOG_ERROR("no root screen, please use lv_scr_mgr_switch create root screen");return false;}cur_scr = mgr_stack_top->scr;stack_node = scr_mgr_stack_push(tag_handle);return scr_mgr_switch(cur_scr, stack_node, anim);
}/*** @brief 出栈n个界面* @param n 如果栈内界面没有n个,则返回根界面* @param anim * @return
*/
bool lv_scr_mgr_popn(uint32_t n, bool anim)
{lv_obj_t* cur_scr = NULL;if ((mgr_stack_top == NULL) || (mgr_stack_top->prev == NULL)){return false;}cur_scr = mgr_stack_top->scr;scr_mgr_stack_pop(n);return scr_mgr_switch(cur_scr, mgr_stack_top, anim);
}/*** @brief 出栈一个界面* @param anim * @return
*/
bool lv_scr_mgr_pop(bool anim)
{return lv_scr_mgr_popn(1, anim);
}/*** @brief 退回到根界面* @param anim * @return
*/
bool lv_scr_mgr_pop_root(bool anim)
{lv_scr_mgr_stack_node_t* stack_node = NULL;lv_scr_mgr_stack_node_t* stack_top = NULL;uint32_t cnt = 0;if (NULL == mgr_stack_root || NULL == mgr_stack_top){return false;}stack_top = mgr_stack_top;while (stack_top != NULL){cnt++;stack_node = stack_top->prev;stack_top = stack_node;}return lv_scr_mgr_popn(cnt-1, anim);
}/*** @brief 获取当前界面id* @param * @return
*/
int32_t lv_scr_mgr_get_cur_id(void)
{if (NULL != mgr_stack_top && NULL != mgr_stack_top->handle){return mgr_stack_top->handle->scr_id;}else{return -1;}
}/*** @brief 获取根界面id* @param* @return
*/
int32_t lv_scr_mgr_get_root_id(void)
{if (NULL != mgr_stack_root && NULL != mgr_stack_root->handle){return mgr_stack_root->handle->scr_id;}else{return -1;}
}
/************************ (C) COPYRIGHT ***********END OF FILE*****************/
/*********************************CopyRight ************************************* @file lv_scr_mgr.h* @author 不咸不要钱* @date 2023-10-11 9:31:49* @brief &#&* *******************************************************************************/
#ifndef _LV_SCR_MGR_H_
#define _LV_SCR_MGR_H_/* Includes ------------------------------------------------------------------*/
#include "stdint.h"
#include "lvgl.h"#ifdef __cplusplus
extern "C" {
#endif/*!< 界面切换动画默认值*/
#define LV_SCR_MGR_LOAD_ANIM_DEFAULT LV_SCR_LOAD_ANIM_MOVE_LEFT
#define LV_SCR_MGR_LOAD_ANIM_TIME 500
#define LV_SCR_MGR_LOAD_ANIM_DELAY 0/*!< 内存泄漏检测 */
#define LV_SCR_MGR_PRINTF_MEM 1 #define LV_SCR_MGR_REG_ENABLE 0#if LV_SCR_MGR_REG_ENABLE#else#endiftypedef struct
{uint32_t scr_id; /*!< id */lv_scr_load_anim_t anim_type; /*!< 切换动画类型 如果为空,则使用 LV_SCR_MGR_LOAD_ANIM_DEFAULT */void (*scr_first_create)(void); /*!< lv_scr_mgr_switch lv_scr_mgr_push 函数会调用该创建函数 pop则不会调用 可以方便实现pop记住焦点 而push使用默认焦点 */lv_obj_t* (*scr_create) (const uint32_t id, void* param); /*!< 创建界面,创建界面时不要使用 lv_scr_mgr_xxx 函数 */void (*scr_destroy)(void); /*!< 删除界面的回调函数,一般用于删除如 lv_timer 等不会随界面自动删除的资源 */
}lv_scr_mgr_handle_t;typedef struct _lv_scr_mgr_stack_node_t
{lv_scr_mgr_handle_t* handle;lv_obj_t* scr;struct _lv_scr_mgr_stack_node_t* prev;struct _lv_scr_mgr_stack_node_t* next;
}lv_scr_mgr_stack_node_t;bool lv_scr_mgr_init(void* param);
void lv_scr_mgr_deinit(void);
void lv_scr_mgr_param_set(void* param);
void* lv_scr_mgr_param_get(void);bool lv_scr_mgr_switch(uint32_t id, bool anim);
bool lv_scr_mgr_push(uint32_t id, bool anim);
bool lv_scr_mgr_popn(uint32_t n, bool anim);
bool lv_scr_mgr_pop(bool anim);
bool lv_scr_mgr_pop_root(bool anim);
int32_t lv_scr_mgr_get_cur_id(void);
int32_t lv_scr_mgr_get_root_id(void);
#ifdef __cplusplus
}
#endif#endif /* _LV_SCR_MGR_H_ *//************************ (C) COPYRIGHT *****END OF FILE*****************/
相关文章:

lvgl 界面管理器
lv_scr_mgr lvgl 界面管理器 适配 lvgl 8.3 降低界面之间的耦合使用较小的内存,界面切换后会自动释放内存内存泄漏检测 使用方法 在lv_scr_mgr_port.h 中创建一个枚举,用于界面ID为每个界面创建一个页面管理器句柄将界面句柄添加到 lv_scr_mgr_por…...

一篇文章让你了解“JWT“
一.JWT简介 1.概念 JWT (JSON Web Token) 是一种用于在网络上安全传输信息的开放标准(RFC 7519)。它是一种紧凑且自包含的方式,用于在不同组件之间传递信息,通常用于身份验证和授权目的。JWT 是以 JSON 格式编码的令牌ÿ…...

HJ73 计算日期到天数转换
HJ73 计算日期到天数转换 int main() {int year, month, day;cin >> year >> month >> day;int monthDays[13] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };int nday monthDays[month - 1] day;if (month > 2 &&((year…...

Unity实现设计模式——适配器模式
Unity实现设计模式——适配器模式 适配器模式又称为变压器模式、包装模式(Wrapper) 将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 在一个在役的项目期望在原有接口的基础…...

【2023年11月第四版教材】专题1 - 计算题考点汇总 (合集篇)
专题1 - 计算题考点汇总 (合集篇) 1 进度类1.1 PERT三点估算1.1.1 β分布1.1.2 三角分布 1.2 单代号网络图1.2.1 画图1.2.2 找关键路径1.2.3 计算总工期1.2.4 总时差1.2.5 自由时差1.2.6 工期压缩 1.3 双代号网络图1.4 双代号时标网络图1.4.1 画图1.4.2 找关键路径1…...
系统架构设计:17 论信息系统的安全性与保密性设计
目录 一 信息安全基础 1 信息安全的基本要素 2 信息安全的范围 3 网络安全...

使用EasyDarwin+ffmpeg+EasyPlayerPro完成rtsp的推流操作和拉流操作
本文分享在做视频类测试过程中所用到的工具EasyDarwinffmpegEasyPlayerPro 首先说一下EasyDarwin,简单来讲,它就是个推流和拉流及系统消耗的监测软件,具体使用方法我会写在下方。 EasyDarwin 1、解压下载好的EasyDarwin压缩包,并找到EasyD…...

FPGA project : flash_secter_erase
flash的指定扇区擦除实验。 先发写指令,再进入写锁存周期等待500ns,进入写扇区擦除指令,然后写扇区地址,页地址,字节地址。即可完成扇区擦除。 模块框图: 时序图: 代码: module…...

HarmonyOS/OpenHarmony原生应用-ArkTS万能卡片组件Radio
单选框,提供相应的用户交互选择项。该组件从API Version 8开始支持。无子组件。 一、接口 Radio(options: {value: string, group: string}) 从API version 9开始,该接口支持在ArkTS卡片中使用。 参数: 二、属性 除支持通用属性外,还支持以…...

python opencv 深度学习 指纹识别算法实现 计算机竞赛
1 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 python opencv 深度学习 指纹识别算法实现 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:3分工作量:4分创新点:4分 该项目较为新颖…...

一图看懂CodeArts Inspector 三大特性,带你玩转漏洞管理服务
华为云漏洞管理服务CodeArts Inspector是面向软件研发和服务运维提供的一站式漏洞管理能力,通过持续评估系统和应用等资产,内置风险量化管理和在线风险分析处置能力,帮助组织快速感应和响应漏洞,并及时有效地完成漏洞修复工作&…...
【Mysql】Mysql的启动选项和系统变量(二)
概述 在Mysql的设置项中一般都有各自的默认值,比方说mysql 5.7服务器端允许同时连入的客户端的默认数量是 151 ,表的默认存储引擎是 InnoDB ,我们可以在程序启动的时候去修改这些默认值,对于这种在程序启动时指定的设置项也称之为…...

FPGA project : flash_read
实验目标: flash的普通读指令,在指定地址开始读。可以更改地址与读的数据个数。 先发送读指令扇区地址页地址字节地址。 然后读数据。再把读到的串行数据转化为8bit的数据,存入fifo。 然后读出FIFO中数据,通过uart_tx模块发送…...

nnunetv2训练报错 ValueError: mmap length is greater than file size
目录 报错解决办法 报错 笔者在使用 nnunetv2 进行 KiTS19肾脏肿瘤分割实验的训练步骤中 使用 2d 和3d_lowres 训练都没有问题 nnUNetv2_train 40 2d 0nnUNetv2_train 40 3d_lowres 0但是使用 3d_cascade_fullres 和 3d_fullres 训练 nnUNetv2_train 40 3d_cascade_fullres …...
React知识点系列(2)-每天10个小知识
目录 1. 如何优化 React 应用的性能?你用过哪些性能分析工具?2. 在 React 中,什么是 Context API?你在什么场景下会使用它?3. 你能解释一下什么是 React Fiber 吗?4. 在项目中,你是否使用过 Rea…...

AutoGPT:让 AI 帮你完成任务事情 | 开源日报 No.54
Significant-Gravitas/AutoGPT Stars: 150.4k License: MIT AutoGPT 是开源 AI 代理生态系统的核心工具包。它采用模块化和可扩展的框架,使您能够专注于以下方面: 构建 - 为惊人之作打下基础。测试 - 将您的代理调整到完美状态。查看 - 观察进展成果呈…...

USB 转串口芯片 CH340
目录 1、概述 2、特点 3、封装 4、引脚 6、参数 6.1 绝对最大值(临界或者超过绝对最大值将可能导致芯片工作不正常甚至损坏) 6.2 电气参数(测试条件:TA25℃,VCC5V,不包括连接 USB 总线的引脚&…...

Day 05 python学习笔记
循环 应用:循环轮播图 最基础、最核心 循环:周而复始,谓之循环 (为了代码尽量不要重复) while循环 while的格式 索引定义 while 表达式(只要结果为布尔值即可): 循环体 通过条件的不断变化,从…...

Python如何17行代码画一个爱心
🌈write in front🌈 🧸大家好,我是Aileen🧸.希望你看完之后,能对你有所帮助,不足请指正!共同学习交流. 🆔本文由Aileen_0v0🧸 原创 CSDN首发🐒 如…...

生产环境中常用Linux命令
太简单的我就不讲解啦,浪费时间,直接将生产中常用的 文章目录 1.总纲2.整机 top3.CPU vmstat3. 内存 free4. 硬盘: df5. 磁盘IO iostat6. 网络IO ifstat7: 内存过高的情景排查 1.总纲 整机:topcpu:vmstat内存:free硬盘:df磁盘io: iostat网络io:ifstat 2.整机 top 首先们要查…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...

如何应对敏捷转型中的团队阻力
应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中,明确沟通敏捷转型目的尤为关键,团队成员只有清晰理解转型背后的原因和利益,才能降低对变化的…...

elementUI点击浏览table所选行数据查看文档
项目场景: table按照要求特定的数据变成按钮可以点击 解决方案: <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

沙箱虚拟化技术虚拟机容器之间的关系详解
问题 沙箱、虚拟化、容器三者分开一一介绍的话我知道他们各自都是什么东西,但是如果把三者放在一起,它们之间到底什么关系?又有什么联系呢?我不是很明白!!! 就比如说: 沙箱&#…...