范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Android开发从设计者角度看Retrofit原理

  作者:Bezier前言
  通常我不喜欢去写分析源码类的文章,流水线式的分析 枯燥乏味,但读完Retrofit源码后让我有了改变这种想法的冲动~~
  一般来讲读源码的好处有两点: 熟悉代码设计流程,使用过程碰到问题可以更快速解决。说实话仅这一点无法激起我读源码的兴趣,毕竟以正确的姿态使用一个优秀的框架不应该出现这种问题。 一个优秀的框架必须要保证易用性、扩展性,所以作者定会引入大量的思考进行设计,如若我们能吸收一二,那何尝不是与作者进行了一次心灵交互呢!
  今天我将带着我的理解,尝试从设计者的角度分析Retrofit原理,相信你认真读完再加以思考,当再被面试官问Retrofit时你的答复或许会让他眼前一亮
  提示:Retrofit基于2.9.0。文中贴的源码可能会有部分缺失,这是我刻意为之,目的在于筛选掉无用信息增强可读性  目录1. 什么是REST ful API? 2. 为什么将请求设置为(接口+注解)形式? 2.1. 迪米特法则和门面模式 2.2. 为什么通过门面模式设计ApiService? 3. 动态代理其实不是工具 3.1. Retrofit构建 3.2. 何为动态代理? 3.3. 动态代理获取ApiService 4. ReturnT、ResponseT做一次适配的意义何在? 4.1 创建HttpServiceMethod 4.2 如何管理callAdapter、responseConverter? 4.3 发起请求 1. 什么是REST ful API?
  一句话概括REST ful API: 在我们使用HTTP协议做数据传输时应当遵守HTTP的规矩,包括请求方法、资源类型、Uri格式等等..
  不久前在群里看到某小伙伴提出一个问题:"应后端要求需要在 GET 请求加入Body 但Retrofit 中GET 请求添加Body会报错,如何解决?" 一时间讨论的好不热闹,有让把Body 塞到Header里的,有让自定义拦截器、也有人直接怂恿改源码...但问题的本质不是后端先违反规则在先吗?两个人打架总不能把挨打的抓起来吧。
  俗话说无规矩不成方圆,面对以上这种情况应当让错误方去修改,因为所有人都知道GET没有Body,否则一旦其他人接手你的代码很容易被搞懵。
  Retrofit对REST ful API的兼容做的很优秀,不符合规范直接给你报错,强行规范你的代码。所以你们公司正在使用REST ful API而Retrofit将是你的不二选择 2. 为什么将请求设置为(接口+注解)形式?
  该小节为前置知识 2.1 迪米特法则和门面模式
  迪米特法则: 也称之为最小知道原则,即模块之间尽量减少不必要的依赖,即降低模块间的耦合性。
  门面模式: 基于迪米特法则拓展出来的一种设计模式,旨在将复杂的模块/系统访问入口控制的更加单一。举个例子:现要做一个获取图片功能,优先从本地缓存获取,没有缓存从网络获取随后再加入到本地缓存,假如不做任何处理,那每获取一张图片都要写一遍缓存逻辑,写的越多出错的可能就越高,其实调用者只是想获取一张图片而已,具体如何获取他不需要关心。此时可以通过门面模式将缓存功能做一个封装,只暴露出一个获取图片入口,这样调用者使用起来更加方便而且安全性更高。其实函数式编程也是门面模式的产物2.2 为什么通过门面模式设计ApiService?
  用 Retrofit 做一次请求大致流程如下:interface ApiService {     /**      * 获取首页数据      */     @GET("/article/list/{page}/json")     suspend fun getHomeList(@Path("page") pageNo: Int)     : ApiResponse }  //构建Retrofit val retrofit = Retrofit.Builder().build()  //创建ApiService实例 val apiService =retrofit.create(ApiService::class.java)  //发起请求(这里用的是suspend会自动发起请求,Java中可通过返回的call请求) apiService.getHomeList(1)
  然后通过 Retrofit 创建ApiService 类型实例调用对应方法即可发起请求。乍一看感觉很普通,但实际上Retrofit 通过这种模式(门面模式)帮我们过滤掉了很多无用信息
  tips:我们都知道 Retrofit 只不过是对OkHttp 做了封装。
  如果直接使用OkHttp,当在构造Request时要做很多繁琐的工作,最要命的是Request可能在多处被构造( ViewModel 、Repository ...),写的越分散出错时排查的难度就越高。而Retrofit通过注解的形式将Request需要的必要信息全依附在方法上(还是个抽象方法,尽量撇除一切多余信息),作为使用者只需要调用对应方法即可实现请求。至于如何解析、构造、发起请求 Retrofit 内部会做处理,调用者不想也不需要知道,
  所以Retrofit通过门面模式帮调用者屏蔽了一些无用信息,只暴露出唯一入口,让调用者更专注于业务开发。像我们常用的Room、GreenDao也使用了这种模式 3. 动态代理其实不是工具
  看过很多Retrofit相关的文章,都喜欢上来就抛动态代理,关于为什么用只字不提,搞的 Retrofit 动态代理像是一个工具(框架)一样,殊不知它只是代理模式思想层面的一个产物而已。本小结会透过Retrofit 看动态代理本质,帮你解除对它的误解3.1 Retrofit构建
  Retrofit构建如下所示: Retrofit.Builder()     .client(okHttpClient)     .addConverterFactory(GsonConverterFactory.create())     .addCallAdapterFactory(RxJava2CallAdapterFactory.create())     .baseUrl(ApiConstants.BASE_URL)     .build()
  很典型的构建者模式,可以配置 OkHttp 、Gson 、RxJava 等等,最后通过build() 做构建操作,跟一下build() 代码:
  #Retrofit.class public Retrofit build() {          //1.CallAdapter工厂集合         List callAdapterFactories = new ArrayList<>(this.callAdapterFactories);         callAdapterFactories.addAll(platform.defaultCallAdapterFactories(callbackExecutor));          //2.Converter工厂集合         List converterFactories =                 new ArrayList<>(                         1 + this.converterFactories.size() + platform.defaultConverterFactoriesSize());         converterFactories.add(new BuiltInConverters());         converterFactories.addAll(this.converterFactories);         converterFactories.addAll(platform.defaultConverterFactories());          return new Retrofit(               callFactory,                 baseUrl,                 unmodifiableList(converterFactories),                 unmodifiableList(callAdapterFactories),                 callbackExecutor,                 validateEagerly);     }
  将一些必要信息注入到 Retrofit 并创建返回。注释1、2处两个集合非常重要,这里先埋个伏笔后面我们再回来看3.2 何为动态代理?
  什么是代理模式?
  代理模式概念非常简单,比如A想做一件事可以让B帮他做,这样做的好处是什么?下面通过一个例子简要说明。需求:每一次本地数据库CRUD都要做一次上报
  最简单粗暴的方式就是每次CRUD时都单独做一次记录,代码如下 //业务层方法test1 fun test1{     //数据库插入操作     dao.insert()     //上报     post() } //业务层方法test2 fun test2(){     //数据库更新操作     dao.update()     //上报     post() }
  以上这种方式存在一个问题:
  上报操作本身与具体业务无关,一旦需要对上报进行修改,那就可能影响到业务,进而可能造成不可预期的问题产生
  面对以上问题可以通过代理模式完美规避,改造后的代码如下: class DaoProxy(){     //数据库插入操作     fun insert(){         dao.insert()         //上报         post()     }      //数据库更新操作     fun update(){         dao.update()         //上报         post()     } }  //业务层方法test1 fun test1{     //数据库插入操作     daoProxy.insert() } //业务层方法test2 fun test2(){     //数据库更新操作     daoProxy.update() }
  新增一个代理类 DaoProxy ,将dao以及上报操作在代理类中执行,业务层直接操作代理对象,这样就将上报从业务层抽离出来,从而避免业务层改动带来的问题。实际使用代理模式时应遵守基于接口而非实现编程思想,但文章侧重于传授思想,规范上可能欠缺
  此时还有一个问题,每次CRUD都会手动做一次上报操作,这显然是模版代码,如何解决?下面来看动态代理:
  什么是动态代理?
  java中的动态代理就是在运行时通过反射为目标对象做一些附加操作,代码如下: class DaoProxy() {     //创建代理类     fun createProxy(): Any {         //创建dao         val proxyAny = Dao()         val interfaces = proxyAny.javaClass.interfaces         val handler = ProxyHandler(proxyAny)         return Proxy.newProxyInstance(proxyAny::class.java.classLoader, interfaces, handler)     }      //代理委托类     class ProxyHandler(private val proxyObject:Any): InvocationHandler {         //代理方法,p1为目标类方法、p2为目标类参数。调用proxyObject任一方法时都会执行invoke         override fun invoke(p0: Any, p1: Method, p2: Array): Any {             //执行Dao各个方法(CRUD)             val result = p1.invoke(proxyObject,p2)             //上报             post()             return result         }     } } //此处规范上应该使用基于接口而非实现编程。如果要替换Dao通过接口编程可提高扩展性 val dao:Dao = DaoProxy().createProxy() as Dao dao.insert() dao.update()
  其中Proxy是JDK中用于创建动态代理的类, InvocationHandler 是一个委托类, 内部的invoke (代理方法)方法会随着目标类(Dao)任一方法的调用而调用,所以在其内部实现上报操作即可消除大量模版代码。
  动态代理与静态代理核心思想一致,区别是动态代理可以在运行时通过反射动态创建一个切面( InvocationHandler#invoke ),用来消除模板代码。喜欢思考的同学其实已经发现,代理模式符合面向切面编程(AOP)思想,而代理类就是切面3.3 动态代理获取ApiService
  2.2小节有提到可以通过 retrofit.create() 创建ApiService ,跟一下retrofit 的create()
  #Retrofit.class public  T create(final Class service) {         //第一处         validateServiceInterface(service);         return (T) Proxy.newProxyInstance(                         service.getClassLoader(),                         new Class<?>[] {service},                         new InvocationHandler() {                             //第二处                             @Override                             public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)                                     throws Throwable {                                 ...                                 return platform.isDefaultMethod(method)                                         ? platform.invokeDefaultMethod(method, service, proxy, args)                                         : loadServiceMethod(method).invoke(args);                             }                         });     }
  create() 大致可以分为两部分:第一部分为 validateServiceInterface() 内容,用来验证ApiService 合法性,比较简单就不多描述,感兴趣的同学可自行查看。第二部分就是 invoke() ,通过3.2小节可知这是一个代理方法,可通过调用ApiService 中的任一方法执行,其中参数method和args代表ApiService 对应的方法和参数。返回值中有一个isDefaultMethod ,这里如果是Java8 的默认方法直接执行,毕竟我们只需要代理ApiService 中方法即可。经过反复筛选最后重任落在了loadServiceMethod ,这也是Retrofit 中最核心的一个方法,下面我们来跟一下
  #Retrofit.class ServiceMethod<?> loadServiceMethod(Method method) {     ServiceMethod<?> result = serviceMethodCache.get(method);     if (result != null) return result;     synchronized (serviceMethodCache) {       result = serviceMethodCache.get(method);       if (result == null) {         result = ServiceMethod.parseAnnotations(this, method);         serviceMethodCache.put(method, result);       }     }     return result;   }
  大致就是对 ServiceMethod 做一个很常见的缓存操作,这样做的目的是为了提升运行效率,毕竟创建一个ServiceMethod 会用到大量反射。创建ServiceMethod对象是通过其静态方法parseAnnotations 实现的,再跟一下这个方法:
  #ServiceMethod.class   static  ServiceMethod parseAnnotations(Retrofit retrofit, Method method) {         //第一步         RequestFactory requestFactory =             RequestFactory.parseAnnotations(retrofit, method);         Type returnType = method.getGenericReturnType();         ...         //第二步         return HttpServiceMethod.parseAnnotations(retrofit,                 method, requestFactory);     }
  第一步:
  通过 RequestFactory 的parseAnnotations() 解析method(ApiService的method) 中的注解信息,具体代码很简单就不再贴了。不过需要注意这一步只是解析注解并保存在RequestFactory 工厂中,会在请求时再通过RequestFactory 将请求信息做拼装。
  第二步:
  调用 HttpServiceMethod 的parseAnnotations 创建ServiceMethod ,这个方法很长并且信息量很大,下一小节我再详细描述,此处你只需知道它做了什么即可。其实到这方法调用链已经很绕了,我先帮大家捋一下 HttpServiceMethod 其实是ServiceMethod 的子类,Retrofit 动态代理里面的loadServiceMethod 就是HttpServiceMethod 类型对象,最后来看一下它的invoke() 方法。
  #HttpServiceMethod.class @Override   final @Nullable ReturnT invoke(Object[] args) {     Call call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);     return adapt(call, args);   }
  创建了一个 OkHttpCall 实例,它内部其实就是对OkHttp的一系列操作,这里先按住不表后面我会再提到。把关注点切到返回值,返回的Call对象没做任何操作,而是传入到adapter() 方法一并返回来,字面意思应该是一个适配操作,那究竟如何适配?这里再埋一个伏笔与3.1结尾相呼应,下一小节我们再一一揭开。
  动态代理讲完了,那么它解决了什么问题?
  假如不使用代理模式,那关于 ApiService 中方法注解解析的操作势必会浸入到业务当中,一旦对其修改就有可能影响到业务,其实也就是也违背了我们前面所说的门面模式和迪米特法则,通过代理模式做一个切面操作(AOP)可以完美规避了这一问题。可见这里的门面模式和代理模式是相辅相成的
  Retrofit 事先都不知道ApiService 方法数量,就算知道也避免不了逐一解析而产生大量的模版代码,此时可通过引入动态代理在运行时动态解析 从而解决这一问题。4. ReturnT、ResponseT做一次适配的意义何在?
  ResponseT 、ReturnT 是 Retrofit  对响应数据类型和返回值类型的简称4.1 创建HttpServiceMethod
  上一小节我们跟到了 adapter() ,这是一个抽象方法,其实现类是通过HttpServiceMethod 的parseAnnotations 创建的,继续跟下去:
  #HttpServiceMethod.class static  HttpServiceMethod parseAnnotations(             Retrofit retrofit, Method method, RequestFactory requestFactory) {         boolean isKotlinSuspendFunction = requestFactory.isKotlinSuspendFunction;         boolean continuationWantsResponse = false;         boolean continuationBodyNullable = false;          Annotation[] annotations = method.getAnnotations();         Type adapterType;         //1.获取adapterType,默认为method返回值类型         if (isKotlinSuspendFunction) {             Type[] parameterTypes = method.getGenericParameterTypes();             Type responseType =                     Utils.getParameterLowerBound(                             0, (ParameterizedType) parameterTypes[parameterTypes.length - 1]);             if (getRawType(responseType) == Response.class && responseType instanceof ParameterizedType) {                 // Unwrap the actual body type from Response.                 responseType = Utils.getParameterUpperBound(0, (ParameterizedType) responseType);                 continuationWantsResponse = true;             } else {             }             adapterType = new Utils.ParameterizedTypeImpl(null, Call.class, responseType);             annotations = SkipCallbackExecutorImpl.ensurePresent(annotations);         } else {             adapterType = method.getGenericReturnType();         }         //2.创建CallAdapter         CallAdapter callAdapter =                 createCallAdapter(retrofit, method, adapterType, annotations);         Type responseType = callAdapter.responseType();         //3.创建responseConverter         Converter responseConverter =                 createResponseConverter(retrofit, method, responseType);          okhttp3.Call.Factory callFactory = retrofit.callFactory;         //4.创建HttpServiceMethod类型具体实例         if (!isKotlinSuspendFunction) {             return new HttpServiceMethod.CallAdapted<>(requestFactory, callFactory, responseConverter, callAdapter);         }         //兼容kotlin suspend方法         else if (continuationWantsResponse) {             //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.             return (HttpServiceMethod)                     new HttpServiceMethod.SuspendForResponse<>(                             requestFactory,                             callFactory,                             responseConverter,                             (CallAdapter>) callAdapter);         } else {             //noinspection unchecked Kotlin compiler guarantees ReturnT to be Object.             return (HttpServiceMethod)                     new HttpServiceMethod.SuspendForBody<>(                             requestFactory,                             callFactory,                             responseConverter,                             (CallAdapter>) callAdapter,                             continuationBodyNullable);         }     }
  注释1 :获取adapterType ,这里的adapter指的是Retrofit构建时通过addCallAdapterFactory() 添加的类型,如果添加的是RxJava那adapterType 便是Observable 。默认是method返回值,同时也会做kotlin suspend 适配
  注释2: 创建callAdapter ,暂时掠过,下面详细描述
  注释3: 创建responseConverter ,暂时掠过,下面详细描述
  注释4: 这里会创建具体的HttpServiceMethod 类型实例,总共有三种类型CallAdapted 、SuspendForResponse 、SuspendForBody ,第一种为默认类型,后两种可兼容kotlin suspend 。内部主要做的事情其实很简单,就是通过内部的adapter() 调用callAdapter ->adapter() ,具体代码就不贴了,感兴趣的自行查看4.2 如何管理callAdapter、responseConverter?
  创建创建callAdapter
  #HttpServiceMethod.class  private static  CallAdapter createCallAdapter(             Retrofit retrofit, Method method, Type returnType, Annotation[] annotations) {         return (CallAdapter) retrofit.callAdapter(returnType, annotations);         ...     }
  通过 retrofit#callAdapter() 获取CallAdapter ,继续跟
  #Retrofit.class public CallAdapter<?, ?> callAdapter(Type returnType, Annotation[] annotations) {         return nextCallAdapter(null, returnType, annotations); }  public CallAdapter<?, ?> nextCallAdapter(             @Nullable CallAdapter.Factory skipPast, Type returnType, Annotation[] annotations) {         int start = callAdapterFactories.indexOf(skipPast) + 1;         for (int i = start, count = callAdapterFactories.size(); i < count; i++) {             //通过returnType在callAdapterFactories获取adapter工厂,再get adapter             CallAdapter<?, ?> adapter = callAdapterFactories.get(i).get(returnType, annotations, this);             if (adapter != null) {                 return adapter;             }         }         ...     }
  先通过 returnType 在callAdapterFactories 获取adapter 工厂,再通过工厂get()获取CallAdapter 实例。callAdapterFactories 是3.1结尾build() 中初始化的,通过platform 添加默认类型,也可以通过addCallAdapterFactory() 添加RxJava之类的适配器类型。
  这里用到了两个设计模式适配器跟策略 适配器模式
  返回的 CallAdapter 其实就是Call 的适配器,假如你想让Retrofit配合RxJava使用,常规方式只能在业务中单独创建Observable 并与Call 融合,关于Observable 与Call融合(适配)其实是与业务无关的,此时可以引入适配器模式将Call适配成Observable ,将适配细节从业务层挪到Retrofit内部,符合迪米特法则策略模式
  通过 ReturnT 获取对应的CallAdapter ,如果ReturnT 是Call 那获取的是DefaultCallAdapterFactory 创建的实例,如果是Observable 则获取的是RxJava2CallAdapterFactory 创建的实例。假如想新增一种适配器只需明确ReturnT,创建对应工厂再通过addCallAdapterFactory 添加即可,Retrofit会通过ReturnT自动寻找对应CallAdapter ,符合开闭原则(扩展开放)
  创建responseConverter
  关于 responseConverter 其实是做数据转换的,可以将ResponseT适配成我们想要的数据类型,比如Gson解析只需通过addConverterFactory 添加GsonConverterFactory 创建的Converter 实例即可 具体添加、获取流程与CallAdapter 基本一致,感兴趣的同学可自行查看4.3 发起请求
  到上一小结我们已经创建了所有需要的内容,再回到 HttpServiceMethod 的invoke,这里会将OkHttpCall传入到adapt执行并返回,HttpServiceMethod 的实现类的adapter 会执行对应CallAdapter 的adapter 我们就取默认的CallAdapter  即DefaultCallAdapterFactory 通过get获取的CallAdapte r,代码如下:DefaultCallAdapterFactory.class  public @Nullable CallAdapter<?, ?> get(         return new CallAdapter>() {             @Override             public Type responseType() {                 return responseType;             }              @Override             public Call adapt(Call call) {                 return executor == null ? call : new DefaultCallAdapterFactory.ExecutorCallbackCall<>(executor, call);             }         };     }
  内部 adapt 即ApiService method 最终返回的ExecutorCallbackCall 是OkHttpCall 装饰类,最后可通过OkHttpCall 的execute 发起请求,代码如下:
  #OkHttpCall.class public Response execute() throws IOException {         okhttp3.Call call;         ...         return parseResponse(call.execute());     }
  OkHttp常规操作,再把关注点放到 onResponse 的parseResponse
  #OkHttpCall.class Response parseResponse(okhttp3.Response rawResponse) throws IOException {         ...         T body = responseConverter.convert(catchingBody);         ...         return Response.success(body, rawResponse);     }
  responseConverter 会对Body做一个适配,如果addConverterFactory 添加了GsonConvert 那解析操作就会在此处进行
  至此Retrofit全部流程分析完毕
  综上所述Retrofit通过REST ful API从范式层面约束代码 通过门面模式设计ApiService可以让开发者更专注于业务 动态代理只是将功能代码从业务剥离,并解决了模板代码问题 ReturnT、ResponseT引入适配器模式可以让结果更加灵活 最后
  在这里我再分享一份由多位大佬亲自收录整理的Android学习PDF+架构视频+面试文档+源码笔记,高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料
  这些都是我现在闲暇时还会反复翻阅的精品资料。里面对近几年的大厂面试高频知识点都有详细的讲解。相信可以有效地帮助大家掌握知识、理解原理,帮助大家在未来取得一份不错的答卷。
  当然,你也可以拿去查漏补缺,提升自身的竞争力。
  如果你有需要的话,只需私信我【进阶】即可获取
高合HiPhiX,或是特斯拉真正意义上的对手8月11日,华人运通宣布旗下豪华纯电动汽车全球首款可进化超跑SUV高合HiPhiX将于北京车展全球上市。下意识地,我对身边的朋友说,这是今年最令人期待的车型,因为它极可能是特斯拉真全新瑞虎7神行版上市限时订购8。99万起曾顺利跑完环塔全程摘得冠军的奇瑞瑞虎7全新升级,推出神行版。新车不仅搭载1。6TGDI7DCT超强动力组合,还拥有应急快速的全方位安全防护和响应快速的智能科技配置。9月3日,全新瑞王晓秋年轻化不止于口号消费者购车,从过去看中机械表现,到现在越来越看中智能科技。8月14日,上汽集团总裁王晓秋在2020中国汽车论坛大会上表示,能够将年轻化和消费升级趋势有效结合的品牌,市场表现会更强劲翼联EDUP摄像头监控器常识红外夜视移动侦测人形追踪是什么疫情常态化的现下,摄像头作为视频会议网络教学的重要载体得到越来越多的关注。与此同时,与家庭安防息息相关的监控器也备受青睐。同为视频设备,红外夜视移动侦测人形追踪声光警戒等功能您清楚无线空间流是什么概念WiFi传输速率与空间流有何关系什么是无线空间流?MIMO中,信号被分成了多个信号同时传输,经过多个天线进行同步传送。由于无线信号在传送的过程当中为了避免发生干扰,会走不同的反射或穿透路径,因此到达接收端的时间会翼联EDUPYooSee(有看头)监控摄像头App连接管理教程近期,翼联EDUP相继推出了4G网络监控WiFi无线监控太阳能WiFi监控,引发了一波市场关注。而对于用户广泛关注的监控摄像头连接和管理问题,小编也整理了一些资料借花献佛,希望能够全景超高清小白都会用翼联EHFi监控摄像头让家更安心外卖到家快递上门,才送煤气又修水管,老人健忘小孩顽皮,是时候装个监控摄像头了。一到夜晚看不清,安装设置太费劲,只能本地不能远程,有没有一款好用的的监控摄像头?选它就对了,翼联EH1翼联EDUP要安全而不是侵权这才是监控摄像头的正确用法近期,摄像头事件引发热议。一批摄像头被违规安装在酒店住房,对房客租户的隐私和人身安全构成了不法侵害。监控摄像头到底应该如何正确使用呢?户外监控将不速之客拒之门外室内监控的作用在于可又到开学季网课正当时翼联EDUP自动对焦摄像头为学习接力去年的一波疫情迁延反复成为常态,前段时间中原大省又遭遇一场百年难遇的雨水灾害,近日郑州洛阳多地纷纷推出网课教学,以确保安全学习两不误。又到开学季,网课正当时,翼联EDUP自动对焦摄奇骏勇闯无人区是一种信仰01hr大漠,孤烟,烈日,黄沙国庆长假过后,我们跟随东风日产一路向西,直抵敦煌,带着感受古道西风瘦马的旖旎心情,期待一场惊险刺激的极致探索之旅。敦煌,地处河西走廊的最西端,地处甘肃TPIPC42A4云台无线摄像头随着科技的快速发展,越来越多的人工智能产品进入到人们生活中。在这些人工智能产品中有一类产品越来越受到人们青睐家庭无线监控摄像头。安全是我们生活的重中之重,没有安全的环境,我们的生活
ArgoAI将开始在加州提供免费公共无人出租车服务转载太平洋电脑网据国外媒体报道,日前由福特和大众支持的无人机科技创业公司ArgoAI已经在加州获得许可,将在该州社会道路上对民众提供免费的无人出租车服务。而非一周前,福特和Argo最多支持12方同时音视频通话最多支持12方同时音视频通话市面上也有不少可以视频通话的应用,但是大多数应用目前的支持人数并不多,畅连的支持人数目前来讲已经能满足大多数人的需求,最多支持12方同时在线进行音视频通PS5销量突破1000万台成索尼史上销售最快的主机来源TechWeb。com。cn作者SukyTechWeb索尼官方近日宣布,新一代游戏主机PlayStation5自2020年11月12日首度问世以来,全球销量已突破1000万台。三星使用人工智能设计未来Exynos芯片组开发智能手机芯片组是一个耗时的过程。据报道,三星正在使用人工智能来设计Exynos芯片组,这些芯片组将用在三星和其它厂商的下一代智能手机当中。具体来说,三星将使用芯片设计软件公司S看最早的日出,吃最靓的海鲜!桑沟湾海洋牧场,吃喝玩乐全在这桑沟湾位于威海荣成,是中国最东端的海湾,也是日出最早的海湾。这里海水清沥,波涌柔缓,海产丰盛。来桑沟湾海洋牧场玩耍的项目可太多了,不仅能乘船观景喂海鸥捞牡蛎摘海虹,还可以在海上平台人工智能机器学习和深度学习,是如何影响视频监控的?许多视频监控专业人员都遇到过人工智能机器学习和深度学习等术语。但是这些术语是什么意思,它们如何影响视频监控?人工智能机器学习和深度学习人工智能是一个宽泛的术语,指的是将人类智能应用云看大熊猫,动物园如何利用视频技术玩转动物IP?动物园作为景区风光或城市形象旅游产品的一大主要亮点,良好的营销策略能为动物园景点或城市吸引更多的游客,带来巨大的商机,推动城市的健康持续发展。因此其营销的重要性是毋庸置疑的,成都大电动车爆炸事故频发!如何基于EasyGBS技术实现电梯智能化防控?一现状背景全国各地因电动车违规停放充电导致的火灾事故频发,教训十分惨痛,一件件电动车在电梯爆炸起火的新闻让人心惊肉跳。因此近期,公安部应急指挥部等多个部门下发了关于规范电动车停放充云端边缘计算AI,智能安防技术如何应用在仓储物流中?云边端架构可以应用在很多不同的视频监控系统中,其中包括智慧工地智慧城市智慧校园安防等场景。本文我们来介绍关于工厂仓储场景的解决方案。一方案背景工厂仓储现场一般都会布置很多的监控摄像PC中的保时捷华硕灵耀X纵横较量保时捷718展现高端奢华非凡实力灵耀X纵横作为笔记本圈内奢华机的代表,其外观低调独特的流线,细腻墨玉黑机身搭配精致琥珀红钻石切边工艺,以及首批获得英特尔Evo平台严苛认证的光环加持,一直是高端商务和精英人士的首选灵耀X双屏的100种玩法,你知道几个?灵耀X双屏的主副屏协同方式极具酷炫,极大提升工作效率,但大部分用户并没有get到很多主副屏的使用技巧,今天这篇文章为大家开拓思路,发掘灵耀X双屏的更多玩法。程序编写快意码农灵耀X双