Retrofit源码分析:动态代理获取Api接口实例,解析注解生成request,线程切换

目录

一,Retrofit的基本使用

1.定义api接口

2.创建Retrofit实例

3.获取api接口实例发起请求

二,静态代理和动态代理

1,静态代理

2,动态代理

三,动态代理获取Api接口实例

四,解析接口方法注解,生成请求方法

五,代理发起网络请求

六,Retrofit如何实现线程切换?


一,Retrofit的基本使用

1.定义api接口

public interface ApiService {
    // GET 请求示例
    @GET("users/{id}")
    Call<User> getUser(@Path("id") int userId);

    // POST 请求示例
    @POST("users")
    Call<User> createUser(@Body User user);

    // 带查询参数的 GET 请求示例
    @GET("users")
    Call<List<User>> getUsers(@Query("page") int page, @Query("size") int size);
}

2.创建Retrofit实例

public class ApiClient {
    
    private static final String BASE_URL = "https://api.github.com/";
    private static Retrofit retrofit;

    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())  // 使用 Gson 解析器
                    .build();
        }
        return retrofit;
    }
}

3.获取api接口实例发起请求

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 获取 apiService 实例
        ApiService apiService = ApiClient.getClient().create(ApiService.class);

        // 创建请求
        Call<User> call = apiService.getUser("octocat");

        // 异步请求
        call.enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                if (response.isSuccessful()) {
                    // 请求成功,处理数据
                    User user = response.body();
                    Log.d("Retrofit", "User: " + user.getName());
                } else {
                    // 请求失败
                    Log.e("Retrofit", "Request failed");
                }
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
                // 请求失败
                Log.e("Retrofit", "Request error", t);
            }
        });
    }
}

 

从Retrofit的使用入手,Retrofit的核心在于:

  • 通过动态代理获取apiService实例,对api请求方法进行拓展。
  • 在代理对象的invoke方法中对接口方法进行解析,解析注解,参数,参数注解,返回类型等。
  • 代理用于发送请求的Call对象执行网络请求,并拦截响应。
  • 底层使用Handler机制切换到主线程,并将响应结果返回。

二,静态代理和动态代理

首先,我们先了解一下代理模式

代理模式:通过代理对象来代替真实对象的访问,从而在不修改原对象的情况下,对原对象的方法进行拓展

 

代理模式一般包含这几个元素:

  • 委托类(被代理的类)
  • 接口(委托类实现的方法)
  • 代理类

1,静态代理

静态代理分为以下几个步骤:

  1. 创建一个接口,定义方法
  2. 创建一个委托类实现接口
  3. 创建一个代理类,持有委托类的引用,实现与委托类相同的接口

(1)创建接口

/*
委托接口
 */
interface ClientInterface {
    fun call()
}

(2)创建委托类实现接口

/*
委托类
 */
class Client : ClientInterface {
    override fun call() {
        println("Client Call")
    }
}

(3)创建代理类,实现与委托类相同的接口

/*
代理类
 */
class Proxy : ClientInterface {
    var client : Client? = null

    fun setInstance(client: Client){
        this.client = client;
    }

    override fun call() {
        println("proxy pre call")
        client?.call()
        println("proxy aft call")
    }
}

这样我们就可以通过使用代理类的call方法,从而对委托类的方法进行拓展;

2,动态代理

动态代理与静态代理的区别就是:动态代理的代理对象由Java中的Proxy.newProxyInstance()在运行时动态生成。

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException

这个方法需要的三个参数为:

  •  loader:委托类的类加载器
  • interfaces:委托类实现的接口
  • h:一个实现了InvocationHandler的类,在这个类的invoke方法中对委托类的方法进行拓展

(1)同样创建委托类和接口

/*
委托类,也就是代理对象
*/
class Client : ClientInterface {
    override fun call() {
        println("client call")
    }
}

/*
委托接口,代理类实现的接口
 */
interface ClientInterface {
    fun call()

    fun call1()
}

(2)创建实现InvocationHandler的类

/*
实现了InvocationHandler的类
 */
class CallInvocationHandler(private var target: Any) : InvocationHandler {

    //代理类调用的方法会被转发到这里运行

    override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
        println("before client: ${args.toString()} call: ${method.name}")
        return method.invoke(target, args)
    }
}

(3)使用Proxy.newProxyInstance()生成代理对象

class Application3 {
    fun main(){
        val client : ClientInterface = Client()

        val proxy = Proxy.newProxyInstance(
            client::class.java.classLoader, //代理对象的类加载器,用于加载代理对象
            arrayOf(client::class.java), //代理对象实现的接口,也就是代理对象要进行的业务
            CallInvocationHandler(client) //实现了 InvocationHandler 接口的对象,通过代理类调用代理对象的方法时,就会转发到invoke方法中被调用
        ) as Client

        proxy.call();
    }
}

动态代理(JDK动态代理)只能代理实现了接口的类,所以与其说代理类代理了委托类,不如说它是代理了接口 

三,动态代理获取Api接口实例

当我们明白了动态代理之后,我们再来看Retrofit的create方法:

private val apiService : ApiService = RetrofitClient.getInstance().create(ApiService::class.java)


public <T> T create(final Class<T> service) {
    validateServiceInterface(service);

    //动态代理,获取ApiService的代理对象
    return (T)
    Proxy.newProxyInstance(
        service.getClassLoader(),
        new Class<?>[] {service},
        new InvocationHandler() {
            private final Platform platform = Platform.get();
            private final Object[] emptyArgs = new Object[0];

            @Override
            public @Nullable Object invoke(Object proxy, Method method, @Nullable Object[] args)
            throws Throwable {

                // object类中的方法直接调用
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }

                args = args != null ? args : emptyArgs;
                
                //检查是否含有默认实现,如果有直接按默认实现调用,
                //没有则进入loadServiceMethod方法
                return platform.isDefaultMethod(method)
                ? platform.invokeDefaultMethod(method, service, proxy, args)
                : loadServiceMethod(method).invoke(args);
            }
        });
}


//在这里对接口中的方法进行解析
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;
}

可见Retrofit通过create方法创建了一个ApiService的代理类。通过ApiService代理类调用请求方法时,就会被转发到InvocationHandler的invoke方法中进行解析(loadServiceMethod)和调用(invoke)。

四,解析接口方法注解,生成请求方法

接着,Retrofit通过ServiceMethod类对方法及方法注解进行解析:

abstract class ServiceMethod<T> {
  static <T> ServiceMethod<T> parseAnnotations(Retrofit retrofit, Method method) {
    //构建请求工厂
    RequestFactory requestFactory = RequestFactory.parseAnnotations(retrofit, method);

    ...

    //封装,形成完整的可执行的请求
    return HttpServiceMethod.parseAnnotations(retrofit, method, requestFactory);
  }

  abstract @Nullable T invoke(Object[] args);
}

ServiceMethod是一个抽象类,实现类为HttpServiceMethod。

ServiceMethod首先构建了一个RequestFactory,RequestFactory中封装了从接口方法解析到的信息,包括:

  • 请求方法(GET, POST等)
  • baseUrl和请求的相对路径
  • 请求头,请求参数,请求体等

final class RequestFactory {
  static RequestFactory parseAnnotations(Retrofit retrofit, Method method) {
    return new Builder(retrofit, method).build();
  }

  private final Method method; //接口方法
  private final HttpUrl baseUrl; //baseUrl
  final String httpMethod; //请求方法 get,post等

  private final @Nullable String relativeUrl; //请求的相对路径
  private final @Nullable Headers headers; //请求头
  private final @Nullable MediaType contentType; //请求体MIME类型

  private final boolean hasBody; //是否包含请求体
  private final boolean isFormEncoded; //是否是表单编码请求
  private final boolean isMultipart; //是否是多部分请求

  private final ParameterHandler<?>[] parameterHandlers; //参数处理数组,根据参数注解决定如何处理参数
  final boolean isKotlinSuspendFunction; //是否为kotlin挂起函数

}

接着在HttpServiceMethod中,使用适配器,格式转换器,结合RequestFactory将请求信息封装为可执行的完整的请求

核心代码:

static <T> HttpServiceMethod<T> parseAnnotations(
        Retrofit retrofit, Method method, RequestFactory requestFactory) {
    CallAdapter<T, ?> callAdapter = createCallAdapter(retrofit, method);
    Type responseType = callAdapter.responseType();
    Converter<ResponseBody, T> responseConverter = createResponseConverter(retrofit, method, responseType);

    return new HttpServiceMethod<>(requestFactory, callAdapter, responseConverter);
}

核心组件:

  • callAdapter适配器:将底层的call对象适配为用户接口返回的类型(Call<T>, Flow<T>, LiveData<T>等);

  • converter格式转化器:例如转化Json格式数据

  • RequestFactory:之前封装好的请求信息

五,代理发起网络请求

在请求构建完毕后,就会调用invoke方法,具体调用的是HttpServiceMethod类中的invoke方法

invoke方法中又返回了adapt方法的调用,adapt方法的具体实现交给三个子类:

  • CallAdapted<ResponseT, ReturnT>:适配返回类型为标准Java类型,如Call<T>,或者RxJava的Single<T>, Observer<T>类型。

  • SuspendForResponse<ResponseT>:适配 Kotlin 协程场景,当接口方法的返回类型为 suspend fun 且需要返回一个 Respond<T>(完整的 HTTP 响应对象)时使用。

  • SuspendForBody<ResponseT>:适配 Kotlin 协程场景,当接口方法的返回类型为 suspend fun 且只需要返回响应体对象 T(不需要完整的 HTTP 响应对象)时使用。

  @Override
  final @Nullable ReturnT invoke(Object[] args) {
    Call<ResponseT> call = new OkHttpCall<>(requestFactory, args, callFactory, responseConverter);
    return adapt(call, args);
  }

  protected abstract @Nullable ReturnT adapt(Call<ResponseT> call, Object[] args);

  static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
   
  }

  static final class SuspendForResponse<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
    
  }

  static final class SuspendForBody<ResponseT> extends HttpServiceMethod<ResponseT, Object> {
    
  }

一般情况,使用的都是CallAdapted子类,CallAdapted子类中的adapt方法返回callAdapter的adapt方法调用:

static final class CallAdapted<ResponseT, ReturnT> extends HttpServiceMethod<ResponseT, ReturnT> {
    private final CallAdapter<ResponseT, ReturnT> callAdapter;

    CallAdapted(
        RequestFactory requestFactory,
        okhttp3.Call.Factory callFactory,
        Converter<ResponseBody, ResponseT> responseConverter,
        CallAdapter<ResponseT, ReturnT> callAdapter) {
      super(requestFactory, callFactory, responseConverter);
      this.callAdapter = callAdapter;
    }

    @Override
    protected ReturnT adapt(Call<ResponseT> call, Object[] args) {
      return callAdapter.adapt(call);
    }
  }

返回类型为Call<T>,对应的callAdapter由Retrofit默认添加的DefaultCallAdapterFactory生成:

@Override
  public @Nullable CallAdapter<?, ?> get(
      Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call.class) {
      return null;
    }
    if (!(returnType instanceof ParameterizedType)) {
      throw new IllegalArgumentException(
          "Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
    }
    final Type responseType = Utils.getParameterUpperBound(0, (ParameterizedType) returnType);

    final Executor executor =
        Utils.isAnnotationPresent(annotations, SkipCallbackExecutor.class)
            ? null
            : callbackExecutor;

    return new CallAdapter<Object, Call<?>>() {
      @Override
      public Type responseType() {
        return responseType;
      }

      @Override
      public Call<Object> adapt(Call<Object> call) {
        //这里使用代理模式,将okHttpCall的功能代理到ExecutorCallbackCall中
        return executor == null ? call : new ExecutorCallbackCall<>(executor, call);
      }
    };
  }

adapt方法最后返回ExecutorCallbackCall对象,所以调用ApiService代理类的请求方法,最终返回的是call对象为ExecutorCallbackCall对象。

或者说是使用了代理模式,将call的功能代理到了ExecutorCallbackCall中,当我们调用call的异步方法enquque时,最终调用到的是ExecutorCallbackCall的enquque方法。

在ExecutorCallbackCall的enquque方法调用了原call对象的异步请求方法,并拦截了onResponse 和 onFailure 回调,切换到指定的线程(通常是主线程),也就是Retrofit中的onResponse 和 onFailure 回调最终是在主线程中获取到的。

至此,Retrofit的整个流程就结束了。

static final class ExecutorCallbackCall<T> implements Call<T> {
    final Executor callbackExecutor;
    final Call<T> delegate; //被代理的call对象

    ExecutorCallbackCall(Executor callbackExecutor, Call<T> delegate) {
      this.callbackExecutor = callbackExecutor;
      this.delegate = delegate;
    }

    @Override
    public void enqueue(final Callback<T> callback) {
      Objects.requireNonNull(callback, "callback == null");

      //调用原call对象的异步请求方法
      delegate.enqueue(
          new Callback<T>() {
            //拦截底层的 onResponse 和 onFailure 回调,切换到指定的线程(通常是主线程)。
            @Override
            public void onResponse(Call<T> call, final Response<T> response) {
              //这里切换到主线程
              callbackExecutor.execute(
                  () -> {
                    if (delegate.isCanceled()) {
                      
                      callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled"));
                    } else {
                      callback.onResponse(ExecutorCallbackCall.this, response);
                    }
                  });
            }

            @Override
            public void onFailure(Call<T> call, final Throwable t) {
              callbackExecutor.execute(() -> callback.onFailure(ExecutorCallbackCall.this, t));
            }
          });
    }

    @Override
    public boolean isExecuted() {
      return delegate.isExecuted();
    }

    @Override
    public Response<T> execute() throws IOException {
      return delegate.execute();
    }

    @Override
    public void cancel() {
      delegate.cancel();
    }

    @Override
    public boolean isCanceled() {
      return delegate.isCanceled();
    }

    @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
    @Override
    public Call<T> clone() {
      return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone());
    }

    @Override
    public Request request() {
      return delegate.request();
    }

    @Override
    public Timeout timeout() {
      return delegate.timeout();
    }
  }

六,Retrofit如何实现线程切换?

ExecutorCallbackCall代理类发起请求后,在响应回调中,通过callbackExecutor.execute切换到主线程。

这里的callbackExecutor是Retrofit在构建时配置的:

public Retrofit build() {

    Executor callbackExecutor = this.callbackExecutor;
    if (callbackExecutor == null) {
        callbackExecutor = platform.defaultCallbackExecutor();
    }

}

在Retrofit的Platform中,Android平台默认返回一个MainThreadExecutor,在MainThreadExecutor的execute方法中,线程通过Hanlder进行切换。

static class Android extends Platform {
    @Override
    public Executor defaultCallbackExecutor() {
        return new MainThreadExecutor();
    }

    static class MainThreadExecutor implements Executor {
        private final Handler handler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(Runnable r) {
            //通过Handler进行线程切换
            handler.post(r);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/941792.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何保护你的 iOS 应用免受逆向工程攻击

逆向工程是分析和解构软件以理解其工作原理的过程。针对 iOS 应用&#xff0c;逆向工程通常涉及分析已编译的二进制文件&#xff08;机器可读的代码&#xff09;&#xff0c;并将其转化为更容易被人类理解的形式。这使得攻击者能够检查应用的逻辑、理解数据处理的方式&#xff…

C++进阶(二)--面向对象--继承

目录 一、继承的概念及定义 1.继承的概念 2.继承的定义 定义格式 继承方式和访问限定符 继承基类成员访问⽅式的变化 3.继承类模板 二、基类和派生类对象赋值转换 三、继承中的作用域 四、派⽣类的默认成员函数 五、继承与友元 六、继承与静态成员 七、多继承及其…

STM32串口第一次接收数据时第一个字节丢失的问题

解决方法&#xff1a;开启中断之前&#xff0c;先清除标志位【1】。 串口清除标志位&#xff1a; __HAL_UART_CLEAR_PEFLAG(&huart1); HAL_UART_Receive_IT(&huart1,&RxUart, 1); 定时器清除标志位&#xff1a; __HAL_TIM_CLEAR_FLAG(&htim3,TIM_FLAG_UPDATE);…

为什么要用云电脑玩游戏?5大好处揭秘,ToDesk云机性能强又易用

电脑在人们日常的工作与生活中无疑是颇为重要的。无论是学生撰写论文报告、企业白领处理数据图形等事项&#xff0c;还是游戏迷、影视迷们畅玩游戏或观看视频都难免要经常用到。拥有一台性能配置优质并且内置软件全面的电脑&#xff0c;对各类群体来说都大有益处&#xff0c;尤…

深入理解批量归一化(BN):原理、缺陷与跨小批量归一化(CBN)

在训练深度神经网络时&#xff0c;批量归一化&#xff08;Batch Normalization&#xff0c;简称BN&#xff09;是一种常用且有效的技术&#xff0c;它帮助解决了深度学习中训练过程中的梯度消失、梯度爆炸和训练不稳定等。然而&#xff0c;BN也有一些局限性&#xff0c;特别是在…

iptables交叉编译(Hisiav300平台)

参考文章&#xff1a;https://blog.csdn.net/Bgm_Nilbb/article/details/135714738 https://bbs.archlinux.org/viewtopic.php?pid1701065 1、libmnl 交叉编译 tar xvf libmnl-1.0.5.tar.bz2 sudo chmod 777 -R libmnl-1.0.5 cd libmnl-1.0.5 mkdir _install //host和CC需要修…

redis数据类型:list

数据结构 源码版本&#xff1a;7.2.2路径&#xff1a;src/adlist.h 关于list的 头文件中涉及到的这三个结构体如下 /* Node, List, and Iterator are the only data structures used currently. */ # 节点 typedef struct listNode {struct listNode *prev; # 前元素的指针s…

达梦8数据库备份与还原

通过命令找到达梦数据库进程所在位置 ps -ef | grep dm 得到达梦相关进程 pwd 进程ID得到进程目录 [rootdmdb01 bin]# pwd /data/dmdbms/bin [rootdmdb01 bin]# ps -ef | grep dm root 1183 2 0 Nov04 ? 00:00:33 [kworker/8:1H-xfs-log/dm-0] root …

电气设计 | 低压接地系统:TN-C 、TN-S、TN-C-S、TT适用哪些场所?

电气设计 | 低压接地系统&#xff1a;TN-C 、TN-S、TN-C-S、TT适用哪些场所&#xff1f; 1、低压配电系统简介2、各种低压配电系统介绍2.1、TN-C系统2.2、TN-S系统2.3、TN-C-S 系统2.4、TT 系统2.5、IT 系统 1、低压配电系统简介 低压配电系统有TN-C、TN-S、TN-C-S、TT和IT五种…

重温设计模式--组合模式

文章目录 1 、组合模式&#xff08;Composite Pattern&#xff09;概述2. 组合模式的结构3. C 代码示例4. C示例代码25 .应用场景 1 、组合模式&#xff08;Composite Pattern&#xff09;概述 定义&#xff1a;组合模式是一种结构型设计模式&#xff0c;它允许你将对象组合成…

漏洞检测工具:Swagger UI敏感信息泄露

Swagger UI敏感信息泄露 漏洞定义 Swagger UI是一个交互式的、可视化的RESTful API文档工具&#xff0c;它允许开发人员快速浏览、测试API接口。Swagger UI通过读取由Swagger&#xff08;也称为OpenAPI&#xff09;规范定义的API描述文件&#xff08;如swagger.json或swagger…

Linux下学【MySQL】表中插入和查询的进阶操作(配实操图和SQL语句通俗易懂)

绪论​ 每日激励&#xff1a;挫折是会让我们变得越来越强大的重点是我们敢于积极的面对它。—Jack叔叔 绪论​&#xff1a; 本章是表操作的进阶篇章&#xff08;没看过入门的这里是传送门&#xff0c;本章将带你进阶的去学习表的插入insert和查找select&#xff0c;本质也就是…

JavaScript 标准内置对象——Object

1、构造函数 2、静态方法 // 将源对象中所有可枚举的自有属性复制到目标对象&#xff0c;&#xff0c;并返回修改后的目标对象 Object.assign(target, ...sources) Object.create(proto, propertiesObject) // 以一个现有对象作为原型&#xff0c;创建一个新对象Object.defineP…

Robot Framework搭建自动化测试框架

1.配置环境 需要安装jdk8&#xff0c;andrid sdk&#xff08;安装adb&#xff09;&#xff0c;pycharm编译环境以及软件 安装Robot Framework 首先&#xff0c;你需要安装Robot Framework&#xff0c;可以使用 pip 进行安装&#xff1a; pip install robotframework安装所需的…

fastjson诡异报错

1、环境以及报错描述 1.1 环境 操作系统为中标麒麟、cpu 为国产鲲鹏服务器。 jdk为openjdk version 1.8.0._242 1.2 错误 com.alibaba.fastjson2.JSONException: syntax error : f at com.alibaba.fastjson2.JSONReaderUTF16.readBoolValue(JSONReaderUTF16.java:6424) at c…

Unity3d 基于UGUI和VideoPlayer 实现一个多功能视频播放器功能(含源码)

前言 随着Unity3d引擎在数字沙盘、智慧工厂、数字孪生等场景的广泛应用&#xff0c;视频已成为系统程序中展示时&#xff0c;不可或缺的一部分。在 Unity3d 中&#xff0c;我们可以通过强大的 VideoPlayer 组件和灵活的 UGUI 系统&#xff0c;将视频播放功能无缝集成到用户界面…

蓝牙协议——音乐启停控制

手机播放音乐 手机暂停音乐 耳机播放音乐 耳机暂停音乐

【EthIf-13】EthIfGeneral容器配置-01

1.EthIfGeneral类图结构 下面是EthIfGeneral配置参数的类图&#xff0c;比较重要的参数就是配置&#xff1a; 接收中断是否打开发送确认中断是否打开EthIf轮询周期 1.EthIfGeneral参数的含义

如何看待2024年诺贝尔物理学奖颁给了机器学习与神经网络?

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于2024年诺贝尔物理学奖颁给了机器学习与神…

有没有检测吸烟的软件 ai视频检测分析厂区抽烟报警#Python

在现代厂区管理中&#xff0c;安全与规范是重中之重&#xff0c;而吸烟行为的管控则是其中关键一环。传统的禁烟管理方式往往依赖人工巡逻&#xff0c;效率低且存在监管死角&#xff0c;难以满足当下复杂多变的厂区环境需求。此时&#xff0c;AI视频检测技术应运而生&#xff0…