React Native 启动速度优化——JS 篇(全网最全,值得收藏)

发表于 2年以前  | 总阅读数:1958 次

前言

本文主要从 JavaScript 入手,总结了一些 JS 侧的优化要点。

1.JSEngine

rn_start_jsEngine

Hermes

Hermes 是 FaceBook 2019 年中旬开源的一款 JS 引擎,从 release[1] 记录可以看出,这个是专为 React Native 打造的 JS 引擎,可以说从设计之初就是为 Hybrid UI 系统打造。

Hermes 支持直接加载字节码,也就是说,BabelMinifyParseCompile 这些流程全部都在开发者电脑上完成,直接下发字节码让 Hermes 运行就行,这样做可以省去 JSEngine 解析编译 JavaScript 的流程,JS 代码的加载速度将会大大加快,启动速度也会有非常大的提升。

Hermes

更多关于 Hermes 的特性,大家可以看我的旧文[《移动端 JS 引擎哪家强》] 这篇文章,我做了更为详细的特性说明与数据对比,这里就不多说了。

2.JS Bundle

rn_start_jsBundle

前面的优化其实都是 Native 层的优化,从这里开始就进入 Web 前端最熟悉的领域了。

其实谈到 JS Bundle 的优化,来来回回就是那么几条路:

  • :缩小 Bundle 的总体积,减少 JS 加载和解析的时间
  • :动态导入(dynamic import),懒加载,按需加载,延迟执行
  • :拆分公共模块和业务模块,避免公共模块重复引入

如果有 webpack 打包优化经验的小伙伴,看到上面的优化方式,是不是脑海中已经浮现出 webpack 的一些配置项了?不过 React Native 的打包工具不是 webpack 而是 Facebook 自研的 Metro[2],虽然配置细节不一样,但道理是相通的,下面我就这几个点讲讲 React Native 如何优化 JS Bundle。

2.1 减小 JS Bundle 体积

Metro 打包 JS 时,会把 ESM 模块转为 CommonJS 模块,这就导致现在比较火的依赖于 ESM 的 Tree Shaking 完全不起作用,而且根据官方回复[3],Metro 未来也不会支持 Tree Shaking :

(Tree Shaking 太 low 了,我们做了个更酷的 Hermes)

因为这个原因,我们减小 bundle 体积主要是三个方向:

  • 对于同样的功能,优先选择体积更小的第三方库
  • 利用 babel 插件,避免全量引用
  • 制定编码规范,减少重复代码

下面我们举几个例子来解释上面的三个思路。

2.1.0 使用 react-native-bundle-visualizer 查看包体积

优化 bundle 文件前,一定要知道 bundle 里有些什么,最好的方式就是用可视化的方式把所有的依赖包列出来。web 开发中,可以借助 Webpack 的 webpack-bundle-analyzer 插件查看 bundle 的依赖大小分布,React Native 也有类似的工具,可以借助 react-native-bundle-visualizer[4] 查看依赖关系:

使用非常简单,按照文档安装分析就可。

2.1.1 moment.js 替换为 day.js

这是一个非常经典的例子。同样是时间格式化的第三方库, moment.js[5] 体积 200 KB,day.js[6] 体积只有 2KB,而且 API 与 moment.js 保持一致。如果项目里用了 moment.js,替换为 day.js 后可以立马减少 JSBundle 的体积。

2.1.2 lodah.js 配合 babel-plugin-lodash

lodash 基本上属于 Web 前端的工程标配了,但是对于大多数人来说,对于 lodash 封装的近 300 个函数,只会用常用的几个,例如 getchunk,为了这几个函数全量引用还是有些浪费的。

社区上面对这种场景,当然也有优化方案,比如说 lodash-es,以 ESM 的形式导出函数,再借助 Webpack 等工具的 Tree Sharking 优化,就可以只保留引用的文件。但是就如前面所说,React Native 的打包工具 Metro 不支持 Tree Shaking,所以对于 lodash-es 文件,其实还会全量引入,而且 lodash-es 的全量文件比 lodash 要大得多。

我做了个简单的测试,对于一个刚刚初始化的 React Native 应用,全量引入 lodash 后,包体积增大了 71.23KB,全量引入 lodash-es 后,包体积会扩大 173.85KB。

既然 lodash-es 不适合在 RN 中用,我们就只能在 lodash 上想办法了。lodash 其实还有一种用法,那就是直接引用单文件,例如想用 join 这个方法,我们可以这样引用:

// 全量
import { join } from 'lodash'

// 单文件引用
import join from 'lodash/join'

这样打包的时候就会只打包 lodash/join 这一个文件。

但是这样做还是太麻烦了,比如说我们要使用 lodash 的七八个方法,那我们就要分别 import 七八次,非常的繁琐。对于 lodash 这么热门的工具库,社区上肯定有高人安排好了,babel-plugin-lodash[7] 这个 babel 插件,可以在 JS 编译时操作 AST 做如下的自动转换:

import { join, chunk } from 'lodash'
// ⬇️
import join from 'lodash/join'
import chunk from 'lodash/chunk'

使用方式也很简单,首先运行 yarn add babel-plugin-lodash -D 安装,然后在 babel.config.js 文件里启用插件即可:

// babel.config.js

module.exports = {
  plugins: ['lodash'],
  presets: ['module:metro-react-native-babel-preset'],
};

我以 join 这个方法为例,大家可以看一下各个方法增加的 JS Bundle 体积:

全量 lodash 全量 loads-es lodash/join 单文件引用 lodash + babel-plugin-lodash
71.23 KB 173.85 KB 119 Bytes 119 Bytes

从表格可见 lodash 配合 babel-plugin-lodash 是最优的开发选择。

2.1.3 babel-plugin-import 的使用

babel-plugin-lodash 只能转换 lodash 的引用问题,其实社区还有一个非常实用的 babel 插件:babel-plugin-import[8],基本上它可以解决所有按需引用的问题

我举个简单的例子,阿里有个很好用的 ahooks[9] 开源库,封装了很多常用的 React hooks,但问题是这个库是针对 Web 平台封装的,比如说 useTitle 这个 hook,是用来设置网页标题的,但是 React Native 平台是没有相关的 BOM API 的,所以这个 hooks 完全没有必要引入,RN 也永远用不到这个 API。

这时候我们就可以用 babel-plugin-import 实现按需引用了,假设我们只要用到 useInterval 这个 Hooks,我们现在业务代码中引入:

import { useInterval } from 'ahooks'

然后运行 yarn add babel-plugin-import -D 安装插件,在 babel.config.js 文件里启用插件:

// babel.config.js

module.exports = {
  plugins: [
    [
      'import',
      {
        libraryName: 'ahooks',
        camel2DashComponentName: false, // 是否需要驼峰转短线
        camel2UnderlineComponentName: false, // 是否需要驼峰转下划线
      },
    ],
  ],
  presets: ['module:metro-react-native-babel-preset'],
};

启用后就可以实现 ahooks 的按需引入:

import { useInterval } from 'ahooks'
// ⬇️
import useInterval from 'ahooks/lib/useInterval'

下面是各种情况下的 JSBundle 体积增量,综合来看 babel-plugin-import 是最优的选择:

全量 ahooks ahooks/lib/useInterval 单文件引用 ahooks + babel-plugin-import
111.41 KiB 443 Bytes 443 Bytes

当然,babel-plugin-import 可以作用于很多的库文件,比如说内部/第三方封装的 UI 组件,基本上都可以通过babel-plugin-import 的配置项实现按需引入。若有需求,可以看网上其他人总结的使用经验,我这里就不多言了。

2.1.4 babel-plugin-transform-remove-console

移除 console 的 babel 插件也很有用,我们可以配置它在打包发布的时候移除 console 语句,减小包体积的同时还会加快 JS 运行速度,我们只要安装后再简单的配置一下就好了:

// babel.config.js

module.exports = {
    presets: ['module:metro-react-native-babel-preset'],
    env: {
        production: {
            plugins: ['transform-remove-console'],
        },
    },
};

2.1.5 制定良好的编码规范

编码规范的最佳实践太多了,为了切合主题(减少代码体积),我就随便举几点:

  • 代码的抽象和复用:代码中重复的逻辑根据可复用程度,尽量抽象为一个方法,不要用一次复制一次
  • 删除无效的逻辑:这个也很常见,随着业务的迭代,很多代码都不会用了,如果某个功能下线了,就直接删掉,哪天要用到再从 git 记录里找
  • 删除冗余的样式:例如引入 ESLint plugin for React Native[10],开启 "react-native/no-unused-styles" 选项,借助 ESLint 提示无效的样式文件

说实话这几个优化其实减少不了几 KB 的代码,更大的价值在于提升项目的健壮性和可维护性

2.2 Inline Requires

Inline Requires 可以理解为懒执行,注意我这里说的不是懒加载,因为一般情况下,RN 容器初始化之后会全量加载解析 JS Bundle 文件,Inline Requires 的作用是延迟运行,也就是说只有需要使用的时候才会执行 JS 代码,而不是启动的时候就执行。React Native 0.64 版本里,默认开启了 Inline Requires

首先我们要在 metro.config.js 里确认开启了 Inline Requires 功能:

// metro.config.js

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true, // <-- here
      },
    }),
  },
};

其实 Inline Requires 的原理非常简单,就是把 require 导入的位置改变了一下。

比如说我们写了个工具函数 join 放在 utils.js 文件里:

// utils.js

export function join(list, j) {
  return list.join(j);
}

然后我们在 App.js 里 import 这个库:

// App.js

import { join } from 'my-module';

const App = (props) => {
  const result = join(['a', 'b', 'c'], '~');

  return <Text>{result}</Text>;
};

上面的写法,被 Metro 编译后,相当于编译成下面的样子:

const App = (props) => {
  const result = require('./utils').join(['a', 'b', 'c'], '~');

  return <Text>{result}</Text>;
};

实际编译后的代码其实长这个样子:

rn_start_inlineRequire

上图红线中的 r() 函数,其实是 RN 自己封装的 require() 函数,可以看出 Metro 自动把顶层的 import 移动到使用的位置。

值得注意的是,Metro 的自动 Inline Requires 配置,目前是不支持export default 导出的,也就是说,如果你的 join 函数是这样写的:

export default function join(list, j) {
  return list.join(j);
}

导入时是这样的:

import join from './utils';

const App = (props) => {
  const result = join(['a', 'b', 'c'], '~');

  return <Text>{result}</Text>;
};

Metro 编译转换后的代码,对应的 import 还是处于函数顶层

rn_start_require

这个需要特别注意一下,社区也有相关的文章,呼吁大家不要用 export default 这个语法,感兴趣的可以了解一下:

深入解析 ES Module(一):禁用 export default object[11]

深入解析 ES Module(二):彻底禁用 default export[12]

2.3 JSBundle 分包加载

分包的场景一般出现在 Native 为主,React Native 为辅的场景里。这种场景往往是这样的:

  • 假设有两条基于 RN 的业务线 A 和 B,它们的 JSBundle 都是动态下发的
  • A 的 JSBundle 大小为 700KB,其中包括 600KB 的基础包(React,React Native 的 JS 代码)和 100KB 的业务代码
  • A 的 JSBundle 大小为 800KB,其中包括 600KB 的基础包和 200KB 的业务代码
  • 每次从 Native 跳转到 A/B 的 RN 容器,都要全量下载解析运行

大家从上面的例子里可以看出,600KB 的基础包在多条业务线里是重复的,完全没有必要多次下载和加载,这时候一个想法自然而然就出来了:

把一些共有库打包到一个 common.bundle 文件里,我们每次只要动态下发业务包 businessA.bundlebusinessB.bundle,然后在客户端实现先加载 common.bundle 文件,再加载 business.bundle 文件就可以了

这样做的好处有几个:

  • common.bundle 可以直接放在本地,省去多业务线的多次下载,节省流量和带宽
  • 可以在 RN 容器预初始化的时候就加载 common.bundle二次加载的业务包体积更小,初始化速度更快

顺着上面的思路,上面问题就会转换为两个小问题:

  • 如何实现 JSBundle 的拆包?
  • iOS/Android 的 RN 容器如何实现多 bundle 加载?

2.3.1 JS Bundle 拆包

拆包之前要先了解一下 Metro 这个打包工具的工作流程。Metro 的打包流程很简单,只有三个步骤:

  • Resolution:可以简单理解为分析各个模块的依赖关系,最后会生成一个依赖图
  • Transformation:代码的编译转换,主要是借助 Babel 的编译转换能力
  • Serialization:所有代码转换完毕后,打印转换后的代码,生成一个或者多个 bundle 文件

从上面流程可以看出,我们的拆包步骤只会在 Serialization 这一步。我们只要借助 Serialization 暴露的各个方法就可以实现 bundle 分包了。

正式分包前,我们先抛开各种技术细节,把问题简化一下:对于一个全是数字的数组,如何把它分为偶数数组和奇数数组?

这个问题太简单了,刚学编程的人应该都能想到答案,遍历一遍原数组,如果当前元素是奇数,就放到奇数数组里,如果是偶数,放偶数数组里。

Metro 对 JS bundle 分包其实是一个道理。Metro 打包的时候,会给每个模块设置 moduleId,这个 id 就是一个从 0 开始的自增 number。我们分包的时候,公有的模块(例如 react``react-native)输出到 common.bundle,业务模块输出到 business.bundle 就行了。

因为要兼顾多条业务线,现在业内主流的分包方案是这样的:

**1.**先建立一个 common.js 文件,里面引入了所有的公有模块,然后 Metro 以这个 common.js 为入口文件,打一个 common.bundle 文件,同时要记录所有的公有模块的 moduleId

// common.js

require('react');
require('react-native');
......

2.对业务线 A 进行打包,Metro 的打包入口文件就是 A 的项目入口文件。打包过程中要过滤掉上一步记录的公有模块 moduleId,这样打包结果就只有 A 的业务代码了

// indexA.js

import {AppRegistry} from 'react-native';
import BusinessA from './BusinessA';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => BusinessA);

**3.**业务线 B C D E...... 打包流程同业务线 A

上面的思路看起来很美好,但是还是存在一个问题:每次启动 Metro 打包的时候,moduleId 都是从 0 开始自增,这样会导致不同的 JSBundle ID 重复

为了避免 id 重复,目前业内主流的做法是把模块的路径当作 moduleId(因为模块的路径基本上是固定且不冲突的),这样就解决了 id 冲突的问题。Metro 暴露了 createModuleIdFactory 这个函数,我们可以在这个函数里覆盖原来的自增 number 逻辑:

module.exports = {
  serializer: {
    createModuleIdFactory: function () {
      return function (path) {
        // 根据文件的相对路径构建 ModuleId
        const projectRootPath = __dirname;
        let moduleId = path.substr(projectRootPath.length + 1);
        return moduleId;
      };
    },
  },
};

整合一下第一步的思路,就可以构建出下面的 metro.common.config.js 配置文件:

// metro.common.config.js

const fs = require('fs');

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
  serializer: {
    createModuleIdFactory: function () {
      return function (path) {
        // 根据文件的相对路径构建 ModuleId
        const projectRootPath = __dirname;
        let moduleId = path.substr(projectRootPath.length + 1);

        // 把 moduleId 写入 idList.txt 文件,记录公有模块 id
        fs.appendFileSync('./idList.txt', `${moduleId}\n`);
        return moduleId;
      };
    },
  },
};

然后运行命令行命令打包即可:

# 打包平台:android
# 打包配置文件:metro.common.config.js
# 打包入口文件:common.js
# 输出路径:bundle/common.android.bundle

npx react-native bundle --platform android --config metro.common.config.js --dev false --entry-file common.js --bundle-output bundle/common.android.bundle

通过以上命令的打包,我们可以看到 moduleId 都转换为了相对路径,并且 idList.txt 也记录了所有的 moduleId:

common.android.bundle idList.js

第二步的关键在于过滤公有模块的 moduleId,Metro 提供了 processModuleFilter 这个方法,借助它可以实现模块的过滤。具体的逻辑可见以下代码:

// metro.business.config.js

const fs = require('fs');

// 读取 idList.txt,转换为数组
const idList = fs.readFileSync('./idList.txt', 'utf8').toString().split('\n');

function createModuleId(path) {
  const projectRootPath = __dirname;
  let moduleId = path.substr(projectRootPath.length + 1);
  return moduleId;
}

module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true,
      },
    }),
  },
  serializer: {
    createModuleIdFactory: function () {
      // createModuleId 的逻辑和 metro.common.config.js 完全一样
      return createModuleId;
    },
    processModuleFilter: function (modules) {
      const mouduleId = createModuleId(modules.path);

      // 通过 mouduleId 过滤在 common.bundle 里的数据
      if (idList.indexOf(mouduleId) < 0) {
        console.log('createModuleIdFactory path', mouduleId);
        return true;
      }
      return false;
    },
  },
};

最后运行命令行命令打包即可:

# 打包平台:android
# 打包配置文件:metro.business.config.js
# 打包入口文件:index.js
# 输出路径:bundle/business.android.bundle

npx react-native bundle --platform android --config metro.business.config.js --dev false --entry-file index.js --bundle-output bundle/business.android.bundle

最后的打包结果只有 11 行(不分包的话得 398 行),可以看出分包的收益非常大。

business.android.bundle

当然使用相对路径作为 moduleId 打包时,不可避免的会导致包体积变大,我们可以使用 md5 计算一下相对路径,然后取前几位作为最后的 moduleId;或者还是采用递增 id,只不过使用更复杂的映射算法来保证 moduleId 的唯一性和稳定性。这部分的内容其实属于非常经典的 Map key 设计问题,感兴趣的读者可以了解学习一下相关的算法理论知识。

2.3.2 Native 实现多 bundle 加载

分包只是第一步,想要展示完整正确的 RN 界面,还需要做到「合」,这个「合」就是指在 Native 端实现多 bundle 的加载。

common.bundle 的加载比较容易,直接在 RN 容器初始化的时候加载就好了。容器初始化的流程上一节我已经详细介绍了,这里就不多言了。这时候问题就转换为 business.bundle 的加载问题。

React Native 不像浏览器的多 bundle 加载,直接动态生成一个 <script /> 标签插入 HTML 中就可以实现动态加载了。我们需要结合具体的 RN 容器实现来实现 business.bundle 加载的需求。这时候我们需要关注两个点:

  1. 时机:什么时候开始加载?
  2. 方法:如何加载新的 bundle?

对于第一个问题,我们的答案是 common.bundle 加载完成后再加载 business.bundle

common.bundle 加载完成后,iOS 端会发送事件名称是 RCTJavaScriptDidLoadNotification 的全局通知,Android 端则会向 ReactInstanceManager 实例中注册的所有 ReactInstanceEventListener 回调 onReactContextInitialized() 方法。我们在对应事件监听器和回调中实现业务包的加载即可。

对于第二个问题,iOS 我们可以使用 RCTCxxBridge 的 executeSourceCode 方法在当前的 RN 实例上下文中执行一段 JS 代码,以此来达到增量加载的目的。不过值得注意的是,executeSourceCode 是 RCTCxxBridge 的私有方法,需要我们用 Category 将其暴露出来。

Android 端可以使用刚刚建立好的 ReactInstanceManager 实例,通过 getCurrentReactContext() 获取到当前的 ReactContext 上下文对象,再调用上下文对象的 getCatalystInstance() 方法获取媒介实例,最终调用媒介实例的 loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously) 方法完成业务 JSBundle 的增量加载。

iOS 和 Android 的示例代码如下:

NSURL *businessBundleURI = // 业务包 URI
NSError *error = nil;
NSData *sourceData = [NSData dataWithContentsOfURL:businessBundleURI options:NSDataReadingMappedIfSafe error:&error];
if (error) { return }
[bridge.batchedBridge executeSourceCode:sourceData sync:NO]
ReactContext context = RNHost.getReactInstanceManager().getCurrentReactContext();
CatalystInstance catalyst = context.getCatalystInstance();
String fileName = "businessBundleURI"
catalyst.loadScriptFromFile(fileName, fileName, false);

本小节的示例代码都属于 demo 级别,如果想要真正接入生产环境,需要结合实际的架构和业务场景做定制。有一个 React Native 分包仓库 react-native-multibundler[13] 内容挺不错的,大家可以参考学习一下。

3.Network

rn_start_network

我们一般会在 React Component 的 componentDidMount() 执行后请求网络,从服务器获取数据,然后再改变 Component 的 state 进行数据的渲染。

网络优化是一个非常庞大非常独立的话题,有非常多的点可以优化,我这里列举几个和首屏加载相关的网络优化点:

  • DNS 缓存:提前缓存 IP 地址,跳过 DNS 寻址时间
  • 缓存复用:进入 RN 页面前,先提前请求网络数据并缓存下来,打开 RN 页面后请求网络前先检查缓存数据,如果缓存未过期,直接从本地缓存里拿数据
  • 请求合并:如果还在用 HTTP/1.1,若首屏有多个请求,可以合并多个请求为一个请求
  • HTTP2:利用 HTTP2 的并行请求和多路复用优化速度
  • 减小体积:去除接口的冗余字段,减少图片资源的体积等等
  • ......

由于网络这里相对来说比较独立,iOS/Android/Web 的优化经验其实都可以用到 RN 上,这里按照大家以往的优化经验来就可以了。

4.Render

rn_start_render

渲染这里的耗时,基本上和首屏页面的 UI 复杂度成正相关。可以通过渲染流程查看哪里会出现耗时:

  • VDOM 计算:页面复杂度越高,JavaScript 侧的计算耗时就会越长(VDOM 的生成与 Diff)
  • JS Native 通讯:JS 的计算结果会转为 JSON 通过 Bridge 传递给 Native 侧,复杂度越高,JSON 的数据量越大,有可能阻塞 Bridge 通讯
  • Native 渲染:Native 侧递归解析 render tree,布局越复杂,渲染时间越长

我们可以在代码里开启 MessageQueue 监视,看看 APP 启动后 JS Bridge 上面有有些啥:

// index.js

import MessageQueue from 'react-native/Libraries/BatchedBridge/MessageQueue'
MessageQueue.spy(true);

rn_start_MessageQueue

从图片里可以看出 JS 加载完毕后有大量和 UI 相关的 UIManager.createView()``UIManager.setChildren() 通讯,结合上面的耗时总结,我们对应着就有几条解决方案:

  • 通过一定的布局技巧降低 UI 嵌套层级,降低 UI 视图的复杂度
  • 减少 re-render,直接在 JS 侧截断重绘流程,减少 bridge 通讯的频率和数据量
  • 如果是 React Native 为主架构的 APP,首屏可以直接替换为 Native View,直接脱离 RN 的渲染流程

上面的这些技巧我都在旧文[《React Native 性能优化指南——渲染篇》] 里做了详细的解释,这里就不多解释了。

Fraic

从上面的我们可以看出,React Native 的渲染需要在 Bridge 上传递大量的 JSON 数据,在 React Native 初始化时,数据量过大会阻塞 bridge,拖慢我们的启动和渲染速度。React Native 新架构中的 Fraic 就能解决这一问题,JS 和 Native UI 不再是异步的通讯,可以实现直接的调用,可以大大加速渲染性能。

Fraic 可以说是 RN 新架构里最让人期待的了,想了解更多内容,可以去官方 issues [14]区围观。

总结

本文主要从 JavaScript 的角度出发,分析了 Hermes 引擎的特点和作用,并总结分析了 JSBundle 的各种优化手段,再结合网络和渲染优化,全方位提升 React Native 应用的启动速度。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/E2MelCFIYQnig84whD3wEA

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:8月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:8月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:8月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:8月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:8月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:8月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:8月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:8月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:8月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:8月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:8月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:8月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:8月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:8月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:8月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:8月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:8月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:8月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:8月以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  236907次阅读
vscode超好用的代码书签插件Bookmarks 1年以前  |  7112次阅读
 目录