深度剖析:前端如何驾驭海量数据,实现流畅渲染的多种途径
文章目录
- 一、分批渲染
- 1、setTimeout定时器分批渲染
- 2、使用requestAnimationFrame()改进渲染
- 2.1、什么是requestAnimationFrame
- 2.2、为什么使用requestAnimationFrame而不是setTimeout或setInterval
- 2.3、requestAnimationFrame的优势和适用场景
- 二、滚动触底加载数据
- 三、Element-Plus虚拟化表格
- 四、自定义虚拟滚动列表
- 1、视图结构
- 2、基本思路
- 3、具体计算
- 4、实现代码
- 五、使用el-table-infinite-scroll插件
- 1、el-table-infinite-scroll(vue3)
- 2、el-table-infinite-scroll(vue2)
- 六、使用Web Workers处理数据
- 七、借助服务端渲染(SSR)
处理大量数据的渲染对于前端开发来说是一项挑战,但也是提升网页性能和用户体验的重要环节。要有效解决这一问题,可以采用虚拟滚动(Virtual Scrolling)、分批渲染(Incremental Rendering)、使用Web Workers处理数据、利用前端分页(Pagination)、借助服务端渲染(SSR)来优化大量数据的处理。其中,虚拟滚动是一种非常有效的技术,它通过只渲染用户可见的列表项来极大减少DOM操作和提高性能。这种方式不仅提升了滚动的流畅度,也减轻了浏览器的负担,尤其适用于长列表数据的展示。
一、分批渲染
分批渲染或称增量渲染,是指将数据分成若干批次进行处理和渲染,每次只处理一小部分数据,通过逐步完成整体渲染的方式,避免了一次性处理大量数据造成的卡顿现象。
实现分批渲染通常可以通过requestAnimationFrame()或setTimeout()等异步API分配任务,确保在每个渲染帧中只处理足够少的数据,避免阻塞主线程。
1、setTimeout定时器分批渲染
//发送请求
onMounted(() => {getData()
})//获取数据
const getData = () => {fetch('http://124.223.69.156:3300/bigData').then(res => res.json()).then(data => {let newData = chunksData(data.data)console.log(newData);}).catch(err => console.log(err));
}//数据分页
const chunksData = (arr) => {let chunkSize = 10;let chunks = [];for (let i = 0; i < arr.length; i += chunkSize) {chunks.push(arr.slice(i, i + chunkSize));}return chunks
}//setTimeout分页渲染
for (let i = 0; i < newData.length; i++) {setTimeout(() => {tableData.push(...newData[i])}, 100*i)
}
2、使用requestAnimationFrame()改进渲染
2.1、什么是requestAnimationFrame
requestAnimationFrame是浏览器用于定时循环操作的一个API,通常用于动画和游戏开发。它会把每一帧中的所有DOM操作集中起来,在重绘之前一次性更新,并且关联到浏览器的重绘操作。
2.2、为什么使用requestAnimationFrame而不是setTimeout或setInterval
与setTimeout或setInterval相比,requestAnimationFrame具有以下优势:
- 通过系统时间间隔来调用回调函数,无需担心系统负载和阻塞问题,系统会自动调整回调频率。
- 由浏览器内部进行调度和优化,性能更高,消耗的CPU和GPU资源更少。
- 避免帧丢失现象,确保回调连续执行,实现更流畅的动画效果。
- 自动合并多个回调,避免不必要的开销。
- 与浏览器的刷新同步,不会在浏览器页面不可见时执行回调。
2.3、requestAnimationFrame的优势和适用场景
requestAnimationFrame最适用于需要连续高频执行的动画,如游戏开发,数据可视化动画等。它与浏览器刷新周期保持一致,不会因为间隔时间不均匀而导致动画卡顿。
const renderData = (page) => {if(page >= newData.length) returnrequestAnimationFrame(() => {tableData.push(...newData[page])page++renderData(page)})
}renderData(0)
二、滚动触底加载数据
前端分页是处理大量数据渲染的另一种常见策略,它通过每次只向用户展示一部分数据,让用户通过分页控件浏览完整的数据集。
实现前端分页首先需要从后端一次性获取完整数据,然后根据设定的每页数据量在前端进行切分,每次仅加载和渲染当前页的数据。这种方式减轻了单次渲染的负担,但增加了数据管理的复杂性。
//发送请求
onMounted(() => {getData()
})//获取数据
const getData = () => {fetch('http://124.223.69.156:3300/bigData').then(res => res.json()).then(data => {let newData = chunksData(data.data)//保存所有数据totalData.push(newData)//渲染第一页面数据renderData()}).catch(err => console.log(err));
}//数据分页
const chunksData = (arr) => {let chunkSize = 10;let chunks = [];for (let i = 0; i < arr.length; i += chunkSize) {chunks.push(arr.slice(i, i + chunkSize));}return chunks
}//渲染数据
const renderData = () => {if(totalData.length == 0) return//添加第一页数据tableData.push(...totalData[0])//删除第一页数据totalData.shift()
}
监听滚动事件,触底触底时触发renderData事件,继续加载下一页数据。
三、Element-Plus虚拟化表格
Element Plus 提供 的Virtualized Table 虚拟化表格
在前端开发领域,表格一直都是一个高频出现的组件,尤其是在中后台和数据分析场景。 但是,对于 Table V1来说,当一屏里超过 1000 条数据记录时,就会出现卡顿等性能问题,体验不是很好。
通过虚拟化表格组件,超大数据渲染将不再是一个头疼的问题。
即使虚拟化的表格是高效的,但是当数据负载过大时,网络和内存容量也会成为您应用程序的瓶颈。
因此请牢记,虚拟化表格永远不是最完美的解决方案,请考虑数据分页、过滤器等优化方案。
<template><div style="width: 100%;height: 100%"><el-auto-resizer><template #default="{ height, width }"><el-table-v2 :columns="columns" :data="tableData" :width="width" :height="height" fixed /></template></el-auto-resizer></div>
</template><script setup>
import { onMounted, reactive } from 'vue';
const tableData = reactive([])
const columns = [{key: 'id',dataKey: 'id',title: 'ID',width: 140
},
{key: 'name',dataKey: 'name',title: 'Name',width: 140,
},
{key: 'value',dataKey: 'value',title: 'Value',width: 140,
}]//获取数据
onMounted(() => {getData()
})const getData = () => {fetch('http://124.223.69.156:3300/bigData').then(res => res.json()).then(data => {tableData.push(...data.data)}).catch(err => console.log(err));
}
</script>
四、自定义虚拟滚动列表
虚拟滚动是通过仅渲染用户当前可视区域内的元素,当用户滚动时动态加载和卸载数据,从而实现长列表的高效渲染。这种方法能显著减少页面初始化时的渲染负担,加快首次渲染速度。
虚拟滚动实现的核心在于计算哪些数据应当被渲染在屏幕上。这涉及到监听滚动事件,根据滚动位置计算当前可视范围内的数据索引,然后仅渲染这部分数据。还需要处理好滚动条的位置和大小,确保用户体验的一致性。
1、视图结构
- viewport:可视区域的容器
- list-area:列表项的渲染区域
<div class="viewport" ref="viewport"><div class="list-area"><!-- item-1 --> <!-- item-2 --> <!-- item-n --> </div>
</div>
2、基本思路
虚拟列表的核心思路是 处理用户滚动时可视区域数据的显示 和 可视区外数据的隐藏,这里为了方便说明,引入以下相关变量:
- totalList :总列表数据
- startIndex :可视区域的开始索引
- endIndex :可视区域的结束索引
- paddingTop :可视区域的上内边距
- paddingBottom :可视区域的下内边距
当用户滚动列表时:
- 计算可视区域的 开始索引 和 结束索引
- 根据 开始索引 和 结束索引 渲染数据
- 计算 可视区域的上内边距 和下内边距 显示滚动条位置
3、具体计算
先假定可视区的高度固定为600px,每个列表项的高度固定为60px,则我们可设置和推导出:
- 可视区高度: viewportHeight = 600
- 列表项高度: itemSize = 60
- 可视区开始索引: startIndex = 0
- 可视区结束索引 :endIndex = startIndex + viewportHeight / itemSize
当用户滚动时,逻辑处理如下:
- 获取可视区滚动距离 scrollTop;
- 根据 滚动距离 scrollTop 和 单个列表项高度 itemSize 计算出 开始索引 startIndex = Math.floor(scrollTop / itemSize);
- 可视区域的上内边距 paddingTop = scrollTop;
- 可视区域的上内边距 paddingBottom = totalList * itemSize - viewportHeight - scrollTop;
- 只显示 开始索引 和 结束索引 之间的列表项;
4、实现代码
<template><div class="viewport" ref="viewport"><div class="list-area" :style="styleObject"><div v-for="(item, index) in scrollList" :key="index" class="item">index:{{ index }} id:{{ item.id }} name:{{item.name }}</div></div></div>
</template>
<script setup>
import { onMounted, reactive, ref, computed, onBeforeUnmount } from 'vue';
//总列表的数据
const totalList = reactive([])
//可视区域的开始索引
let startIndex = ref(0)
// 假设每个列表项的高度是60px
let itemSize = ref(60)
//可视区域的上内边距
let paddingTop = ref(0)
//可视区域的下内边距
let paddingBottom = ref(0)
//容器的高度
let viewportHeight = ref(600)
//容器
const viewport = ref(null)// 计算可视区域的列表数据
const scrollList = computed(() => {return totalList.slice(startIndex.value, endIndex.value);
})// 计算可视区域的高度和内边距
const styleObject = computed(() => {return {paddingTop: `${paddingTop.value}px`,paddingBottom: `${paddingBottom.value}px`,height: `${viewportHeight.value}px`}
})// 计算可视区域的结束索引
const endIndex = computed(() => {return Math.min(totalList.length, startIndex.value + Math.ceil(viewportHeight.value / itemSize.value));
})//发送请求获取数据
onMounted(() => {getData()
})//获取数据
const getData = () => {fetch('http://124.223.69.156:3300/bigData').then(res => res.json()).then(data => {let newArr = data.datatotalList.push(...newArr)initScrollListener()}).catch(err => console.log(err));
}// 监听可视区域滚动事件
const initScrollListener = () => {scrollListener()viewport.value.addEventListener('scroll', scrollListener);
}// 计算可视区域的内边距
const scrollListener = () => {// 计算可视区域滚动距离const scrollTop = viewport.value.scrollTop;// 计算可视区域的开始索引startIndex.value = Math.max(0, Math.floor(scrollTop / itemSize.value));// 计算可视区域的上内边距paddingTop.value = scrollTop;// 如果是最后一页,则不需要额外的底部填充 if (endIndex.value >= totalList.length) {paddingBottom.value = 0;} else {// 计算可视区域的下内边距paddingBottom.value = totalList.length * itemSize.value - viewportHeight.value - scrollTop;}
}// 移除可视区域滚动事件
onBeforeUnmount(() => {removeScrollListener()
})// 移除可视区域滚动事件
const removeScrollListener = () => {viewport.value.removeEventListener('scroll', scrollListener);
}<style scoped>
.viewport {width: 600px;height: 600px;overflow: auto;border: 1px solid #D3DCE6;margin: auto;
}.item {height: 59px;line-height: 60px;border-bottom: 1px solid #D3DCE6;padding-left: 20px;
}
</style>
这里可以看出,永远只渲染10条数据。随着滚动条滚动,动态渲染10条数据。


五、使用el-table-infinite-scroll插件
1、el-table-infinite-scroll(vue3)
- 安装
npm install --save el-table-infinite-scroll
- 全局引入
import ElTableInfiniteScroll from "el-table-infinite-scroll";
app.use(ElTableInfiniteScroll);
- 局部引入
<template><el-table v-el-table-infinite-scroll="load"></el-table>
</template><script setup>
import { default as vElTableInfiniteScroll } from "el-table-infinite-scroll";
</script>
- 组件中使用
<template><p style="margin-bottom: 8px"><span>loaded page(total: {{ total }}): {{ page }}, </span>disabled:<el-switch v-model="disabled" :disabled="page >= total"></el-switch></p><el-tablev-el-table-infinite-scroll="load":data="data":infinite-scroll-disabled="disabled"height="200px"><el-table-column type="index" /><el-table-column prop="date" label="date" /><el-table-column prop="name" label="name" /><el-table-column prop="age" label="age" /></el-table>
</template><script setup>
import { ref } from 'vue';const dataTemplate = new Array(10).fill({date: '2009-01-01',name: 'Tom',age: '30',
});const data = ref([]);
const disabled = ref(false);
const page = ref(0);
const total = ref(5);const load = () => {if (disabled.value) return;page.value++;if (page.value <= total.value) {data.value = data.value.concat(dataTemplate);}if (page.value === total.value) {disabled.value = true;}
};
</script><style lang="scss" scoped>
.el-table {:deep(table) {margin: 0;}
}
</style>
2、el-table-infinite-scroll(vue2)
- 安装
npm install --save el-table-infinite-scroll@2
- 全局引入
import Vue from "vue";
import ElTableInfiniteScroll from "el-table-infinite-scroll";
Vue.directive("el-table-infinite-scroll", ElTableInfiniteScroll);
- 局部引入
<script>
import ElTableInfiniteScroll from "el-table-infinite-scroll";
export default {directives: {"el-table-infinite-scroll": ElTableInfiniteScroll,},
};
</script>
- 组件中使用
<template><el-tablev-el-table-infinite-scroll="load":data="data":infinite-scroll-disabled="disabled"height="200px"><el-table-column type="index" /><el-table-column prop="date" label="date" /><el-table-column prop="name" label="name" /><el-table-column prop="age" label="age" /></el-table>
</template><script>
const dataTemplate = new Array(10).fill({date: "2009-01-01",name: "Tom",age: "30",
});export default {data() {return {data: [],page: 0,total: 5,};},methods: {load() {if (this.disabled) return;this.page++;if (this.page <= this.total) {this.data = this.data.concat(dataTemplate);}if (this.page === this.total) {this.disabled = true;}},},
};
</script>
六、使用Web Workers处理数据
Web Workers提供了一种将数据处理操作放在后台线程的方法,这样即使处理大量或者复杂的数据,也不会阻塞UI的更新和用户的交互。
在Web Workers中处理数据,前端主线程可以保持高响应性。数据处理完成后,再将结果发送回主线程进行渲染。这对于需要复杂计算处理的大量数据尤为有用。
这里不详细描述
七、借助服务端渲染(SSR)
服务端渲染(SSR)是指在服务器端完成页面的渲染工作,直接向客户端发送渲染后的HTML内容,能显著提升首次加载的速度,对于SEO也非常友好。
虽然SSR不是直接在前端处理大量数据,但它通过减轻前端渲染压力、提前渲染页面内容来间接优化大数据处理的性能问题。结合客户端渲染,可以实现快速首屏加载与动态交互的平衡。
这里不详细描述
相关文章:
深度剖析:前端如何驾驭海量数据,实现流畅渲染的多种途径
文章目录 一、分批渲染1、setTimeout定时器分批渲染2、使用requestAnimationFrame()改进渲染2.1、什么是requestAnimationFrame2.2、为什么使用requestAnimationFrame而不是setTimeout或setInterval2.3、requestAnimationFrame的优势和适用场景 二、滚动触底加载数据三、Elemen…...
AI时代,你的工作会被AI替代吗?
AI在不同领域的应用和发展速度是不同的。在智商方面,尤其是在逻辑推理、数据分析和模式识别等领域,AI已经取得了显著的进展。例如,在国际象棋、围棋等策略游戏中,AI已经能够击败顶尖的人类选手。在科学研究、医学诊断、股市分析等…...
Java_日志
日志技术 可以将系统执行的信息,方便的记录到指定的位置(控制台、文件中、数据库中) 可以随时以开关的形式控制日志启停,无需侵入到源代码中去进行修改。 日志技术的体系结构 日志框架:JUL、Log4j、Logback、其他实现。 日志接口…...
springcould-config git源情况下报错app仓库找不到
在使用spring config server服务的时候发现在启动之后的一段时间内控制台会抛出异常,spring admin监控爆红,控制台信息如下 --2024-06-26 20:38:59.615 - WARN 2944 --- [oundedElastic-7] o.s.c.c.s.e.JGitEnvironmentRepository : Error occured …...
MySQL serverTimezone=UTC
在数据库连接字符串中使用 serverTimezoneUTC 是一个常见的配置选项,特别是当数据库服务器和应用程序服务器位于不同的时区时。这个选项指定了数据库服务器应当使用的时区,以确保日期和时间数据在客户端和服务器之间正确传输和处理。 UTC(协…...
基于YOLOv9的PCB板缺陷检测
数据集 PCB缺陷检测,我们直接采用北京大学智能机器人开放实验室数据提供的数据集, 共六类缺陷 漏孔、鼠咬、开路、短路、杂散、杂铜 已经对数据进行了数据增强处理,同时按照YOLO格式配置好,数据内容如下 模型训练 采用YOLO…...
高考结束,踏上西北的美食之旅
高考的帷幕落下,暑期的阳光洒来,是时候放下书本,背上行囊,踏上一场充满期待的西北之旅。而在甘肃这片广袤的土地上,除了壮丽的自然风光,还有众多令人垂涎欲滴的美食等待着您的品尝。当您踏入甘肃࿰…...
人工智能 (AI) 在能源系统中应用的机会和风险
现代文明极度依赖于电力的获取。电力系统支撑着我们视为理所当然的几乎所有基本生活功能。没有电力的获取,大多数经济活动将是不可能的。然而,现有的电网系统并未设计来应对当前——更不用说未来的——电力需求。与此同时,气候变化迫切要求我…...
[AIGC] 定时删除日志文件
文章目录 需求实现脚本解释 需求 实现一个定时任务,定时删除两天前的日志文件,如果某个目录使用量超过80%,则删除文件 实现 要实现这样的要求,我们可以创建一个shell脚本,在该脚本中使用find命令查找两天前的日志文…...
C++:typeid4种cast转换
typeid typeid typeid是C标准库中提供的一种运算符,它用于获取类型的信息。它主要用于类型检查和动态类型识别。当你对一个变量或对象使用typeid运算符时,它会返回一个指向std::type_info类型的指针,这个信息包含了关于该类型名称、大小、基…...
vue3的配置和使用
vue的使用需要配置node且node版本需要在15以上。管理员方式打开cmd,输入node -v,可以查看node版本。 创建vue有以下两种方式 npm init vuelatestnpm create vuelatest创建后输入项目名,其它的输入否即可,新手可以先不用 按照要求…...
决策树划分属性依据
划分依据 基尼系数基尼系数的应用信息熵信息增益信息增益的使用信息增益准则的局限性 最近在学习项目的时候经常用到随机森林,所以对决策树进行探索学习。 基尼系数 基尼系数用来判断不确定性或不纯度,数值范围在0~0.5之间,数值越低&#x…...
短视频利器 ffmpeg (2)
ffmpeg 官网这样写到 Converting video and audio has never been so easy. 如何轻松简单的使用: 1、下载 官网:http://www.ffmpeg.org 安装参考文档: https://blog.csdn.net/qq_36765018/article/details/139067654 2、安装 # 启用RPM …...
【计算机毕业设计】基于Springboot的智能物流管理系统【源码+lw+部署文档】
包含论文源码的压缩包较大,请私信或者加我的绿色小软件获取 免责声明:资料部分来源于合法的互联网渠道收集和整理,部分自己学习积累成果,供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…...
【2024】LeetCode HOT 100——图论
目录 1. 岛屿数量1.1 C++实现1.2 Python实现1.3 时空分析2. 腐烂的橘子2.1 C++实现2.2 Python实现2.3 时空分析3. 课程表3.1 C++实现3.2 Python实现3.3 时空分析4. 实现 Trie (前缀树)4.1 C++实现4.2 Python实现4.3 时空分析1. 岛屿数量 🔗 原题链接:200. 岛屿数量 经典的Fl…...
解析Java中1000个常用类:Currency类,你学会了吗?
在线工具站 推荐一个程序员在线工具站:程序员常用工具(http://cxytools.com),有时间戳、JSON格式化、文本对比、HASH生成、UUID生成等常用工具,效率加倍嘎嘎好用。程序员资料站 推荐一个程序员编程资料站:程序员的成长之路(http://cxyroad.com),收录了一些列的技术教程…...
5.x86游戏实战-CE定位基地址
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 本次游戏没法给 内容参考于:微尘网络安全 上一个内容:4.x86游戏实战-人物状态标志位 上一个内容通过CE未知的初始值、未变动的数值、…...
istitle()方法——判断首字母是否大写其他字母小写
自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 istitle()方法用于判断字符串中所有的单词首字母是否为大写而其他字母为小写。istitle()方法的语法格式如下: str.istitle() …...
Linux实用命令练习
目录 一、常用命令 二、系统命令 三、用户和组 四、权限 五、文件相关命令 六、查找 七、正则表达式 八、输入输出重定向 九、进程控制 十、其他命令 1、远程文件复制:scp 2、locate查找 3、which命令 4、设置或显示环境变量:export 5、修…...
刷题——二叉搜索树与双向链表
二叉搜索树与双向链表_牛客题霸_牛客网 方法一: void dfs(TreeNode* pRootOfTree, TreeNode* &pre){if(pRootOfTree NULL)return;dfs(pRootOfTree->left, pre);//所有左子树if(pre)pre->right pRootOfTree;pRootOfTree->left pre;pre pRootOfTree…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...
安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
