背景
在竞争激烈的移动时代,各大互联网公司都在争相抢夺市场,如何提高研发效率,快速迭代产品成为非常重要的因素。
跨平台方案能够节约一定开发、测试、运维成本。Flutter是由谷歌开源的跨平台框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。
一、 为什么选择Flutter
携程在已经引入了 React Native 的情况下,为什么还会选择 Flutter?更多是对性能的考虑。开发效率与性能体验就像天平两端,需要找到一个平衡点。RN 能够满足我们绝大部分的业务,并且热更、版本控制都很灵活。但是在复杂页面上,特别是在长列表的渲染上,还是存在一定的问题,促使我们去尝试一些新的解决方案。Flutter官宣自绘UI引擎,采用原生方式做渲染,媲美原生体验。
Native 、React Native、Flutter 对比如下:
1.1 研发效率
Flutter具有跨平台性,可以在多端上运行。同时Dart语言作为开发语言,本身的优势就在于它既支持JIT,又支持AOT,在 JIT(Just In Time)即时编译功能下,能提供 Hot Reload 功能。在开发过程中,实时地看到界面改动。生产包AOT编译,将代码编译成 ARM 二进制,从而既可以享受运行时又具有原生语言相近的运行效率。
1.2 扩展性好
Flutter提供了多种不同的Channel,用于 Dart 和平台之间相互通信。通过这些桥方法,使Flutter具有很好地与 Native 和 React Native 进行混合编程的能力。赋予 Flutter 一些 Native 的能力,同时也能很好地让我们在现有 Native 项目混合Flutter开发。
二、 Provider对MVVM架构的实践
在Flutter的开发过程中,特别是一些业务复杂的页面,为了代码结构清晰,模块逻辑解耦,我们一般采用的是模块化的编程思想。随之而来的问题就是,组件之间怎么相互通讯,比如变更了登录态,如何通知其他模块刷新?
推荐使用Provider来管理各个组件的状态,我们实践下来 ,主体布局采用MVVM模式是比较方便做模块化编程的。
2.1 为什么需要使用Provider
如果状态是该组件私有的,则应该由组件自己管理;但是如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。对于组件私有的状态很好理解,当需要刷新当前widget的时候,只需要通过setState()的方法来实现组件重绘的效果;对于跨组件共享的状态,可以使用EventBus来实现。
可是当事件多了的时候,难以正确管理,其次订阅者必须要显式注册状态改变回调,也必须在组件销毁的时候手动解绑以避免内存泄漏。而Provider就可以通过自身的原理,简单地去实现状态共享,不需要麻烦的操作。且Provider是官方推荐的状态管理方式,具有良好的生态环境及维护团队。
2.2 Provider的实现原理
Provider是基于InheritedWidget的再次封装,InheritedWidget提供了一种数据在Widget树中自上而下传递,共享的方式。我们在根Widget继承了InheritedWidget,然后在该组件中存放一个数据data,那么可以在任意子Widget中来获取该组件的数据并使用。当在任一组件中改变了共享数据data,InheritedWidget组件会自上而下通知所有使用过共享数据的组件并刷新组件,同时会回调didChangeDependencies() 方法。
共享数据的Model变化后,会自动通知ChangeNotifierProvider,ChangeNotifierProvider内部会重新构建InheritedWidget,而依赖该InheritedWidget的子Widget就会更新。
2.3 Provider的使用方式 架构模式图如下:
1)创建业务ViewModel,在ViewModel内部存放需要共享的数据。ViewModel 继承Flutter SDK中提供的ChangeNotifier类,它继承Listenable,也实现了一个Flutter风格的订阅者模式,其内部实现了addListener(),removeListener()等方法,实现对订阅者的处理。同时最好复写dispose()和notifyListeners()方法,防止用户在调用数据时销毁界面,而等到数据获取到以后通知界面刷新导致Crash。
2)注册状态管理类,使用ChangeNotifierProvider或者MutiProvider将需要共享数据的Widget包起来,单个NotifierProvider时使用ChangeNotifierProvider,多个NotifierProvider时使用MutiProvider包装,如下:
///多个NotifierProvider的时候
return MultiProvider(providers: [
ChangeNotifierProvider(create: (context) => dataViewModel(mCommonAdvancedFilterRoot,query)),
ChangeNotifierProvider(create: (context) => UserPreferentialViewModel(query)),
ChangeNotifierProvider(create: (context) => UserPromotionViewModel())
///需要调用共享数据的子Widget
], child: ListResearchPageful(query));
3)在被包起来的Widget中的任一子组件中获取共享数据ViewModel,可以在StatefulWidget中的builder()方法中获取,也可以使用Builder组件进行获取,如下:
///在StatefulWidget中的build()方法中获取ViewModel
class ListResearchPageState extends TripState<ListResearchPageful> {
@override
Widget build(BuildContext context) {
///使用Provider包装以后,可以在widget的任一一个子widget获取共享数据并操作数据,在这里就是可以在HotelListView方法下的唯一位置获取ViewModel
var listViewModel = Provider.of<ListDataViewModel>(context);
var userPromotionViewModel = Provider.of<UserPromotionViewModel>(context);
return MediaQuery(
child: QueryListPage(widget.query,
ListDataViewModel, userPromotionViewModel));
}
}
///借用Builder组件进行获取ViewModel
@override
Widget build(BuildContext context) {
///使用Provider包装以后,可以在widget的任一一个子widget获取共享数据并操作数据,在这里就是可以在ListView方法下的唯一位置获取ListDataViewModel
var userPromotionViewModel = Provider.of<UserPromotionViewModel>(context);
return MediaQuery(
child: Builder(builder: (context) {
var listDataViewModel = Provider.of<ListDataViewModel>(context);
return queryListPage(
widget.query, listDataViewModel, userPromotionViewModel);
},));
}
4)获取到ViewModel后,可以在子组件中直接使用viewmodel中的共享数据,如下:
//领券监听
///此处可以直接使用viewModel调用viewmodel中的方法
Event.addEventListener(
"UPDATE_QUERY_RESULT_LIST",(eventName, eventData) {
if (isOnPause) {
listViewModel.isNeedRefresh = true;
listViewModel.refreshListData(listViewModel.query);
} else {
listViewModel.refreshListData(listViewModel.query);
}
});
2.4 Provider的优势
1)我们的业务代码更专注数据,只要更新Model,UI就会自动更新,不用在状态改变后再去手动调用setState()来显示更新页面。
2)数据改变的消息传递被屏蔽时,我们无需手动去处理状态改变事件的发布和订阅,provider自行处理。
3)在大型复杂应用中,尤其是需要全局共享的状态非常多时,使用Provider将会大大简化代码逻辑,降低出错的概率,提高开发效率。
三、Flutter 性能调优
一个新技术改造完成,我们最关注的当然是性能体验有没有达到预期。那Flutter页面性能评判标准是什么,如何去度量,有没有可视化工具,帮我们去做一些性能调优。
3.1 Flutter渲染原理简介
在做性能优化之前,先让我们了解一下渲染的原理。Flutter的一切皆为Widget。为了性能又区分了 [StatefulWidget]。StatefulWidget 能通过[setState()]来实现刷新。这样的设计方便我们去控制局部刷新,从而提高性能。
Flutter 中的控件会历 Widget -> Element -> RenderObject -> Layer 这样的变化过程,而其中 Layer 的组成由 RenderObject 中的 isRepaintBoundary 标志位决定。
当调用 setState() 时,RenderObject 就会往上的父节点去查找,根据 isRepaintBoundary是否为 true,会决定是否从这里开始往下去触发重绘,来确定要更新哪些区域。
3.2 构建运行Profile模式
Flutter 支持三种模式编译 app,Debug模式、Release模式和Profile模式。Debug 模式 采用JIT编译,支持HotReload,所以在Debug模式下会放大性能问题。性能分析需要确保使用真机并在profile模式下运行,这样拿到的数据是最接近真实性能的。
1)Debug 模式对应 Dart 的 JIT 模式,可以在真机和模拟器上运行。该模式会打开所有的断言,以及所有的调试信息、服务扩展和调试辅助。此外,该模式支持有状态的 Hot reload。
2)Release 模式对应 Dart 的 AOT 模式,只能在真机上运行,不能在模拟器上运行,其编译目标为最终的线上发布。该模式会关闭所有的断言,以及尽可能多的调试信息、服务扩展和调试辅助。此外,该模式优化了应用快速启动、代码快速执行,以及二级制包大小。
3)Profile 模式,基本与 Release 模式一致,只是多了对 Profile 模式的服务扩展的支持,包括支持跟踪,以及一些为了最低限度支持所需要的依赖。该模式用于分析真实设备实际运行性能。
flutter run —profile 命令是使用 Profile 模式来编译的。IDE 也是支持这个模式的,例如 Android Studio 提供了 Run > Profile… 菜单选项。
a. 打包Flutter工程Profile产物
// 进入flutter项目,执行build-release,并指定输出目录 tripflutter
build-release -o /projects/ctrip_flutter/release -i info
b. 配置Native项目 打包好flutter产物之后,需要导入到native项目并打包。修改Native项目根目录的gradle.properties文件。
### 开启Profile模式
TRIP_FLUTTER_PROFILE=true
### 设置profile模式下js使用的产物目录(过程1构建的 ./profile 目录)
TRIP_FLUTTER_LOCAL_OUTPUTS_PATH=/projects/ctrip_flutter/profile
c. 构建Native工程
直接通过IDE运行到手机上。
3.3 性能分析工具及方法
1)performance overlay
平时常用的性能分析工具有performance overlay,通过它可以直观看到当前帧的耗时。在Profile模式下,通过Android Studio 看页面的FPS,注意需要在HotReload 连接的情况下查看。
选中 View > Tool Windows > Flutter Performance。
点击上面图中的箭头所指的按钮,就会在手机或模拟器中打开(如下图所示)。FPS是一个动态过程,页面滑动这个值是一直变化的,最右边的是当前帧。出现红色则表示耗时超过16.6ms,也就是发生丢帧现象,也是我们常说的页面闪动问题。performance overlay的主要功能如下:
2)Dart DevTool
另一个工具是Dart DevTool ,在Android studio右侧,还可以从Flutter inspector里面的more action,以及Flutter Performance底部的入口进入。
目前DevTools支持的功能有如下一些:
3.4 实战性能技巧
1)懒加载ListView
推荐使用ListView.builder()构建List,这样当Item滚入屏幕时才创建Item,而不是ListView-children,这样会立刻创建所有的Item。
///Bad code 不推荐使用children 构建List
ListView(children: getItems(mList))
List<Widget> getItems(List<FilterNode> mList){
List<Widget> items=new List<Widget>();
if(null!=mList){
for(Node node in mList){
items.add(Text("不推荐写法"));
}
}
return items;
}
///推荐写法
ListView.builder(
// physics: NeverScrollableScrollPhysics(),
//shrinkWrap: true,
itemCount:mList.length,
itemBuilder: (BuildContext context, int index) {
return Text("推荐使用ListView.builder()");
})
)
注意,无论是ListView还是GridView,只要是设置了shrinkWrap: true属性,都没有了懒加载的效果了。
2)控制刷新范围与次数
如上图所示,需要滑动的过程中,显示、隐藏标题栏,并且是一个渐变的过程,遇到这种情况,一定要尽量的控制刷新的范围和频次。控制在只在头图可见的情况下面触发setStat(),避免不必要的页面滑动触发刷新。
scrollController.addListener(() {
if (scrollController.offset > scrollHeight && titleAlpha != 255) {
setState(() {
titleAlpha = 255;
});
}
if (scrollController.offset <= 0 && titleAlpha != 0) {
setState(() {
titleAlpha = 0;
});
}
if (scrollController.offset > 0 && scrollController.offset < scrollHeight) {
setState(() {
titleAlpha = scrollController.offset * 255 ~/ scrollHeight;
});
}
});
如上图所示在列表中 Item 中存在大量的倒计时。一定要控制刷新倒计时只影响控件本身,并且只有可视的区域视图是在刷新的,不可见的情况下及时销毁计时器。一直刷整个列表,性能开销是恐怖的。
Widget build(BuildContext context) {
return Text(timeRemaining,
style: TextStyle(
color: HotelColors.hotel_list_reduction_sale_color,
fontSize: 10,
fontWeight: FontUtil.mediumWeight));
}
3)避免组件重复创建
能复用的组件尽量复用,特别是在组件化编程,页面级的情况下面,每次刷新页面把所有的子组件都重新渲染一遍,性能开销也是很大的。尽量复用,避免不必要的视图创建。
///存放界面所有的widgets,用以缓存
List<Widget> widgets = new List<Widget>();
///因为头部布局是静态的不刷新,使用变量控制是否复用以前的widgets
var refreshPage = true;
///获取界面布局所有的widgets
List<Widget> getPageWidgets(ScriptDataEntity data) {
if(widgets.isNotEmpty && !refreshPage) {
return widgets;
}
}
四、Flutter 布局技巧
4.1 Flutter 不可见组件预加载
Flutter 一些组件基本都是有懒加载的,不可见的组件是没有渲染视图的,这样滑动过去,有用到网络图片的地方,经常会先白一下。针对这种情况我们对将要加载的图片进行预加载处理,比如列表页在分页请求数据回来的时候做图片预加载。还有,下一个页面的图片,需要一进去就有图片直接显示,就可以在当前页面做图片预加载。 预加载
未预加载
代码如下所示:
///对每一页加载的数据进行做图片预加载
(hotelListViewModel.currentPageHotels ?? []).forEach((element) {
var logo = element?.logo ?? "";
if (StringUtil.isNotEmpty(logo)) {
precacheImage(NetworkImage(logo), context);
}
});
当数据出来后使用PreChcheImage()预加载处理图片链接,以保证当用户滑动图片以后不会看到图片加载白屏这种问题。
4.2 Flutter 数据预加载
为了缩短用户的加载等待时长,我们经常需要一些预加载方法。比如在前一个页面预加载下一个页面的数据,或者在长列表的分页请求时候,可以做分页预加载。比如当你滑动到第五个可见的时候,就提前把下一页的数据加载好。
列表页通过桥方法获取上一个页面预加载的数据,这样就能有一个直出体验,这里要考虑数据已经加载好、加载中、加载失败的情况。同时还要考虑,缓存数据的时效性,什么情况下需要删除缓存。
///请求列表数据数据
void loadListData(HotelQuery query) {
///在首页提前获取列表页的数据并缓存到本地,当用户进入列表页时可以直接展示数据
if (resultModel != null) {
///判断是否需要再次请求数据
_dealWithResult(resultModel);
return;
} else if (isPreloading) {
///通过桥方法获取首页已经缓存的数据 HotelBridge.getListCache<Map>({'queryModel':query.toJson()})
.then((resp) {
final newResultModel =
QueryResultModel.fromJson(resp);
///有缓存数据直接处理使用
_dealWithResult(newResultModel);
}).catchError((error) {
///没有数据采取请求列表页的数据
getHotelList();
});
}
}
4.3 布局自适应高度
如果需要根据内容填充的高度来自适应左边图片的高度,目前Flutter并不支持该功能,我们可以借助IntrinsicHeight组件来完美地解决该问题。InstrinsicHeight可以让同一行的子widget都是相同的高度。
五、Flutter 中常见问题分析及解决方案
5.1 设置State引起的问题
1)错误展示信息:
NoSuchMethodError: The method markNeedsBuild was called on null。
2)错误分析
这个错误一般情况下出现在异步任务,比如一些界面请求网络数据,异步获取本地数据等,需要根据数据的状态来改变刷新Widget State。异步任务结束在页面被销毁之后,没有检查State是否还是mounted状态,继续setState()就会出现这个错误。错误代码如下所示:
///从服务器端获取当前活动终止时间,当服务器返回以后,会通知刷新这里
///如果用户在数据返回之前销毁该界面,等数据回来以后刷新界面就会报错
final endTime = roomDetailItemEntity?.tonightEndTime ?? '';
int endTimeOfNum = 0;
if (endTime.isNotEmpty) {
try {
endTimeOfNum = int.parse(endTime) ?? 0;
if(endTimeOfNum - Util.currentTimeMillis() > 0) {
this.setState(() {
_showCountDown = true;
});
}
} catch (e) {}
}
3)处理办法
在调用setState()方法之前检查是否mounted,mounted是一个标示当前Widget树是否已经被渲染的状态值。所以mounted检查很重要,只要涉及到异步还有各种回调的时候,都不能忘记检查该值。如下:
final endTime = roomDetailItemEntity?.tonightEndTime ?? '';
int endTimeOfNum = 0;
if (endTime.isNotEmpty) {
try {
endTimeOfNum = int.parse(endTime) ?? 0;
if(endTimeOfNum - Util.currentTimeMillis() > 0) {
if(mounted) {
this.setState(() {
_showCountDown = true;
});}}} catch (e) {}
}
5.2 使用MediaQuery.of()动态获取屏幕属性的问题
1)错误展示信息
BoxConstraints has a negative minimum width;
2)错误分析
这种情况一般出现在需要获取屏幕宽度,根据屏幕宽度减去另外一个组件的宽度,用来设置另外一个组件的宽度导致,在一些计算速度比较低的手机,可能获取到的屏幕宽度为0,这样就会导致你的组件的宽度为负数,报出错误异常。如下所示:
Widget hotelListDesContent(BuildContext context) {
return Container(
///此处想实现左边是图片,右边是相关信息的布局,如果MediaQuery.on(context).size.width获取为0时,就会报出异常
width: MediaQuery.of(context).size.width - Dimens.image_width80,
///右边内容
child: Stack(children: [
Container(child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
hotelListDesName(),
englishName(),
hotelListRemarkContent(),],),),
///左边图片
Positioned(child: fullRoomItem()),
],
));
}
3)处理方式
尽量使用Expand,Flexible,Flex,Wrap,Stack等组件配合Column,Row进行动态布局设置组件的宽高等。如下所示:
Widget hotelListDesContent(BuildContext context) {
return Expanded(
flex: 1,child: Stack(
children: [Container(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
hotelListDesName(),
englishName(),
hotelListRemarkContent(),],),),
Positioned(child: fullRoomItem()),
],
));
}
5.3 使用Provider时,未判断界面状态通知界面刷新的问题
1)错误信息展示
Null check operator used on a null value;
2)错误分析
一般情况下出现这种问题是由于界面销毁后,继续调用notifyListeners()方法通知界面刷新引起的bug。当用户打开一个界面,我们发送了API请求,此时用户销毁了界面,我们并未监听,等到数据返回以后,强行通知界面刷新,导致Crash。如下所示:
HotelServices.getTyHotelRoomPrice(params, ApiCallBack(onSuccess: (Object obj) {
this.roomPriceEntity = HotelRoomPriceEntity.fromJson(obj);
this.resultCode = 1;
///如果在数据返回是,用户已经关闭当前界面,此处通知刷新界面会导致crash
notifyListeners();
}, onError: (int code, String message) {}
notifyListeners()
}));
3)处理方式
正常情况下,我们会写一个基类继承ChangeNotifier,在内部重新复写dispose()方法,同时重新封装方法通知刷新界面,在每次需要通知刷新界面的时候判断当前界面是否已经被销毁。如下所示:
import 'package:flutter/cupertino.dart';
/// ViewModel基类
class HotelViewModel extends ChangeNotifier{
bool _disposed = false;
@override
void dispose() {
_disposed = true;
super.dispose();
}
void hotelNotifyListeners() {
if(!_disposed){
notifyListeners();
}
}
}
5.4 使用Text.rich时导致的问题
1)错误信息展示:UnimplementedError
2)错误分析
出现这个问题的原因在于使用Text.rich来展示多个Span组件时,如果设置了最大行数,当组件超过最大行数,有别的组件未成功展示时,再次点击当前widget,使它接受时间,就会导致crash,用户的感知为操作无响应,其实已经crash。如下所示:
///母房型名称, 当前我们Text最大显示两行,当大于两行是,出现...,可是此时第二个组件无处显示,当用户点击就会crash
Row(children: <Widget>[
Expanded(child: Text.rich(TextSpan(
children: [TextSpan(
text: itemRoomEntity.baseName ??""),
WidgetSpan(
child: Container(
padding: EdgeInsets.only(bottom: Dimens.gap_dp3),
child: Icon(HotelIcons.show_more),
),
),
]), maxLines: 2, overflow: TextOverflow.ellipsis),
),
], crossAxisAlignment: CrossAxisAlignment.center,),
3)解决办法
使用Flexible代替Expanded,直接使用Text即可,区别在于Flexible不会自动填充整个剩余宽度,如下所示:
///母房型名称
Row( mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Flexible(child: Text((childCount > 1)?itemRoomEntity.baseName ?? "":"",
maxLines: 2,
overflow: TextOverflow.ellipsis,),),
Container(child: Icon(childCount ==1?HotelIcons.show_more:null),
margin: EdgeInsets.only(top: Dimens.gap_dp2),),
], crossAxisAlignment: CrossAxisAlignment.center,)
六、总结与展望
总结一下,本文我们介绍了选择Flutter的初衷,Provider 状态管理的实际使用,建议Flutter主体的构架采用MVVM模式,还介绍了一些Flutter性能检测、量化工具和一些性能优化点供大家参考。收集了Flutter开发过程中常见并且大量发生的问题,并提供了相应的解决方案。
在复杂业务和长列表上面体验,确实 Flutter 优于 React Native。但是React Native 也有它的优势,比如灵活的版本迭代。没有最好的跨平台方案,只有最合适业务的。目前来说,Flutter还处于早期阶段,随着Flutter2.0的重大升级,其跨平台能力、性能、生态系统将会蓬勃发展,还是很值得尝试的。后续我们也将有更多的业务接入Flutter。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/VP6WEQkEel3W4tdo3ThYDw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为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 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。