前言
本文是基于之前的系列文章做的一个合集,精简之后整理为一篇长文供大家参考。合集的入口在此。合集内部有每种方案的详细使用手册,大家可以对照本文参考使用。
https://juejin.cn/post/7132643283083198501
登录拦截与放行是大部分App开发都会遇到的一个场景,如果你的App有游客模式,但是部分高级功能需要登录之后才能使用。那么我们就需要在用户点击这个操作的时候校验是否登录,当登录完成之后再跳转到指定的页面或弹窗。如果这些入口很多的话,那么我们就需要到处写这些逻辑。比较初级的用法是使用消息总线,当登录完成之后发送对应key消息,然后去完成对应key的事件。
有没有一种更简单的方式,集中统一方便的管理登录拦截再放行这一个场景。
下面我们一起来看一看具体的方案。
本质就是把你要拦截执行的方法作为一个对象,存入到一个方法池列表中,使用完之后再自动释放掉。(需要注意生命周期,当页面Destory的时候要主动释放)先定义方法对象:
public abstract class IFunction {
public String functionName;
public IFunction(String functionName) {
this.functionName = functionName;
}
protected abstract void function();
}
方法池:
public class FunctionManager {
private static FunctionManager functionManager;
private static HashMap<String, IFunction> mFunctionMap;
public FunctionManager() {
mFunctionMap = new HashMap<>();
}
public static FunctionManager get() {
if (functionManager == null) {
functionManager = new FunctionManager();
}
return functionManager;
}
/**
* 添加方法
*/
public FunctionManager addFunction(IFunction function) {
if (mFunctionMap != null) {
mFunctionMap.put(function.functionName, function);
}
return this;
}
/**
* 执行方法
*/
public void invokeFunction(String key) {
if (TextUtils.isEmpty(key)) {
return;
}
if (mFunctionMap != null) {
IFunction function = mFunctionMap.get(key);
if (function != null) {
function.function();
//用完移除掉
removeFunction(key);
} else {
try {
throw new RuntimeException("function not found");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 使用之后移除相关的缓存
*/
public void removeFunction(String key) {
if (mFunctionMap != null) {
mFunctionMap.remove(key);
}
}
}
使用的时候也是非常简单:
private fun checkLogin() {
if (SP().getString(Constants.KEY_TOKEN, "").checkEmpty()) {
FunctionManager.get().addFunction(object : IFunction("gotoProfilePage") {
override fun function() {
gotoProfilePage()
}
})
gotoLoginPage()
} else {
gotoProfilePage()
}
}
登录完成之后,我们需要手动调用:
//方法池的方式
FunctionManager.get().invokeFunction("gotoProfilePage")
这样就可以触发回调完成登录拦截的功能了。如果想对游客的校验也做一个封装,也可以在 FunctionManager 中定义好,可以自由扩展。
其本质是通过消息总线实现,通过管理类发送消息,接收消息,通过回调的方式去执行拦截的方法。相比前者,他的好处是不需要我们处理生命周期。
我们指定好统一的消息key之后,都通过这个key来处理登录完成的逻辑。
public class FunctionManager {
private static FunctionManager functionManager;
private static HashMap<String, Function> mFunctionMap;
public FunctionManager() {
mFunctionMap = new HashMap<>();
}
public static FunctionManager get() {
if (functionManager == null) {
functionManager = new FunctionManager();
}
return functionManager;
}
public void addLoginCallback(LifecycleOwner owner, ILoginCallback callback) {
LiveEventBus.get("login", Boolean.class).observe(owner, aBoolean -> {
if (aBoolean != null && aBoolean) {
callback.callback();
}
});
}
public interface ILoginCallback {
void callback();
}
public void finishLogin() {
LiveEventBus.get("login").post(true);
}
}
FunctionManager.get().addLoginCallback(this) {
gotoProfilePage()
}
登录完成之后,我们需要手动调用:
//方法池的方式
FunctionManager.get().finishLogin()
这样就可以触发回调完成登录拦截的功能了。
其实不使用一些容器,我们原始的使用Intent也是可以实现逻辑的。
原理是通过登录成功之后startActivity启动自己的页面,然后通过 onNewIntent 拿到对应的操作意图去执行对应的操作。
只是需要我们把原始的意图封装到启动自己的Intent中。
fun switchPage3() {
f (!LoginManager.isLogin()) {
val intent = Intent(mActivity, Demo3Activity::class.java)
intent.addCategory(switch_tab3)
gotoLoginPage(intent)
} else {
switchFragment(3)
}
}
//把原始意图当参数传递
fun gotoLoginPage(targetIntent: Intent) {
val intent = Intent(mActivity, LoginDemoActivity::class.java)
intent.putExtra("targetIntent", targetIntent)
startActivity(intent)
}
//通过这样的方式可以拿到携带的数据
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
YYLogUtils.w("收到newintent:" + intent.toString())
val categories = intent.categories
when (categories.take(1)[0]) {
switch_tab1 -> {
switchFragment(1)
}
switch_tab2 -> {
switchFragment(2)
}
switch_tab3 -> {
switchFragment(3)
}
}
}
那么在Login页面登录完成之后再启动当前页面即可把携带的数据传递回来,通过newIntent就可以做对应的操作。
如果说Intent的方案还需要我们手动的处理跳转,那么此方案就是升级版,自动的拦截跳转,之后的放行方案我们还是通过 Intent 与 onNewIntent 的回调来处理。
难点就是如何使用Hook代替Activity的启动。
public class DynamicProxyUtils {
//修改启动模式
public static void hookAms() {
try {
Field singletonField;
Class<?> iActivityManager;
// 1,获取Instrumentation中调用startActivity(,intent,)方法的对象
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 10.0以上是ActivityTaskManager中的IActivityTaskManagerSingleton
Class<?> activityTaskManagerClass = Class.forName("android.app.ActivityTaskManager");
singletonField = activityTaskManagerClass.getDeclaredField("IActivityTaskManagerSingleton");
iActivityManager = Class.forName("android.app.IActivityTaskManager");
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// 8.0,9.0在ActivityManager类中IActivityManagerSingleton
Class activityManagerClass = ActivityManager.class;
singletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
iActivityManager = Class.forName("android.app.IActivityManager");
} else {
// 8.0以下在ActivityManagerNative类中 gDefault
Class<?> activityManagerNative = Class.forName("android.app.ActivityManagerNative");
singletonField = activityManagerNative.getDeclaredField("gDefault");
iActivityManager = Class.forName("android.app.IActivityManager");
}
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
// 2,获取Singleton中的mInstance,也就是要代理的对象
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Method getMethod = singletonClass.getDeclaredMethod("get");
Object mInstance = getMethod.invoke(singleton);
if (mInstance == null) {
return;
}
//开始动态代理
Object proxy = Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManager},
new AmsHookBinderInvocationHandler(mInstance));
//现在替换掉这个对象
mInstanceField.set(singleton, proxy);
} catch (Exception e) {
e.printStackTrace();
}
}
//动态代理执行类
public static class AmsHookBinderInvocationHandler implements InvocationHandler {
private Object obj;
public AmsHookBinderInvocationHandler(Object rawIActivityManager) {
obj = rawIActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
Intent raw;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
//原始意图
raw = (Intent) args[index];
YYLogUtils.w("原始意图:" + raw);
//设置新的Intent-直接制定LoginActivity
Intent newIntent = new Intent();
String targetPackage = "com.guadou.kt_demo";
ComponentName componentName = new ComponentName(targetPackage, LoginDemoActivity.class.getName());
newIntent.setComponent(componentName);
YYLogUtils.w("改变了Activity启动");
args[index] = newIntent;
YYLogUtils.w("拦截activity的启动成功" + " --->");
return method.invoke(obj, args);
}
//如果不是拦截的startActivity方法,就直接放行
return method.invoke(obj, args);
}
}
}
使用的时候我们需要启动代理,在跳转页面的时候就会自动拦截了。
mBtnProfile.click {
//启动动态代理
DynamicProxyUtils.hookAms()
gotoActivity<ProfileDemoActivity>()
}
之后的逻辑和上面的Intent方案是一样的回调处理,走 onNewIntent 里面处理。
目前的Hook只兼容到Android12。还没有看13的源码不知道有没有变动。并且此方案只能适用于页面的跳转,有些场景比如切换Tab、ViewPager的情况下,是无法实现拦截的。
如果不想全部的页面都拦截,大家也可以自行实现白名单的管理,只拦截部分的页面。
但相对其他方案来说其实不是很好用,这样的自动感觉还不如全手动的Intent灵活。
相对其他的方案,此方案的思路就比较清奇,利用线程的等待与恢复来实现,当我们跳转到登录页面的时候我们让线程等待,然后等待登录完成之后我们再恢复等待。
/**
* 登录拦截的线程管理
*/
public class LoginInterceptThreadManager {
private static LoginInterceptThreadManager threadManager;
private static final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
private static final Handler mHandler = new Handler();
private LoginInterceptThreadManager() {
}
public static LoginInterceptThreadManager get() {
if (threadManager == null) {
threadManager = new LoginInterceptThreadManager();
}
return threadManager;
}
/**
* 检查是否需要登录
*/
public void checkLogin(Runnable nextRunnable, Runnable loginRunnable) {
if (LoginManager.isLogin()) {
//已经登录
mHandler.post(nextRunnable);
return;
}
//如果没有登录-先去登录页面
mHandler.post(loginRunnable);
singleThreadExecutor.execute(() -> {
try {
YYLogUtils.w("开始运行-停止");
synchronized (singleThreadExecutor) {
singleThreadExecutor.wait();
YYLogUtils.w("等待notifyAll完成了,继续执行");
if (LoginManager.isLogin()) {
mHandler.post(nextRunnable);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
public void loginFinished() {
if (mHandler == null) return;
if (singleThreadExecutor == null) return;
synchronized (singleThreadExecutor) {
singleThreadExecutor.notifyAll();
}
}
}
使用的时候也简单:
private fun checkLogin() {
LoginInterceptThreadManager.get().checkLogin( {
gotoProfilePage()
}, {
gotoLoginPage()
})
}
private fun gotoLoginPage() {
gotoActivity<LoginDemoActivity>()
}
private fun gotoProfilePage() {
gotoActivity<ProfileDemoActivity>()
}
登录完成之后,我们需要手动调用:
//方法池的方式
oginInterceptThreadManager.get().loginFinished()
这样就可以触发回调完成登录拦截的功能了。
既然线程都可以,没道理协程不能使用这样的方案,协程也可以使用等待恢复的方案,还能使用协程通信的方案,开启两个协程,然后当登录完成之后去通知其中的接收协程去继续执行。
class LoginInterceptCoroutinesManager private constructor() : DefaultLifecycleObserver, CoroutineScope by MainScope() {
companion object {
private var instance: LoginInterceptCoroutinesManager? = null
get() {
if (field == null) {
field = LoginInterceptCoroutinesManager()
}
return field
}
fun get(): LoginInterceptCoroutinesManager {
return instance!!
}
}
private lateinit var mCancellableContinuation: CancellableContinuation<Boolean>
fun checkLogin(loginAction: () -> Unit, nextAction: () -> Unit) {
launch {
if (LoginManager.isLogin()) {
nextAction()
return@launch
}
loginAction()
val isLogin = suspendCancellableCoroutine<Boolean> {
mCancellableContinuation = it
YYLogUtils.w("暂停协程,等待唤醒")
}
YYLogUtils.w("已经恢复协程,继续执行")
if (isLogin) {
nextAction()
}
}
}
fun loginFinished() {
if (!this@LoginInterceptCoroutinesManager::mCancellableContinuation.isInitialized) return
if (mCancellableContinuation.isCancelled) return
mCancellableContinuation.resume(LoginManager.isLogin(), null)
}
override fun onDestroy(owner: LifecycleOwner) {
YYLogUtils.w("LoginInterceptCoroutinesManager - onDestroy")
mCancellableContinuation.cancel()
cancel()
}
}
使用也比较简单:
//协程的方式
mBtnProfile2.click {
LoginInterceptCoroutinesManager.get().checkLogin(loginAction = {
gotoLoginPage()
}, nextAction = {
gotoProfilePage()
})
}
登录完成之后,我们需要手动调用:
//方法池的方式
oginInterceptThreadManager.get().loginFinished()
这样就可以触发回调完成登录拦截的功能了。协程另一种方案就是通知的方式:
class LoginInterceptCoroutinesManager private constructor() : DefaultLifecycleObserver, CoroutineScope by MainScope() {
companion object {
private var instance: LoginInterceptCoroutinesManager? = null
get() {
if (field == null) {
field = LoginInterceptCoroutinesManager()
}
return field
}
fun get(): LoginInterceptCoroutinesManager {
return instance!!
}
}
private val channel = Channel<Boolean>()
fun checkLogin(loginAction: () -> Unit, nextAction: () -> Unit) {
launch {
if (LoginManager.isLogin()) {
nextAction()
return@launch
}
loginAction()
val isLogin = channel.receive()
YYLogUtils.w("收到消息:" + isLogin)
if (isLogin) {
nextAction()
}
}
}
fun loginFinished() {
launch {
async {
YYLogUtils.w("发送消息:" + LoginManager.isLogin())
channel.send(LoginManager.isLogin())
}
}
}
override fun onDestroy(owner: LifecycleOwner) {
cancel()
}
}
使用起来和暂停恢复的方案是一样样的。
除了这些方案之外,网上比较流行的就是面向切面AOP的方案。
需要我们集成 AspectJ 框架来实现。
使用的时候就需要定义一个自定义的注解,然后围绕这个注解做一些操作。
//不需要回调的处理
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
}
除了注解的类
@Aspect
public class LoginAspect {
@Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.Login)")
public void Login() {
}
@Pointcut("@annotation(com.guadou.kt_demo.demo.demo3_bottomtabbar_fragment.aop.LoginCallback)")
public void LoginCallback() {
}
//带回调的注解处理
@Around("LoginCallback()")
public void loginCallbackJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
YYLogUtils.w("走进AOP方法-LoginCallback()");
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature)){
throw new RuntimeException("该注解只能用于方法上");
}
LoginCallback loginCallback = ((MethodSignature) signature).getMethod().getAnnotation(LoginCallback.class);
if (loginCallback == null) return;
//判断当前是否已经登录
if (LoginManager.isLogin()) {
joinPoint.proceed();
} else {
LifecycleOwner lifecycleOwner = (LifecycleOwner) joinPoint.getTarget();
LiveEventBus.get("login").observe(lifecycleOwner, new Observer<Object>() {
@Override
public void onChanged(Object integer) {
try {
joinPoint.proceed();
LiveEventBus.get("login").removeObserver(this);
} catch (Throwable throwable) {
throwable.printStackTrace();
LiveEventBus.get("login").removeObserver(this);
}
}
});
LoginManager.gotoLoginPage();
}
}
//不带回调的注解处理
@Around("Login()")
public void loginJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
YYLogUtils.w("走进AOP方法-Login()");
Signature signature = joinPoint.getSignature();
if (!(signature instanceof MethodSignature)){
throw new RuntimeException("该注解只能用于方法上");
}
Login login = ((MethodSignature) signature).getMethod().getAnnotation(Login.class);
if (login == null) return;
//判断当前是否已经登录
if (LoginManager.isLogin()) {
joinPoint.proceed();
} else {
//如果未登录,去登录页面
LoginManager.gotoLoginPage();
}
}
}
定义一个工具类来定义一些固定的方法:
object LoginManager {
@JvmStatic
fun isLogin(): Boolean {
val token = SP().getString(Constants.KEY_TOKEN, "")
YYLogUtils.w("LoginManager-token:$token")
val checkEmpty = token.checkEmpty()
return !checkEmpty
}
@JvmStatic
fun gotoLoginPage() {
commContext().gotoActivity<LoginDemoActivity>()
}
}
到这里我们就能使用AOP来拦截了。我们把需要拦截的方法使用我们的自定义注解来标记。然后我们的处理器就会对这个注解做一些围绕的操作。
override fun init() {
mBtnCleanToken.click {
SP().remove(Constants.KEY_TOKEN)
toast("清除成功")
}
mBtnProfile.click {
//不带回调的登录方式
gotoProfilePage2()
}
}
@Login
private fun gotoProfilePage2() {
gotoActivity<ProfileDemoActivity>()
}
可以看到内部也是通过消息总线来执行继续操作的逻辑的,我们需要在登录完成之后发送这个通知才行。
最后一种方案是基于责任链模式的改版,自定义拦截器实现的,和默认的责任链是有些差异的。其中没有用到参数的传递。
原理是我们定义2层拦截,一个是校验登录,一个是执行逻辑。当我们校验登录不通过的时候就会跳转到登录页面,当登录完成之后,我们继续拦截器就会走到执行逻辑。间接的完成一个登录拦截的功能。
拦截器的定义:
object LoginInterceptChain {
private var index: Int = 0
private val interceptors by lazy(LazyThreadSafetyMode.NONE) {
ArrayList<Interceptor>(2)
}
//默认初始化Login的拦截器
private val loginIntercept = LoginInterceptor()
// 执行拦截器。
fun process() {
if (interceptors.isEmpty()) return
when (index) {
in interceptors.indices -> {
val interceptor = interceptors[index]
index++
interceptor.intercept(this)
}
interceptors.size -> {
clearAllInterceptors()
}
}
}
// 添加一个拦截器。
fun addInterceptor(interceptor: Interceptor): LoginInterceptChain {
//默认添加Login判断的拦截器
if (!interceptors.contains(loginIntercept)) {
interceptors.add(loginIntercept)
}
if (!interceptors.contains(interceptor)) {
interceptors.add(interceptor)
}
return this
}
//放行登录判断拦截器
fun loginFinished() {
if (interceptors.contains(loginIntercept) && interceptors.size > 1) {
loginIntercept.loginfinished()
}
}
//清除全部的拦截器
private fun clearAllInterceptors() {
index = 0
interceptors.clear()
}
}
校验登录的拦截器:
/**
* 判断是否登录的拦截器
*/
class LoginInterceptor : BaseLoginInterceptImpl() {
override fun intercept(chain: LoginInterceptChain) {
super.intercept(chain)
if (LoginManager.isLogin()) {
//如果已经登录 -> 放行, 转交给下一个拦截器
chain.process()
} else {
//如果未登录 -> 去登录页面
LoginDemoActivity.startInstance()
}
}
fun loginfinished() {
//如果登录完成,调用方法放行到下一个拦截器
mChain?.process()
}
}
继续执行的拦截器:
/**
* 登录完成下一步的拦截器
*/
class LoginNextInterceptor(private val action: () -> Unit) : BaseLoginInterceptImpl() {
override fun intercept(chain: LoginInterceptChain) {
super.intercept(chain)
if (LoginManager.isLogin()) {
//如果已经登录执行当前的任务
action()
}
mChain?.process()
}
}
使用的时候我们使用拦截器管理即可。
private fun checkLogin() {
LoginInterceptChain.addInterceptor(LoginNextInterceptor {
gotoProfilePage()
}).process()
}
登录完成之后记得手动放行哦。
//拦截器放行
LoginInterceptChain.loginFinished()
这样就完成了登录拦截的功能了。下面放一张效果图,其实几种方案的效果都是差不多的:
本文是一个总纲或者说是总结,这里的几种方法我都只是简单的介绍了一下,具体的使用可以看看单独的文章,每一篇具体使用的方式之前都已经出了对应的文章,并附带了Demo,有兴趣的朋友可以前往查看。
https://juejin.cn/post/7132643283083198501
总的来说实现这种方式推荐大家使用简单易于理解和集成使用的方式。例如方法池,消息通知回调,线程协程的方案,自定义拦截的方案其实都是不错的,大家自己按需选择即可。
除开一些集成困难,有兼容性的一些方案之外,其他的这些方案都是可以用的了,剩下的我们需要考虑的就是,此方案是否有更大的内存开销,是否有内存泄露风险,需要处理页面意外关闭的情况吗?有没有降级或兜底的方案?有没有崩溃的风险?有没有重复调用的风险?等等等等。
本文也只是基于Demo的实现,如果正式在生产上面使用的话,大家可以自行扩展一下它的健壮性。
本文全部代码均以开源,源码在此。大家可以点个Star关注一波,有问题我会及时更新。
https://gitee.com/newki123456/Kotlin-Room
好了,本期内容如有错漏的地方,希望同学们可以指出交流。如果有更好的方法,也欢迎大家评论区讨论。
如果感觉本文对你有一点点的启发,还望你能点赞支持一下,你的支持是我最大的动力。
Ok,这一期就此完结。
本文由哈喽比特于1年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/WS8jhOVBS_j9xT8db5KL0A
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为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 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。