【微前端-Single-SPA、qiankun的基本原理和使用】
背景
在实际项目中,随着日常跌倒导致的必然墒增,项目会越来越冗余不好维护,而且有时候一个项目会使用的其他团队的功能,这种跨团队不好维护和管理等等问题,所以基于解决这些问题,出现了微前端的解决方案。微前端具有拆分和集成的特点,本文主要讲解主流的single-spa以及qiankun的基本原理和使用。
微前端
定义:微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略,就是将多个应用集合到一起,通过动态匹配的方式展示。
特点: 与技术无关、渐进式增量升级和迁移、模块隔离、运行时子应用状态不共享
解决问题:解决随着项目迭代会导致必然墒增,导致项目越来越庞大冗余,不好维护的问题,微前端将一个大应用拆分为可以独立运行的子应用,然后集合子应用。

Single-SPA
组合式路由分发:由主容器监听路由变化,动态进行子应用的挂载/卸载
特点
网页打开主应用会执行registerApplication注册子应用,然后会监控路由变化,当路由匹配activeWhen时,会先坚持子应用状态先通过在子应用注入的生命周期卸载上一个子应用,然后挂载匹配路由的子应用。函数流程大致是:registerApplication -> start -> bootstrap -> mount-> unmount

1、主应用注册子应用
singleSpa.registerApplication({'app1',() => import('src/app1/main.js'),(location) => location.pathname.startsWith('/app1'),customProps: { authToken: "d83jD63UdZ6RS6f70D0" }
});
singleSpa.registerApplication({'app2',() => import('src/app2/main.js'),(location) => location.pathname.startsWith('/app2'),customProps: (name, location) => {return { authToken: "d83jD63UdZ6RS6f70D0" };}
});
2、子应用注入主应用控制的生命周期
bootstrap: 初始化
应用挂载之前的初始化,这个生命周期函数会在应用第一次挂载前执行一次。
mount:挂载
每当应用的activity function返回真值(registerApplication的第三个参数),但该应用处于未挂载状态时,挂载的生命周期函数就会被调用。调用时,函数会根据URL来确定当前被激活的路由,然后加载对应的js文件,创建DOM元素、监听DOM事件等以向用户呈现渲染的内容。任何子路由的改变(如hashchange或popstate等)不会再次触发mount,需要各应用自行处理。
unmount:卸载
每当应用的activity function返回假值,但该应用已挂载时,卸载的生命周期函数就会被调用。卸载函数被调用时,会清理在挂载应用时被创建的DOM元素、事件监听、内存、全局变量和消息订阅等。
let domEl;
export function bootstrap(props) {return Promise.resolve().then(() => {domEl = document.createElement('div');domEl.id = 'app1';document.body.appendChild(domEl);});
}
export function mount(props) {return Promise.resolve().then(() => {// 在这里通常使用框架将ui组件挂载到dom。请参阅https://single-spa.js.org/docs/ecosystem.html。domEl.textContent = 'App 1 is mounted!'});
}
export function unmount(props) {return Promise.resolve().then(() => {// 在这里通常是通知框架把ui组件从dom中卸载。参见https://single-spa.js.org/docs/ecosystem.htmldomEl.textContent = '';})
}
unload:移除
可选的生命周期,只有当主应用调用unloadApplication时,才会触发子应用中的unload函数,一旦应用被移除,它的状态将会变成NOT_LOADED,下次激活时会被重新初始化bootstrap阶段。
timeout:超时设置
主应用可以针对每个阶段进行超时设置,超过设置时间还没完成,则会抛出异常。
全局配置:
import { setBootstrapMaxTime, setMountMaxTime, setUnmountMaxTime, setUnloadMaxTime } from 'single-spa';// 设置 bootstrap 阶段的最大超时时间为 5 秒
setBootstrapMaxTime(5000, '应用 bootstrap 超时');// 设置 mount 阶段的最大超时时间为 10 秒
setMountMaxTime(10000, '应用 mount 超时');// 设置 unmount 阶段的最大超时时间为 5 秒
setUnmountMaxTime(5000, '应用 unmount 超时');// 设置 unload 阶段的最大超时时间为 5 秒
setUnloadMaxTime(5000, '应用 unload 超时');
给特定的子应用配置:
import { registerApplication } from 'single-spa';registerApplication({name: 'my-app',app: () => System.import('my-app'),activeWhen: ['/my-app'],customProps: {bootstrapTimeout: 5000, // bootstrap 阶段的超时时间mountTimeout: 10000, // mount 阶段的超时时间unmountTimeout: 5000, // unmount 阶段的超时时间unloadTimeout: 5000, // unload 阶段的超时时间}
});
超时处理的默认行为:
当 single-spa 检测到应用在指定阶段的操作超过了配置的超时时间时,默认的行为是:
- Bootstrap 阶段超时: 应用将无法进入 MOUNTED 状态,single-spa 会抛出错误。
- Mount 阶段超时: 应用将无法完全加载和显示,single-spa 会抛出错误。
- Unmount 阶段超时: 应用无法正确卸载,可能导致残留的状态或资源占用。
- Unload 阶段超时: 应用无法被卸载,可能导致内存泄漏或其他问题。
3、主应用启动/卸载子应用
卸载有两种:
- 子应用切换:当子应用A切换B时,会先卸载当前子应用A,然后挂载匹配的子应用B,这时候A的资源还是在内存中,以便切回A时能快速响应,如果切回A,则不会进入bootstrap初始化周期。
- 卸载子应用:当主应用彻底卸妆子应用A时,会调用A中的unload生命周期,并且会将子应用状态设置为NOT_LOADED,然后将A从内存中移除,再次启动A时,会重新进入bootstrap初始化周期。
// 启动 single-spa
singleSpa.start();// 卸载子应用
unloadApplication('my-app').then(() => {console.log('子应用已经卸载');// 模拟用户导航到子应用的路径,触发重新加载navigateToUrl('/container');
});
4、生命周期执行流程
- 加载阶段:
- 子应用第一次被加载时,single-spa 会依次执行子应用的 bootstrap 和 mount 钩子。
- 挂载阶段:
- 子应用在页面上呈现时,single-spa 会执行子应用的 mount 钩子。
- 卸载阶段:
- 当子应用不再需要显示时,single-spa 会调用 unmount 钩子,将子应用从页面上移除。
- 这时,子应用的状态和资源可能仍然保留在内存中,以便快速重新加载。
- 完全卸载阶段:
- 调用 unloadApplication 时,single-spa 会执行子应用的 unload 钩子,并将子应用的资源从内存中释放。
- 下一次匹配到这个子应用时,会重新执行 bootstrap 钩子。
5、存在的问题
- 手动加载资源,会根据子应用打包方式改变
single-spa采用的是Js Entry的方式来加载子应用。需要手动加载js、css等资源,如果子应用打包方式改变(比如生成的资源路径变化),主应用中也要变化。
import { registerApplication } from 'single-spa';
registerApplication({name: 'app',() => import('src/app1/main.js'), // 手动加载Js资源(location) => location.pathname.startsWith('/app1'),
})singleSpa.start()
- 代码冲突
single-spa主要通过Js Entry将多个子应用引入到主应用,多个Js运行在主应用的全局上下文中,可能存在命名冲突、样式冲突、Js冲突等问题。需要手动做JS、CSS的隔离。
- 子应用通信
Single-SPA只是将多个子应用挂载在主应用,而子应用之间互相通信,没有内置功能,需要自己手动实现。
qiankun
qiankun 是一个基于 single-spa 的微前端实现库,解决了single-spa的一些问题,就是一个更完善的single-spa库。主要是解决了single-spa的一些问题,并做了丰富。使用思路和single-spa大致一样,在主应用中注册子应用,然后在子应用中写入主应用的生命周期等待主应用的调用。
- 需要手动写加载资源路径,如果子应用打包方式改变,主应用资源加载路径就要调整
- 使用Js Entry导致多应用间Js、Css代码冲突问题
- 子应用间通信
特点
- 沙箱隔离: 嵌入主容器的不同子应用的Js、Css是相互隔离的,1、通过scope的方式,在dom节点和样式都加上前缀,避免冲突。2、Proxy代理对象的方式,子应用访问的是这个代理对象,CSS 的隔离就是使用 shadow dom ,这是浏览器支持的特性,shadow root 下的 dom 的样式是不会影响其他 dom 的。
解决问题一
通过Html Entry的方式,在主应用配置子应用的html即可,qiankun会自动加载并解析html文件,并将script脚本部分解析拆离出来单独加载,其他html部分添加到主应用到dom中。以此来解决需要用户手动写资源url路径的问题(这个功能的实现放在 import-html-entry 这个包里)
registerMicroApps([{name: 'react app', // app name registeredentry: '//localhost:7100', // 会自动加载html文件container: '#yourContainer',activeRule: '/yourActiveRule',},{name: 'vue app',entry: { scripts: ['//localhost:7100/main.js'] },container: '#yourContainer2',activeRule: '/yourActiveRule2',},
]);
子应用的html文件:

挂载在主应用的dom上:

解决问题二: JS、CSS隔离
JS隔离
主要就是全局window的隔离
qiankun 实现 window 隔离有三种沙箱:
- 快照,加载子应用前记录下 window 的属性,卸载之后恢复到之前的快照
- diff,加载子应用之后记录对 window 属性的增删改,卸载之后恢复回去
- Proxy,创建一个代理对象,每个子应用访问到的都是这个代理对象

从源码中能看出来,当浏览器支持Proxy的时候,会根据useLooseSandbox来判断使用LegacySandbox还是ProxySandbox。这两个都是基于Proxy代理的,但是LegacySandbox的隔离度不高,对子应用进行基本隔离,主要是兼容较老的一些浏览器。ProxySandbox是拦截子应用的所有全局变量和函数的访问和修改,是强隔离的。当浏览器不支持Proxy时,会降级使用快照来隔离。
由于前两种都是先记录window的更改,当卸载之后再恢复之前的状态,所以不能存在多个子应用,否则会发生冲突,一般还是使用Proxy代理沙箱。
Proxy代理沙箱
Proxy代理对象,子应用访问的是这个代理对象,Proxy对代理对象进行了get、set拦截操作,会隔离不同子应用,使其只会访问自身上下文,不会影响全局对象。
createFakeWindow
通过createFakeWindow创建ProxyWindow代理:通过createFakeWindow传入全局window,会对其进行代理,返回一个代理代理window(fakeWindow)和一个保存拥有自定义getter属性的对象(propertiesWithGetter)
为什么需要propertiesWithGetter?
ProxySandbox 通过 Proxy 来代理对全局对象(如 window)的访问。但是,有些属性具有自定义的 getter 方法,这些 getter 方法在属性被访问时会执行特定的逻辑。这些属性不能简单地通过 Proxy 来直接代理,因为访问时需要正确触发这些 getter 方法。
function createFakeWindow(globalContext, speedy) {var propertiesWithGetter = new Map();var fakeWindow = {};Object.getOwnPropertyNames(globalContext).filter(function (p) {var descriptor = Object.getOwnPropertyDescriptor(globalContext, p);return !(descriptor === null || descriptor === void 0 ? void 0 : descriptor.configurable);}).forEach(function (p) {var descriptor = Object.getOwnPropertyDescriptor(globalContext, p);if (descriptor) {var hasGetter = Object.prototype.hasOwnProperty.call(descriptor, 'get');if (p === 'top' || p === 'parent' || p === 'self' || p === 'window' ||p === 'document' && speedy || inTest ) {descriptor.configurable = true;if (!hasGetter) {descriptor.writable = true;}}if (hasGetter) propertiesWithGetter.set(p, true);}});return {fakeWindow: fakeWindow,propertiesWithGetter: propertiesWithGetter};}
通过Proxy代理window
基于代理fakeWindow对象,对子应用的操作进行拦截

以get为例:
get: function get(target, p) {_this.registerRunningApp(name, proxy);if (p === Symbol.unscopables) return unscopables;if (p === 'window' || p === 'self') {return proxy;}if (p === 'globalThis' || inTest ) {return proxy;}if (p === 'top' || p === 'parent' || inTest ) {if (globalContext === globalContext.parent) {return proxy;}return globalContext[p];}if (p === 'hasOwnProperty') {return hasOwnProperty;}if (p === 'document') {return _this.document;}if (p === 'eval') {return eval;}if (p === 'string' && globalVariableWhiteList.indexOf(p) !== -1) {return globalContext[p];}var actualTarget = propertiesWithGetter.has(p) ? globalContext : p in target ? target : globalContext;var value = actualTarget[p];if (isPropertyFrozen(actualTarget, p)) {return value;}if (!isNativeGlobalProp(p) && !useNativeWindowForBindingsProps.has(p)) {return value;}var boundTarget = useNativeWindowForBindingsProps.get(p) ? nativeGlobal : globalContext;return rebindTarget2Fn(boundTarget, value);},
其中globalVariableWhiteList用来保存一个允许全局访问的变量或属性的白名单。这个白名单中的变量或属性在沙箱的代理过程中不会被隔离或修改,允许子应用直接访问和操作这些全局变量,qiankun不会对白名单中的属性进行修改。
const globalVariableWhiteList = ['window','self','globalThis','document','navigator','location','localStorage','sessionStorage','fetch','XMLHttpRequest','console','setTimeout','setInterval','clearTimeout','clearInterval','requestAnimationFrame','cancelAnimationFrame'
];
Css隔离
1、Shadow DOM(影子DOM)
允许创建隔离的 DOM 子树,通过给真实DOM绑定一个Shadow DOM,然后将我们的样式通过添加到Shadow DOM树的方式添加,Shadow DOM树中的DOM天生不会影响真实DOM。shadow dom 是 web components 技术的一部分,其实就一个 attachShadow 的 api(原生)。web components 添加内容的时候,不直接 appendChild,而是通过attachshadow创建一个影子DOM,然后再在下面 appendChild。shadow dom 就是原生自带的CSS沙箱。内部样式影响不了外界,外界样式也影响不了 shadow dom 内的元素。但是使用组件的Modal弹窗这些默认挂载在body中的,样式就会有问题。

2、Scope CSS隔离
Scope CSS(作用域 CSS),通过给子应用的根节点添加一个前缀来进行区分,结合样式重写来让样式只会影响本应用的DOM(添加子应用唯一ID - 前缀),一般用文件路径和项目package.name来组成hash为前缀。
以Vue的scope为例:

Vue: vue的scoped样式其实也有问题,它是通过.vue文件在项目中的相对路径path+文件名进行计算hash值的,当主子应用中同时存在一个path和文件名相同时的.vue文件,它的data-v-xxxxx算出来就是一样的,此时样式还是会冲突。可以加上package.name来区分
React使用的是CSS Module来实现CSS隔离,在编译时会进行hash换算,所以在代码中要使用style[class_name]的方式来添加样式类
解决问题三: 应用间通信
single-spa需要自己实现应用间的状态共享,而qiankun提供的api:initGlobalState在主容器中初始化需要共享的状态,然后在挂载在子应用中的mount生命周期通过props.onGlobalStateChange来获取共享数据
initGlobalState 是 qiankun 提供的一个 API,用于创建一个全局的共享状态对象,并允许不同的微前端应用进行读写和监听这个共享状态的变化。这个共享状态管理系统与沙箱机制是分开的,不依赖window,而是由qiankun自身维护的一个状态管理系统。qiankun会监控这个状态,当状态发生变化时会通知所有订阅了这个状态变化的应用。
import { initGlobalState, MicroAppStateActions } from 'qiankun';// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
// 设置状态
actions.setGlobalState(state);
// 监控状态变化
actions.onGlobalStateChange((state, prev) => {// state: 变更后的状态; prev 变更前的状态console.log(state, prev);
});
// 取消监听
actions.offGlobalStateChange();
// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {props.onGlobalStateChange((state, prev) => {// state: 变更后的状态; prev 变更前的状态console.log(state, prev);});props.setGlobalState(state);
}
相关文章:
【微前端-Single-SPA、qiankun的基本原理和使用】
背景 在实际项目中,随着日常跌倒导致的必然墒增,项目会越来越冗余不好维护,而且有时候一个项目会使用的其他团队的功能,这种跨团队不好维护和管理等等问题,所以基于解决这些问题,出现了微前端的解决方案。…...
多元化功能空间,打造影像产业生态圈
国际数字影像产业园的多元化功能空间定位涵盖了从产业实训、研发创新、资产交易、集群发展到孵化服务、大数据支持、产学研合作以及人力资源服务等多个方面,旨在为数字影像产业提供全方位的支持和服务,推动产业的升级和发展。 1、产业实训空间࿱…...
华为鸿蒙正式杀入工业自动化,反攻开始了!
导语 大家好,我是社长,老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 在近日举行的2024华为开发者大会上,华龙讯达与华为共同发布了基于鸿蒙内核技术的“HualongOS 华龙工业操作系统”,这一里…...
学历优先还是专业优先?高考志愿填报的抉择
学历优先还是专业优先?高考志愿填报的抉择 2024年高考帷幕落下,新一轮的思考与选择悄然来临。对于每一位高考考生,选择学校和专业是开启大学新生活的两个前置必选项。然而,有时候“鱼与熊掌不可兼得”,在分数受限的情…...
SpringAOP常用功能实现
1. 导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency> 2. 核心通知 package com.example.aspect;import lombok.SneakyThrows; import org.aspectj.l…...
Java基础的重点知识-04-封装
文章目录 面向对象思想封装 面向对象思想 在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽象出来,描述成计算机事件的设计思想。 面向对象思想的三大基本特征: 封装、继承、多态 1.类和对象 类是对象的抽象ÿ…...
win7 的 vmware tools 安装失败
没有安装vmware tools的系统屏幕显示异常。桌面是比较小的图像,四周是黑边在 vmware 软件里 方法1,下补丁 https://www.catalog.update.microsoft.com/Search.aspx?qkb4474419 方法2,使用老版vm tools http://softwareupdate.vmware.com/c…...
【杂记-浅谈OSPF协议之IR、ABR、ASBR、BR】
OSPF协议之IR、ABR、ASBR、BR 一、 内部路由器 IR二、骨干路由器 BR三、边界路由器 ABR四、自治系统边界路由器 ASBR 一、 内部路由器 IR 概念 IR,Internal Router,即内部路由器,指的是所有接口都属于同一个OSPF区域的路由器。这类路由器只维…...
element 问题整合
没关系,凡事发生必有利于我 文章目录 一、el-table 同级数据对齐及展开图标的位置问题二、el-table 勾选框为圆角及只能勾选一个 一、el-table 同级数据对齐及展开图标的位置问题 element 官方提供的扩展tree型数据在表格里默认是靠左边对齐,项目需求需要…...
Linux下vi文件的时候替换指定的内容
需要将nginx.conf中的192.168.222.188,替换为178.21.120.225 操作步骤: 1、vi 文件 vi nginx.conf2、输入 :%s/192.168.222.188/178.21.120.225/g3、最后保存 :wq...
【知识学习】阐述Unity3D中MaterialTexture的概念及使用方法示例
在Unity3D中,Material和Texture是渲染过程中非常重要的两个概念,它们共同工作以实现丰富的视觉效果。 Material Material是Unity中的一个组件,用于定义物体表面的视觉属性。一个Material可以包含多种属性,如颜色、纹理、反射率等…...
java创建0byte的空文件
在Java中,使用File类创建一个文件通常不会自动创建一个0字节的文件,它只是用来表示文件系统中的一个文件或目录。如果你想要创建一个0字节的文件,你需要使用FileOutputStream来写入文件。以下是创建一个0字节文件的示例代码: imp…...
Qt 实战(6)事件 | 6.2、事件过滤器
文章目录 一、事件过滤器1、什么是事件过滤器?2、如何实现事件过滤器?3、应用示例4、总结 前言: 在Qt的事件处理机制中,事件过滤器(Event Filter)是一种非常强大且灵活的工具,它允许开发者在一个…...
【PyTorch】【机器学习】图片张量、通道分解合成和裁剪
一、导入所需库 from PIL import Image import torch import numpy as np import matplotlib.pyplot as plt二、读取图片 pic np.array(Image.open(venice-boat.jpg))上述代码解释:先用Image.open()方法读取jpg格式图片,再用np.array()方法将图片转成…...
如何提高工业交换机的电源功耗?
工业交换机的电源功耗是指在工作状态下所消耗的能量。随着工业自动化技术的发展,工业交换机在生产和制造领域中扮演着至关重要的角色。它们通过连接各种设备和系统,实现信息的传输和处理,提高生产效率和质量。然而,工业交换机的大…...
源站静态文件更新后,CDN会自动刷新吗
源站静态文件更新后,CDN不会自动刷新缓存,而是在缓存时间过期后,才会经由用户触发回源获取最新文件。如希望在缓存过期时间之前,实现CDN节点与源站静态文件同步更新,则需要通过CDN控制台-【刷新预取】菜单,…...
Token的应用场景
JWT JWT由三部分组成:头部(Header)、有效载荷(Payload)和签名(Signature)头部通常指定了Token的类型和使用的哈希算法;有效载荷包含了一系列的声明,例如用户的ID、Token…...
机器学习课程复习——奇异值分解
1. 三种奇异值分解 奇异值分解(Singular Value Decomposition, SVD)包含了: 完全奇异值分解(Complete Singular Value Decomposition, CSVD)紧奇异值分解(Tight Singular Value Decomposition, TSVD&…...
Java--乐观锁
乐观锁是一种并发控制机制,用于处理多个事务或线程对同一数据进行并发修改的问题。它假设多个事务或线程在操作数据时不会互相干扰,因此不在数据上加锁,而是在提交数据时检查数据是否被其他事务修改过。如果数据在提交前已经被其他事务修改&a…...
静默升级oracle 11g (从11.2.0.1升级到11.2.0.4)
原文:https://www.cnblogs.com/daizhengyang/p/13353783.html 一个环境是oracle 11.2.0.1,一个环境是oracle 11.2.0.4,同样的数据,同样的sql,在两个版本数据库表现不一样。于是,干脆都统一为11.2.0.4。 但…...
观成科技:隐蔽隧道工具Ligolo-ng加密流量分析
1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具,该工具基于TUN接口实现其功能,利用反向TCP/TLS连接建立一条隐蔽的通信信道,支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式,适应复杂网…...
C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
linux 错误码总结
1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...
P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
