在一些电商的小程序项目中,长列表是一个很普遍的场景,在加载大量的列表数据的过程中,可能会遇到手机卡顿,白屏等问题。也许数据进行分页处理可以防止一次性加载数据带来的性能影响,但是随着数据量越来越大,还是会让小程序应用越来越卡顿,响应速度越来越慢。这种问题不仅仅在小程序上,在移动端 h5 项目中同样存在。
这个时候就需要优化长列表,今天将一起讨论一下,长列表的优化方案及其实践。
影响小程序长列表性能的因素有很多。我们先分析一下小程序长列表的性能卡点是什么?
那么上述有一个问题,为什么向 item 注入数据会影响性能呢?这些主要是因为小程序的设计。
整个小程序框架系统分为两部分:逻辑层(App Service)和 视图层(View)。小程序提供了自己的视图层描述语言 WXML 和 WXSS,以及基于 JavaScript 的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,让开发者能够专注于数据与逻辑。
2.jpeg
但是当外层向每一个 Item 注入数据的时候,本质上是外层逻辑层注入数据到视图层,再从视图层传输到 item 组件的逻辑层。如下图所示。
1.jpeg
解决方案:
那么如上是造成长列表性能瓶颈的原因,那么解决手段是什么呢?
无论是上述哪种性能卡点,本质上原因就是 Item 的数量过多导致的,在小程序和移动端 h5 上,由于滑动加载,会导致数据量越来越大,item 越来越多,所以 控制 item 数量 是解决问题的关键。
通过上面我们知道了,解决长列表的手段本身就是控制 item 的数量,原理就是当数据填充的时候,理论上数据是越来越多的,但是可以通过手段,让视图上的 item 渲染,而不在视图范围内的数据不需要渲染,那就不去渲染,这样的好处有:
明白了基本原理之后,接下来看一下具体的实现方案。
让视图区域的 item 真实的渲染,这是长列表优化的主要手段,那么第一个问题就是如何知道哪些 item 在可视区域内? 正常情况下,当在移动端滑动设备的时候,只有手机屏幕内可视区域是真正需要渲染的部分,如下所示:
那就首先就要知道哪些 item 在屏幕区域内,一般情况下,这种长列表都是基于 scroll-view 实现的,scroll-view 提供了很多回调函数可以处理滚动期间发生的事件。比如 scroll,scrolltoupper,scrolltolower 等。
在 scroll 滑动过程中,可以通过 srollTop 和 scroll-view 的高度,以及每一个 item 的高度,来计算哪些 item 是在视图范围内的。
下面我们来简单的计算一下,在视图区域内的 item 的索引:
那么计算流程如下所示:
5.jpeg
那么处于 startIndex 和 endIndex 的 item 就是在视图区域的 item 。那么通过 slice 截取到列表就是需要渲染的内容。
this.setData({
renderList:this.data.dataList.slice(startIndex,endIndex)
})
但是如果只让视图中的 item 进行渲染,那么其他 item 的地方如何处理呢,因为我们需要 scroll-view 构造出真实滑动到当前位置的效果。这个时候为了创建出 scroll-view 真实的滑动效果,不需要渲染数据的地方可以用一个空的元素占位。
缓冲区:但是正常情况下,不会直接将 startIndex 和 endIndex 作为真正渲染的内容。因为滑动的速度是快速的,以竖直方向上的滑动为例子,如果快速上滑或者下滑过程中,需要触发 setData 改变渲染的内容,那么更新不及时的情况下,不会让用户看到真实的列表内容,这样就会造成一个极差的用户体验。
为了解决这个问题,引出了一个上下缓冲的概念,就是在渲染真实的列表 item 的时候,在滑动的两个边界加上一定的缓冲区,在缓冲区的 item 也会正常渲染。
还是以上下滑动为例子,我们来看一下,缓冲区是如何定义的。
所以 [ startIndex - bufferCount , endIndex + bufferCount ] 为真正的渲染区间,在这个区间内部的 item 都会真实的渲染。
如下图,我们来看一下在滑动过程中,渲染区间的变化情况:
6.jpeg
对于 bufferCount ,总结好处有以下二点:
小程序提供了 createIntersectionObserver 接口,可以创建 IntersectionObserver 对象来判断元素是否在可视区域内。
这个 api 一般用于判断曝光埋点,微信官方建议使用节点布局相交状态监听 IntersectionObserver 推断某些节点是否可见、有多大比例可见;
先来看一下这个方法的基础方法:createIntersectionObserver 创建并返回一个 IntersectionObserver 对象实例。在自定义组件或包含自定义组件的页面中,应使用 this.createIntersectionObserver([options]) 来代替。接下来看一下 IntersectionObserver 对象上有哪些方法。
基础使用:
wxml中:
<!--index.wxml-->
<view class="container">
<view class="usermotto" />
<view id="currentView" >观察元素节点</view>
</view>
js中:
Page({
onLoad() {
const Observer = wx.createIntersectionObserver(this)
Observer.relativeToViewport({
top: 0, // 当元素到达顶部触发回调事件
bottom:0, // 当元素到达底部触发回调事件
}).observe('#currentView',(res)=>{
console.log('元素是否在可视范围内',res.intersectionRatio <= 0 ? '否' : '是' )
})
},
})
如上通过 IntersectionObserver 对象来监听元素的位置,然后可以通过 res.intersectionRatio 判断元素是否在指定的区域内部。
8.gif
实现原理:
那么这个方法,既然能判断元素的曝光,那么也可以用来做长列表优化使用。它的实现原理如下所示:
7.jpeg
这种方式可以把数据进行分组,然后每组创建一个 IntersectionObserver ,当分组处于视图区域内的时候,才渲染本分组的数据,那么其他分组没有在视图范围内,所以不需要渲染真实的元素,只需要渲染占位元素或者是骨架节点就可以了。
缓冲距离:
这种实现方案也会存在相同的问题,就是在快速滑动过程中,如果只选择上下边界 top:0 和 bottom:0 ,那么也会造成滑动时候,渲染不及时导致无法看到正常的列表元素的情况发生。
为了解决这个问题,那么也会设置一定的缓冲距离,这个一般会在边界处入手。比如我们可以设置当列表分组在距离屏幕上边界和下边界一屏距离的时候就触发事件,渲染真实的元素。
wx.getSystemInfo({
success: (res) => {
const { windowHeight } = res
const Observer = wx.createIntersectionObserver(this)
Observer.relativeToViewport({
top: windowHeight, // 距离屏幕顶部一屏距离
bottom:windowHeight,// 距离屏幕底部一屏距离
}).observe('#xxx',(res)=>{
//...
this.setData({
// ...选择渲染的列表
})
})
},
});
如上面所示,通过 getSystemInfo 获取屏幕高度 windowHeight,然后设置 top ,bottom 为屏幕高度,这样当列表分组处于距离屏幕顶部一屏距离和屏幕底部一屏距离都会触发事件,然后就可以通过 intersectionRatio 判断当前列表分组是消失在视图区域,还是进入到视图区域。
实现的原理图如下:
8.jpeg
长列表解决方案目前已经非常成熟了,有很多解决思路,但是本质上都是大相径庭的。这里介绍两种能够在实际开发中落地的技术方案。
对于长列表方案,微信官方有一套自己的解决方案,就是 recycle-view 。
核心的思路就是只渲染显示在屏幕的数据,基本实现就是监听 scroll 事件,并且重新计算需要渲染的数据,不需要渲染的数据留一个空的 div 占位元素。
滚动过程中,重新渲染数据的同时,需要设置当前数据的前后的 div 占位元素高度,同时是指在同一个渲染周期内。。
在滚动过程中,为了避免频繁出现白屏,会多渲染当前屏幕的前后2个屏幕的内容。
长列表组件由2个自定义组件 recycle-view、recycle-item 和一组 API 组成,对应的代码结构如下
├── miniprogram-recycle-view/
└── recycle-view 组件
└── recycle-item 组件
└── index.js
包结构详细描述如下:
目录/文件 | 描述 |
---|---|
recycle-view 组件 | 长列表组件 |
recycle-item 组件 | 长列表每一项 item 组件 |
index.js | 提供操作长列表数据的API |
来看一下具体的使用:
1 . 安装组件
npm install --save miniprogram-recycle-view
{
"usingComponents": {
"recycle-view": "miniprogram-recycle-view/recycle-view",
"recycle-item": "miniprogram-recycle-view/recycle-item"
}
}
2 . 在页面的 json 配置文件中添加 recycle-view 和 recycle-item 自定义组件的配置
<recycle-view batch="{{batchSetRecycleData}}" id="recycleId">
<view slot="before">长列表前面的内容</view>
<recycle-item wx:for="{{recycleList}}" wx:key="id">
<view>
<image style='width:80px;height:80px;float:left;' src="{{item.image_url}}"></image>
{{item.idx+1}}. {{item.title}}
</view>
</recycle-item>
<view slot="after">长列表后面的内容</view>
</recycle-view>
3 . WXML 文件中引用 recycle-view
const createRecycleContext = require('miniprogram-recycle-view')
Page({
onReady: function() {
var ctx = createRecycleContext({
id: 'recycleId',
dataKey: 'recycleList',
page: this,
itemSize: { // 这个参数也可以直接传下面定义的this.itemSizeFunc函数
width: 162,
height: 182
}
})
ctx.append(newList)
// ctx.update(beginIndex, list)
// ctx.destroy()
},
itemSizeFunc: function (item, idx) {
return {
width: 162,
height: 182
}
}
})
4 . 页面 JS 管理 recycle-view 的数据
通过这种的优缺点是显而易见的。首先对于 view 和 item 的结构是清晰的,但是对于数据需要手动通过 ctx.append 进行追加,而且对于整个 recycle-view 和 recycle-item 的处理逻辑是要和业务层耦合在一起的,这种方式对于小程序的开发者有一定技术熟练度的要求。
当然 recycle-view 是基于微信原生小程序实现的,所以可以适用于原生小程序,以及基于原生小程序衍变的其他平台小程序,比如支付宝小程序,美团小程序等。即便是 api 层面不兼容,也可以通过下载改造的方式,来应用到我们的项目中。
Taro 是多端统一开发的解决方案,可以一套代码运行到移动 web 端,小程序端,React Native 端,Taro 的实现原理也如出一辙,比起全量渲染数据生成的视图,Taro 只渲染当前可视区域(visible viewport)的视图,非可视区域的视图在用户滚动到可视区域再渲染:
3.jpg
如上图就是大致的实现原理。
基本使用:
使用 React/Nerv 我们可以直接从 @tarojs/components/virtual-list 引入虚拟列表(VirtualList)组件:
import VirtualList from '@tarojs/components/virtual-list'
以 Taro React 为例子,接下来看一下 VirtualList 的具体使用:
function buildData (offset = 0) {
return Array(100).fill(0).map((_, i) => i + offset);
}
const Row = React.memo(({ id, index, style, data }) => {
return (
<View id={id} className={index % 2 ? 'ListItemOdd' : 'ListItemEven'} style={style}>
Row {index} : {data[index]}
</View>
);
})
export default class Index extends Component {
state = {
data: buildData(0),
}
render() {
const { data } = this.state
const dataLen = data.length
return (
<VirtualList
height={500} /* 列表的高度 */
width='100%' /* 列表的宽度 */
itemData={data} /* 渲染列表的数据 */
itemCount={dataLen} /* 渲染列表的长度 */
itemSize={100} /* 列表单项的高度 */
>
{Row} /* 列表单项组件,这里只能传入一个组件 */
</VirtualList>
);
}
}
VirtualList 的五个属性都是必填项。VirtualList 的数据处理,数据截取,空白填充都是内部实现的,开发者只需要关注将 data 数据注入到 VirtualList 就可以了。这样让虚拟列表使用成本大大降低,也降低了和业务的耦合度。
VirtualList 这种方式是基于 Taro 平台开发的,所以它的使用场景就有一定局限性,开发者只能通过 Taro 中使用,比如一些原生小程序,就很不适用了,即便是想要通过改造源码的方式来让 VirtualList 兼容原生小程序或者其他平台的小程序,成本也是巨大的,无异于重构一下项目。
接下来我们实现一个长列表组件,选用的是第二种基于 IntersectionObserver 这种方式,我们实现的这个长列表遵循一下原则:
业务组件使用:在正式讲解之前,先来看一下长列表组件是如何使用的:
业务组件 wxml 文件:
<long-list-view
list="{{list}}"
generic:item="list-item"
generic:skeleton="list-skeleton"
/>
可以看到 long-list-view 的 props 只有三个。
我们看一下业务组件的 json 文件:
"usingComponents": {
"list-item":"...", // 业务组件每个卡片组件 item
"list-skeleton":"...", // 当业务组件不渲染时,占位的组件
"long-list-view":"..." // 长列表组件
}
这里引入了一个新的概念—抽象节点。那么我们先来看看什么是抽象节点。
有时,自定义组件模板中的一些节点,其对应的自定义组件不是由自定义组件本身确定的,而是自定义组件的调用者确定的。这时可以把这个节点声明为“抽象节点”。
例如,我们现在来实现一个“选框组”(selectable-group)组件,它其中可以放置单选框(custom-radio)或者复选框(custom-checkbox)。这个组件的 wxml 可以这样编写:
<!-- selectable-group.wxml -->
<view wx:for="{{labels}}">
<label>
<selectable disabled="{{false}}"></selectable>
{{item}}
</label>
</view>
其中,“selectable”不是任何在 json 文件的 usingComponents 字段中声明的组件,而是一个抽象节点。它需要在 componentGenerics 字段中声明:
{
"componentGenerics": {
"selectable": true
}
}
通过上面明白了抽象节点的使用。那么为什么要用抽象节点呢?
recycle-item
relations: {
'./recycle-view': {
type: 'parent', // 关联的目标节点应为子节点
linked() {}
}
},
recycle-view
relations: {
'../recycle-item/recycle-item': {
type: 'child', // 关联的目标节点应为子节点
}
}
言归正传,我们主要是通过抽象节点实现的,抽象节点的注册是在业务组件中的,但是使用是在公共组件中的,这样就大大降低和业务组件的耦合程度。
说明白了抽象节点,然后我们来看一下长列表组件结构。
├── long-list-view/
└── index.js
└── index.wxml
└── index.json
└── index.wxss
首先要在 index.json 声明当前组件和抽象节点。
index.json
{
"component": true,
"componentGenerics": {
"item": true, //抽象节点 item
"skeleton": true //抽象节点 skeleton
}
}
接下来就是重点看一下 index.js 。从上面我们知道传入长列表组件中的数据 list ,list 是随着加载数据增多,会越来越多;同时还有有一种情况发生,就是如果 list 变化特别频繁,那么会让长列表一直触发 setData 来执行渲染任务,这样也会造成卡顿的,那么我们长列表需要做的事情是:
时间切片
时间切片的手段通常可以采用 setTimeout 来模拟实现,比如一次性有 50 个 item 需要渲染,那么 list 每次追加 10 个item ,这个时候就会让 setData 在短时间内执行 5 次,并且还要有视图上的响应,这样就会造成性能上和用户体验上的问题。
那么时间切片是如何解决这个问题的呢?首先每一次渲染会创建一个渲染任务 task,但是并不会立即执行 task,而是把 task 放进一个待渲染的队列 renderPendingQueue 中,然后每次执行队列中的一个任务,当任务执行完毕后,通过 setTimeout 来在下一次的宏任务中再次执行下个更新任务。其原理图如下所示:
WechatIMG91.jpeg
下面我们来实现这个功能:
Component({
properties:{
list: {
type: Array,
value: [],
/* 通过 observer 来监听 list 的变化 */
observer(val, preVal) {
if (val.length) {
const cloneVal = val.slice();
/* 找到增量传入的 list */
cloneVal.splice(0, preVal.length);
/* 创建一个渲染任务 */
const task = () => {
/* setList 为真正需要执行的渲染列表的方法 */
this.setList(cloneVal);
};
/* 把渲染任务放入到待渲染队列中 */
this.renderPendingQueue.push(task);
/* 开始执行渲染任务 */
this.runRenderTask();
}
},
},
},
methods:{
/* 更新待渲染对列 */
runRenderTask() {
/* 如果没有渲染任务了,或者渲染任务已经开始,那么直接返回 */
if (this.renderPendingQueue.length === 0 || this.isRenderTask) return;
/* 取出第一个渲染任务 */
const current = this.renderPendingQueue.shift();
/* 开始渲染 */
this.isRenderTask = true;
/* 执行渲染任务 */
typeof current === 'function' && current();
},
},
})
这个的处理逻辑是:
从上面可以知道,setList 为真正需要执行的渲染列表的方法,我们接着往下看:
Component({
//...
data:{
/* 待渲染的视图列表 */
viewList: [],
/* 手机屏幕高度 */
winHeight: 0,
/* 分组索引 */
groupIndex:0
},
lifetimes: {
ready() {
wx.getSystemInfo({
success: (res) => {
this.setData({
/* 获取屏幕的高度 */
winHeight: res.windowHeight,
});
},
});
/* 保存所有数据 */
this.wholeList = []
/* 记录分组高度 */
this.groupHeightArr = []
}
},
methods:{
/* 装载数据 */
setList(val) {
const { groupIndex } = this.data;
const newList = this.data.viewList;
/* 把数据保存到 wholeList 中 */
this.wholeList[groupIndex] = val;
/* 插入到视图列表中 */
newList[groupIndex] = val;
this.data.groupIndex++;
/* 直接渲染最新加入的数据 */
this.setData(
{
viewList: newList,
},
() => {
/* 记录渲染后的视图高度 */
this.setHeight(groupIndex);
}
);
},
},
})
setList 做的事情很简单,将增量的列表数据渲染出来,然后开始通过 setHeight 将渲染后的数据高度记录下来,这里有的同学会问,为什么要记录渲染后的数据高度,原因很简单,如果列表单元项 item 在规定的渲染区域之外,那么需要 skeleton 占位,但是需要设置 skeleton 高度,所以需要记录分组的高度作为占位节点的高度。
Component({
//...
methods:{
//...
···/* 设置高度 */
setHeight(groupIndex) {
const query = wx.createSelectorQuery().in(this);
query && query
.select(`#wrp_${groupIndex}`)
.boundingClientRect((res) => {
/* 记录分组的高度 */
this.groupHeightArr[groupIndex] = res && res.height;
})
.exec();
/* 开始监听分组变化 */
this.observePage(groupIndex);
},
},
})
通过 setHeight 来记录高度之后。那么接下来就需要给当前分组创建一个 IntersectionObserver 来判断:
Component({
//...
methods:{
//...
/* 观察页面变化 */
observePage(groupIndex) {
wx.createIntersectionObserver(this)
.relativeToViewport({
/* 这里规定的有效区域为两个屏幕 */
top: 2 * this.data.winHeight,
bottom: 2 * this.data.winHeight,
})
.observe(`#wrp_${groupIndex}`, (res) => {
const newList = this.data.viewList;
const nowWholeList = this.wholeList[groupIndex];
if (res.intersectionRatio <= 0) {
console.log('当前分组:',groupIndex,'虚拟节点占位' )
/* 如果不在有效的视图范围内,那么不需要渲染真实的数据,只需要计算高度,进行占位就可以了。 */
const listViewHeightArr = [];
const listViewItemHeight = this.groupHeightArr[groupIndex] / nowWholeList.length;
for (let i = 0; i < nowWholeList.length; i++) {
listViewHeightArr.push({ listViewItemHeight });
}
newList[groupIndex] = listViewHeightArr;
} else {
console.log('当前分组:',groupIndex,'显示' )
/* 如果在有效的区域内,那么直接渲染真实的数据就可以了 */
newList[groupIndex] = this.wholeList[groupIndex];
}
this.setData({
viewList: newList,
}, () => {
this.isRenderTask = false;
/* 渲染下一个更新任务 */
this.runRenderTask();
});
});
},
},
})
看一下 observePage 做了哪些事情?
接下来看一下 wxml 如何处理的:
<view class="list-view">
<!-- 列表前自定义插槽 -->
<slot name="before" />
<view
wx:for="{{ viewList }}"
id="wrp_{{ groupIndex }}"
wx:for-index="groupIndex"
wx:for-item="listItem"
wx:key="index"
>
<view
wx:for="{{ listItem }}"
wx:for-item="listItem"
wx:key="index"
>
<block wx:if="{{ listItem.listViewItemHeight }}">
<!-- 不在可视范围内 -->
<view style="height: {{listItem.listViewItemHeight}}px;overflow: hidden">
<skeleton/>
</view>·
</block>
<block wx:else>
<!-- 在可视范围内 -->
<item listItem="{{listItem}}"/>
</block>
</view>
</view>
<!-- 列表后自定义插槽 -->
<slot name="after" />
</view>
模拟使用:
接下来我们做一下模拟:
<scroll-view
scroll-y="{{ true }}"
bindscrolltolower="handleScrollLower"
style="height:{{winHeight}}px;"
lower-threshold="200"
>
<long-list-view
list="{{list}}"
generic:item="item"
generic:skeleton="skeleton"
/>
</scroll-view>
数据是模拟的,接下来看一下整体的效果:
13.gif
本章节介绍了在小程序端长列表的性能瓶颈,介绍了常用的解决方案,感兴趣的同学可以试着实现一下长列表,也希望做小程序列表优化的同学看到,能够有一个启发。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/zgpK6L0Tf81KIhf-4k-gnA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。