上一篇介绍了移动端开发的相关技术,这一篇主要是从 Hybrid 开发的 JS Bridge 通信讲起。
顾名思义,JS Bridge 的意思就是桥,也就是连接 JS 和 Native 的桥梁,它也是 Hybrid App 里面的核心。一般分为 JS 调用 Native 和 Native 主动调用 JS 两种形式。
URL Scheme 是一种特殊的 URL,一般用于在 Web 端唤醒 App,甚至跳转到 App 的某个页面,比如在某个手机网站上付款的时候,可以直接拉起支付宝支付页面。
这里有个小例子,你可以在浏览器里面直接输入 weixin://
,系统就会提示你是否要打开微信。输入 mqq://
就会帮你唤起手机 QQ。
这里有个常用 App URL Scheme 汇总:URL Schemes 收集整理
在手机里面打开这个页面后点击这里,就会提示你是否要打开微信。
我们常说的 Deeplink 一般也是基于 URL Scheme 来实现的。一个 URI 的组成结构如下:
URI = scheme:[//authority]path[?query][#fragment]
// scheme = http
// authority = www.baidu.com
// path = /link
// query = url=xxxxx
authority = [userinfo@]host[:port]
除了 http/https 这两个常见的协议,还可以自定义协议。借用维基百科的一张图:
通常情况下,App 安装后会在手机系统上注册一个 Scheme,比如 weixin://
这种,所以我们在手机浏览器里面访问这个 scheme 地址,系统就会唤起我们的 App。
一般在 Android 里面需要到 AndroidManifest.xml 文件中去注册 Scheme:
<activity
android:name=".login.dispatch.DispatchActivity"
android:launchMode="singleTask"
android:theme="@style/AppDispatchTheme">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="taobao" />
<data android:host="xxx" />
<data android:path="/goods" />
</intent-filter>
</activity>
在 iOS 中需要在 Xcode 里面注册,有一些已经是系统使用的不应该使用,比如 Maps、YouTube、Music。
具体可以参考苹果开发者官网文档:Defining a Custom URL Scheme for Your App
在 iOS 里面又需要区分 UIWebView 和 WKWebView 两种 WebView:
WKWebView 是 iOS8 之后出现的,目的是取代笨重的 UIWebView,它占用内存更少,大概是 UIWebView 的 1/3,支持更好的 HTML5 特性,性能更加强大。
但也有一些缺点,比如不支持缓存,需要自己注入 Cookie,发送 POST 请求的时候带不了参数,拦截 POST 请求的时候无法解析参数等等。
「题外话(小小吐槽一下)」
我们这边有很多场景是需要客户端和银行交互的,为了从银行 H5 回调到我们的客户端,常常是会让银行跳到我们的 H5 中间页,客户端去拦截并解析银行带来的参数。
以前有些页面是 Python 服务端渲染做的,支持 POST 请求打开页面。后来 iOS 客户端升级 WKWebView,导致无法获取银行 POST 带过来的参数。
为了配合客户端发版上线,曾经连夜修改了我们 App 里面所有相关的绑卡和支付 H5 页面,还好没有搞出重大线上事故。
JS 调用 Native 通信大致有三种方法:
这三种方式总体上各有利弊,下面会一一介绍。
仔细思考一下,如果是 JS 和 Java 之间传递数据,我们该怎么做呢?
对于前端开发来说,调 Ajax 请求接口是最常见的需求了。不管对方是 Java 还是 Python,我们都可以通过 http/https 接口来获取数据。实际上这个流程和 JSONP 更加类似。
已知客户端是可以拦截请求的,那么可不可以在这个上面做文章呢?
如果我们请求一个不存在的地址,上面带了一些参数,通过参数告诉客户端我们需要调用的功能呢?
比如我要调用扫码功能:
axios.get('http://xxxx?func=scan&callback_id=yyyy')
客户端可以拦截这个请求,去解析参数上面的 func
来判断当前需要调起哪个功能。客户端调起扫码功能之后,会获取 WebView 上面的 callbacks 对象,根据 callback_id 回调它。
所以基于上面的例子,我们可以把域名和路径当做通信标识,参数里面的 func 当做指令,callback_id 当做回调函数,其他参数当做数据传递。对于不满足条件的 http 请求不应该拦截。
当然了,现在主流的方式是前面我们看到的自定义 Scheme 协议,以这个为通信标识,域名和路径当做指令。
这种方式的好处就是 iOS6 以前只支持这种方式,兼容性比较好。
我们有很多种方法可以发起请求,目前使用最广泛的是 iframe 跳转:
<a href="taobao://">点击我打开淘宝</a>
location.href = "taobao://"
const iframe = document.createElement("iframe");
iframe.src = "taobao://"
iframe.style.display = "none"
document.body.appendChild(iframe)
在 Android 侧可以用 shouldOverrideUrlLoading
来拦截 url 请求。
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith("taobao")) {
// 拿到调用路径后解析调用的指令和参数,根据这些去调用 Native 方法
return true;
}
}
在 iOS 侧需要区分 UIWebView 和 WKWebView 两种方式。在 UIWebView 中:
- (BOOL)shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(BPWebViewNavigationType)navigationType
{
if (xxx) {
// 拿到调用路径后解析调用的指令和参数,根据这些去调用 Native 方法
return NO;
}
return [super shouldStartLoadWithRequest:request navigationType:navigationType];
}
在 WKWebView 中:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(nonnull WKNavigationAction *)navigationAction decisionHandler:(nonnull void (^)(WKNavigationActionPolicy))decisionHandler
{
if(xxx) {
// 拿到调用路径后解析调用的指令和参数,根据这些去调用 Native 方法
BLOCK_EXEC(decisionHandler, WKNavigationActionPolicyCancel);
} else {
BLOCK_EXEC(decisionHandler, WKNavigationActionPolicyAllow);
}
[self.webView.URLLoader webView:webView decidedPolicy:policy forNavigationAction:navigationAction];
}
目前不建议只使用拦截 URL Scheme 解析参数的形式,主要存在几个问题。
1.连续续调用 location.href
会出现消息丢失,因为 WebView 限制了连续跳转,会过滤掉后续的请求。
2.URL 会有长度限制,一旦过长就会出现信息丢失 因此,类似 WebViewJavaScriptBridge 这类库,就结合了注入 API 的形式一起使用,这也是我们这边目前使用的方式,后面会介绍一下。
这种方式是利用弹窗会触发 WebView 相应事件来拦截的。
一般是在 setWebChromeClient
里面的 onJsAlert
、onJsConfirm
、onJsPrompt
方法拦截并解析他们传来的消息。
// 拦截 Prompt
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
if (xxx) {
// 解析 message 的值,调用对应方法
}
return super.onJsPrompt(view, url, message, defaultValue, result);
}
// 拦截 Confirm
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
return super.onJsConfirm(view, url, message, result);
}
// 拦截 Alert
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
return super.onJsAlert(view, url, message, result);
}
我们以 WKWebView 为例:
+ (void)webViewRunJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
defaultText:(NSString *)defaultText
completionHandler:(void (^)(NSString * _Nullable))completionHandler
{
/** Triggered by JS:
var person = prompt("Please enter your name", "Harry Potter");
if (person == null || person == "") {
txt = "User cancelled the prompt.";
} else {
txt = "Hello " + person + "! How are you today?";
}
*/
if (xxx) {
BLOCK_EXEC(completionHandler, text);
} else {
BLOCK_EXEC(completionHandler, nil);
}
}
这种方式的缺点就是在 iOS 上面 UIWebView 不支持,虽然 WKWebView 支持,但它又有更好的 scriptMessageHandler
,比较尴尬。
前面我们有讲过在 iOS 中内置了 JavaScriptCore 这个框架,可以实现执行 JS 以及注入 Native 对象等功能。
这种方式不依赖拦截,主要是通过 WebView 向 JS 的上下文注入对象和方法,可以让 JS 直接调用原生。
PS:iOS 中的 Block 是 OC 对于闭包的实现,它本质上是个对象,定义 JS 里面的函数。
iOS 侧代码:
// 获取 JS 上下文
JSContext *context = [webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
// 注入 Block
context[@"callHandler"] = ^(JSValue * data) {
// 处理调用方法和参数
// 调用 Native 功能
// 回调 JS Callback
}
JS 代码:
window.callHandler(JSON.stringify({
type: "scan",
data: "",
callback: function(data) {
}
}));
这种方式的牛逼之处在于,JS 调用是同步的,可以立马拿到返回值。
我们也不再需要像拦截方式一样,每次传值都要把对象做 JSON.stringify
,可以直接传 JSON 过去,也支持直接传一个函数过去。
WKWebView 里面通过 addScriptMessageHandler
来注入对象到 JS 上下文,可以在 WebView 销毁的时候调用 removeScriptMessageHandler
来销毁这个对象。
前端调用注入的原生方法之后,可以通过 didReceiveScriptMessage
来接收前端传过来的参数。
WKWebView *wkWebView = [[WKWebView alloc] init];
WKWebViewConfiguration *configuration = wkWebView.configuration;
WKUserContentController *userCC = configuration.userContentController;
// 注入对象
[userCC addScriptMessageHandler:self name:@"nativeObj"];
// 清除对象
[userCC removeScriptMessageHandler:self name:@"nativeObj"];
// 客户端处理前端调用
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
// 获取前端传来的参数
NSDictionary *msgBody = message.body;
// 如果是 nativeObj 就进行相应处理
if (![message.name isEqualToString:@"nativeObj"]) {
//
return;
}
}
使用 addScriptMessageHandler
注入的对象实际上只有一个 postMessage
方法,无法调用更多自定义方法。前端的调用方式如下:
window.webkit.messageHandlers.nativeObj.postMessage(data);
需要注意的是,这种方式要求 iOS8 及以上,而且返回不是同步的。和 UIWebView 一样的是,也支持直接传 JSON 对象,不需要 stringify。
安卓4.2之前注入 JS 一般是使用 addJavascriptInterface
,和前面的 addScriptMessageHandler
有一些类似,但又没有它的限制。
public void addJavascriptInterface() {
mWebView.addJavascriptInterface(new DatePickerJSBridge(), "DatePickerBridge");
}
private class PickerJSBridge {
public void _pick(...) {
}
}
在 JS 里面调用:
window.DatePickerBridge._pick(...)
但这种方案有一定风险,可以参考这篇文章:WebView中接口隐患与手机挂马利用
在 Android4.2 之后提供了 @JavascriptInterface
注解,暴露给 JS 的方法必须要带上这个。
所以前面的 _pick
方法需要带上这个注解。
private class PickerJSBridge {
@JavascriptInterface
public void _pick(...) {
}
}
Native 调用 JS 一般就是直接 JS 代码字符串,有些类似我们调用 JS 中的 eval
去执行一串代码。一般有 loadUrl
、evaluateJavascript
等几种方法,这里逐一介绍。
但是不管哪种方式,客户端都只能拿到挂载到 window
对象上面的属性和方法。
在 Android 里面需要区分版本,在安卓4.4之前的版本支持 loadUrl,使用方式类似我们在 a 标签的 href
里面写 JS 脚本一样,都是javascript:xxx
的形式。
这种方式无法直接获取返回值。
webView.loadUrl("javascript:foo()")
在安卓4.4以上的版本一般使用 evaluateJavascript
这个 API 来调用。这里需要判断一下版本。
if (Build.VERSION.SDK_INT > 19) //see what wrapper we have
{
webView.evaluateJavascript("javascript:foo()", null);
} else {
webView.loadUrl("javascript:foo()");
}
在 iOS 的 UIWebView 里面使用 stringByEvaluatingJavaScriptFromString
来调用 JS 代码。这种方式是同步的,会阻塞线程。
results = [self.webView stringByEvaluatingJavaScriptFromString:"foo()"];
WKWebView 可以使用 evaluateJavaScript
方法来调用 JS 代码。
[self.webView evaluateJavaScript:@"document.body.offsetHeight;" completionHandler:^(id _Nullable response, NSError * _Nullable error) {
// 获取返回值 response
}];
前面讲完了 JS 和 Native 互调的所有方法,这里来介绍一下我们这边 JS Bridge 的设计吧。
我们这边的 JS Bridge 通信是基于 WebViewJavascriptBridge 这个库来实现的。
主要是结合 Scheme 协议+上下文注入来做。考虑到 Android 和 iOS 不一样的通信方式,这里进行了封装,保证提供给外部的 API 一致。
具体功能的调用我们封装成了 npm 包,下面的是几个基础 API:
pageDidBack
这种场景。那么这几个 API 又是如何实现的呢?这里 Android 和 iOS 封装不一致,应当分开来说。
前面我们有说过安卓可以通过 @JavascriptInterface
注解来将对象和方法暴露给 JS。
所以这里的几个方法都是通过注解暴露给 JS 来调用的,在 JS 层面做了一些兼容处理。
首先最简单的是这个 hasHandler
,就是在客户端里面维护一张表(其实我们是写死的),里面有支持的 Bridge 模块信息,只需要用 switch...case
判断一下就行了。
@JavascriptInterface
public boolean hasHandler(String cmd) {
switch (cmd) {
case xxx:
case yyy:
case zzz:
return true;
}
return false;
}
然后我们来看 callHandler
这个方法,它是提供 JS 调用 Native 功能的方法。在调用这个方法之前,我们一般需要先判断一下 Native 是否支持这个功能。
function callHandler(name, params, callback) {
if (!window.WebViewJavascriptBridge.hasHandler(name)) {
}
}
如果 Native 没有支持这个 Bridge,我们就需要对回调进行兼容性处理。这个兼容性处理包括两个方面,一个是功能方面,一个是 callback 的默认回参。
比如我们调用 Native 的弹窗功能,如果客户端没支持这个 Bridge,或者我们是在浏览器里面打开的这个页面,此时应该降级到使用 Web 的 alert
弹窗。
对于 callback,我们可以默认给传个 0,表示当前不支持这个功能。
假设这个 alert
的 bridge 接收两个参数,分别是 title
和 content
,那么此时就应该使用浏览器自带的 alert
展示出来。
function fallback(params, callback) {
let content = `${params.title}\n{params.content}`
window.alert(content);
callback && callback(0)
}
这个 fallback
函数我们希望能够更加通用,每个调用方法都应该有自己的 fallback
函数,所以前面的 callHandler
应该设计成这样:
function callHandler(name, params, fallback) {
return function(...rest, callback) {
const paramsList = {};
for (let i = 0; i < params.length; i++) {
paramsList[params] = rest[i];
}
if (!callback) {
callback = function(result) {};
}
if (fallback && !window.WebViewJavascriptBridge.hasHandler(name))) {
fallback(paramsList, callback);
} else {
window.WebViewJavascriptBridge.callHandler(name, params, callback);
}
}
}
我们可以基于这个函数封装一些功能方法,比如前面的 alert:
function fallback(params, callback) {
let content = `${params.title}\n{params.content}`
window.alert(content);
callback && callback(0)
}
function alert(
title,
content,
cb: any
) {
return callHandler(
'alert',
['title', 'content'],
fallback
)(title, content, cb);
}
alert(`this is title`, `hahaha`, function() {
console.log('success')
})
具体效果类似下面这种,这是从 Google 上随便找的一张图(侵删):
那么客户端又如何实现回调 callback 函数的呢?前面说过,客户端想调用 JS 方法,只能调用挂载到 window
对象上面的。
因此,这里使用了一种很巧妙的方法,实际上 callback 函数依然是 JS 执行的。
在调用 Native 之前,我们可以先将 callback 函数和一个 uniqueId 映射起来,然后存在 JS 本地。我们只需要将 callbackId 传给 Native 就行了。
function callHandler(name, data, callback) {
const id = `cb_${uniqueId++}_${new Date().getTime()}`;
callbacks[id] = callback;
window.bridge.send(name, JSON.stringify(data), callbackId)
}
在客户端这里,当 send 方法接收到参数之后,会执行相应功能,然后使用 webView.loadUrl
主动调用前端的一个接收函数。
@JavascriptInterface
public void send(final String cmd, String data, final String callbackId) {
// 获取数据,根据 cmd 来调用对应功能
// 调用结束后,回调前端 callback
String js = String.format("javascript: window.bridge.onReceive(\'%1$s\', \'%2$s\');", callbackId, result.toDataString());
webView.loadUrl(js);
}
所以 JS 需要事前定义好这个 onReceive
方法,它接收一个 callbackId 和一个 result。
window.bridge.onReceive = function(callbackId, result) {
let params = {};
try {
params = JSON.parse(result)
} catch (err) {
//
}
if (callbackId) {
const callback = callbacks[callbackId];
callback(params)
delete callbacks[callbackId];
}
}
大致流程如下:
注册的流程比较简单,也是我们把 callback 函数事先存到一个 messageHandler
对象里面,不过这次的 key 不再是一个随机的 id,而是 name
。
function registerHandler(handlerName, callback) {
if (!messageHandlers[handlerName]) {
messageHandlers[handlerName] = [handler];
} else {
// 支持注册多个 handler
messageHandlers[handlerName].push(handler);
}
}
// 检查是否有这个注册可以直接检查 messageHandlers 里面是否有
function hasRegisteredHandler(handlerName) {
let has = false;
try {
has = !!messageHandlers[handlerName];
} catch (exception) {}
return has;
}
这里不像 callHandler
需要主动调用 window.bridge.send
去通知客户端,只需要等客户端到了相应的时机来调用 window.bridge.onReceive
就行了。
所以这里还需要改造一下 onReceive
方法。由于不再会有 callbackId 了,所以客户端可以传个空值,然后将 handlerName
放到 result 里面。
window.bridge.onReceive = function(callbackId, result) {
let params = {};
try {
params = JSON.parse(result)
} catch (err) {
//
}
if (callbackId) {
const callback = callbacks[callbackId];
callback(params)
delete callbacks[callbackId];
} else if (params.handlerName)(
// 可能注册了多个
const handlers = messageHandlers[params.handlerName];
for (let i = 0; i < handlers.length; i++) {
try {
delete params.handlerName;
handlers[i](params);
} catch (exception) {
}
}
)
}
这种情况下的流程如下,可以发现完全不需要 JS 调用 Native:
讲完了 Android,我们再来讲讲 iOS,原本 iOS 可以和 Android 设计一致,可是由于种种原因导致有不少差异。
iOS 和 Android 中最显著的差异就在于这个 window.bridge.send
方法的实现,Android 里面是直接调用 Native 的方法,iOS 中是通过 URL Scheme 的形式调用。
协议依然是 WebViewJavaScriptBridge 里面的协议,URL Scheme 本身不会传递数据,只是告诉 Native 有新的调用。
然后 Native 会去调用 JS 的方法,获取队列里面所有需要执行的方法。
所以我们需要事先创建好一个 iframe,插入到 DOM 里面,方便后续使用。
const CUSTOM_PROTOCOL_SCHEME = 'wvjbscheme';
const QUEUE_HAS_MESSAGE = '__WVJB_QUEUE_MESSAGE__';
function _createQueueReadyIframe(doc) {
messagingIframe = doc.createElement('iframe');
messagingIframe.style.display = 'none';
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
doc.documentElement.appendChild(messagingIframe);
}
每次调用的时候只需要复用这个 iframe 就行了。这里是处理 callback 并通知 Native 的代码:
function callHandler(handlerName, data, responseCallback) {
_doSend({ handlerName: handlerName, data: data }, responseCallback);
}
function _doSend(
message,
callback
) {
if (responseCallback) {
const callbackId = `cb_${uniqueId++}_${new Date().getTime()}`;
callbacks[callbackId] = callback;
message['callbackId'] = callbackId;
}
sendMessageQueue.push(message);
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
通知 Native 之后,它怎么拿到我们的 handlerName
和 data
呢?我们可以实现一个 fetchQueue
的方法。
function _fetchQueue() {
const messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
然后将其挂载到 window.WebViewJavascriptBridge
对象上面。
window.WebViewJavascriptBridge = {
_fetchQueue: _fetchQueue
};
这样 iOS 就可以使用 evaluateJavaScript
轻松拿到这个 messageQueue
。
- (void)flushMessageQueue
{
[_webView evaluateJavaScript:@"WebViewJavascriptBridge._fetchQueue();" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
[self _flushMessageQueue:result];
}];
}
- (void)_flushMessageQueue:(id)messageQueueObj
{
// 解析 messageQueueString
// 根据传入的 handlerName 执行对应操作
}
那么 iOS 又是如何回调 JS 的 callback 函数呢?这个其实和 Android 的 onReceive
是同样的原理。
这里可以实现一个 _handleMessageFromObjC
方法,同样挂载到 window.WebViewJavascriptBridge
对象上面,等待 iOS 回调。
function _dispatchMessageFromObjC(messageJSON) {
const message = JSON.parse(messageJSON);
if (message.responseId) {
var responseCallback = callbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete callbacks[message.responseId];
}
}
流程如下图:
registerHandler 和 Android 原理是一模一样的,都是提前注册一个事件,等待 iOS 调用,具体就不多讲了,这里直接放代码:
// 注册
function registerHandler(handlerName, handler) {
if (typeof messageHandlers[handlerName] === 'undefined') {
messageHandlers[handlerName] = [handler];
} else {
messageHandlers[handlerName].push(handler);
}
}
// 回调
function _dispatchMessageFromObjC(messageJSON) {
const message = JSON.parse(messageJSON);
if (message.responseId) {
var responseCallback = callbacks[message.responseId];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete callbacks[message.responseId];
} else if (message.handlerName){
handlers = messageHandlers[message.handlerName];
for (let i = 0; i < handlers.length; i++) {
try {
handlers[i](message.data, responseCallback);
} catch (exception) {
}
}
}
}
这些就是 Hybrid 里面 JS 和 Native 交互的大致原理,忽略了不少细节,比如初始化 WebViewJavascriptBridge
对象等等,感兴趣的也可以参考一下这个库:JsBridge
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/T78J3dM9i5TvLu0h6joX3A
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为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 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。