当前位置: 首页 > news >正文

Android12的ANR解析

0. 参考:

ANR分析
深入理解 Android ANR 触发原理以及信息收集过程

1.ANR的触发分类:

ANR分为4类:

  • InputDispatchTimeout:输入事件分发超时5s,包括按键和触摸事件。
  • BroadcastTimeout:比如前台广播在10s内未执行完成,后台60s
  • ServiceTimeout:前台服务在20s内未执行完成,后台服务未在200s内完成。
  • ContentProviderTimeout:contenProvider在publish后超时10s

2.ANR的触发机制

2.1基本概念

  • ANR 的检测和处理逻辑主要写在 AMS(Activity Manager Service) 进程中
  • 当超时事件触发后,AMS 会记录下应用主线程的状态,并调用 ActivityManagerService.appNotResponding() 方法来启动 ANR 处理流程。
  • 当应用程序在UI线程阻塞太长时间,就会弹出系统弹框,询问我们继续等待还是关闭应用程序,此时发生了ANR。

3.Service,BroadCast,Provider触发ANR:

3.1 ANR是在发生这些调用的时候,就启用"定时炸弹"。

  • 1.APP侧:
    在Activity中调用startService后,调用链:ContextImpl.startService()->ContextImpl.startServiceCommon()
    1. AMS侧:
      ActivityManagerService.startService()
      ->ActiveServices.startServiceLocked()
      ->ActiveServices.startServiceInnerLocked()
      ->ActiveServices.bringUpServiceLocked()
      ->ActivieServices.realStartServiceLocked()
      ->ActivieServices.realStartServiceLocked
  • 2.1 其中realStartServiceLocked主要通过向AMS发送一个Handler的延迟消息告诉AMS这个调用超时了。
    private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,boolean enqueueOomAdj) throws RemoteException {  // 2
...............// 记录此调用的开始---原因是 "create"// 从此处开始倒计时--发送handler消息到AMSbumpServiceExecutingLocked(r, execInFg, "create", null /* oomAdjReason */);
................// 通知APP启动Service,执行 handleCreateService// 调用到 ApplicationThread.scheduleCreateServicethread.scheduleCreateService(r, r.serviceInfo,mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),app.mState.getReportedProcState());
..........
  • 2.2 ActivieServices.bumpServiceExecutingLocked---->ActivieServices.scheduleServiceTimeoutLocked
    这个延迟时间就是1中说到的超时20s, 对应ActiveService的SERVICE_TIMEOUT变量;
    void scheduleServiceTimeoutLocked(ProcessRecord proc) {  // 2
...................Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);msg.obj = proc;mAm.mHandler.sendMessageDelayed(msg, proc.mServices.shouldExecServicesFg()? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);  // 发送延迟消息给AMS的Handler}
  • 2.3 scheduleCreateService
    会通过Binder调用回APP侧,最终会调用到ApplicationThread.scheduleCreateService
        public final void scheduleCreateService(IBinder token,ServiceInfo info, CompatibilityInfo compatInfo, int processState) {updateProcessState(processState, false);CreateServiceData s = new CreateServiceData();s.token = token;s.info = info;s.compatInfo = compatInfo;sendMessage(H.CREATE_SERVICE, s);  // 发送Handler消息}// 找到处理Handler消息的地方:handleCreateService最终会调用到这个Service的onCreate方法上。case CREATE_SERVICE:if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,("serviceCreate: " + String.valueOf(msg.obj)));}handleCreateService((CreateServiceData)msg.obj);  // 执行Service的回调onCreateTrace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);break;
  • 3 小结
    1)最开始调用了startService来触发AMS发送一个延迟为SERVICE_TIMEOUT(20s)的Handler事件,
    2)并最终回调到实现了这个Service的onCreate函数。
    3)如果超过了20s这个Handler消息还是没被取消,那么就会去触发AMS的ANR机制。

3.2 取消延迟发送的Handler消息, “取消炸弹”

  • 在上面描述的正常流程中,会调用到handleCreateService,最终会调用removeMessages取消3.1中的延迟发送的Handler消息
    private void handleCreateService(CreateServiceData data) { 
.....service.onCreate();   // 进入service的onCreate回调
....// Service启动完成,需要通知AMSActivityManager.getService().serviceDoneExecuting(data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
.....}public void serviceDoneExecuting(IBinder token, int type, int startId, int res) {synchronized(this) {if (!(token instanceof ServiceRecord)) {Slog.e(TAG, "serviceDoneExecuting: Invalid service token=" + token);throw new IllegalArgumentException("Invalid service token");}// 执行到ActivityManagerService的 serviceDoneExecuting方法mServices.serviceDoneExecutingLocked((ServiceRecord) token, type, startId, res, false);}}// ActivityManagerService的 serviceDoneExecuting方法private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,boolean finishing, boolean enqueueOomAdj) {
......// 将Handler消息取消mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
..........}

3.3 ANR的触发, “炸弹引爆”

  • 就是延迟发送的Handler消息处理,
    在ActivityManagerService.java可看到处理SERVICE_TIMEOUT_MSG的代码
            case SERVICE_TIMEOUT_MSG: {// 调用到ActiveServices的serviceTimeout方法mServices.serviceTimeout((ProcessRecord) msg.obj);} break;//com.android.server.am.ActiveServices.javavoid serviceTimeout(ProcessRecord proc) {................// 计算是否超时,if (anrMessage != null) {  // 超时// 触发超时机制,mAm是AMS,mAnrHelper是AnrHelpermAm.mAnrHelper.appNotResponding(proc, anrMessage);  }}
  • 调用到AnrHelper的appNotResponding方法,最终是调用到AmrConsumerThread().start方法来处理ANR
    void appNotResponding(ProcessRecord anrProcess, String annotation) {appNotResponding(anrProcess, null /* activityShortComponentName */, null /* aInfo */,null /* parentShortComponentName */, null /* parentProcess */,false /* aboveSystem */, annotation);}void appNotResponding(ProcessRecord anrProcess, String activityShortComponentName,ApplicationInfo aInfo, String parentShortComponentName,WindowProcessController parentProcess, boolean aboveSystem, String annotation) {synchronized (mAnrRecords) {mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,parentShortComponentName, parentProcess, aboveSystem, annotation));}startAnrConsumerIfNeeded();}private void startAnrConsumerIfNeeded() {if (mRunning.compareAndSet(false, true)) {new AnrConsumerThread().start();  // 启动AnrConsumerThread线程处理ANR}}
private class AnrConsumerThread extends Thread {@Overridepublic void run() {AnrRecord r;while ((r = next()) != null) {......//这里的r就是AnrRecordr.appNotResponding(onlyDumpSelf);......}}
}
private static class AnrRecord {void appNotResponding(boolean onlyDumpSelf) {//mApp是ProcessRecord,mErrorState是ProcessErrorStateRecordmApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,mParentShortComponentName, mParentProcess, mAboveSystem, mAnnotation,onlyDumpSelf);}
}

调用到ProcessErrorStateRecord.appNotResponding来弹出无响应弹出。或者dump操作啥的,就是平时见到的ANR

3.4 ANR做了哪些事情

  • 主要就是记录ANR错误的相关信息 + 弹出ANR的弹窗
  • 最终会调用到ProcessErrorStateRecord的appNotResponding,下面来研究ProcessErrorStateRecord.java的这个函数
    void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,String parentShortComponentName, WindowProcessController parentProcess,boolean aboveSystem, String annotation, boolean onlyDumpSelf) {  // 
........// 将ANR的相关信息记录到anr文件中StringBuilder info = new StringBuilder();
.....StringBuilder report = new StringBuilder();....................// 弹出ANR的Dialogif (mService.mUiHandler != null) {// Bring up the infamous App Not Responding dialogMessage msg = Message.obtain();msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;msg.obj = new AppNotRespondingDialog.Data(mApp, aInfo, aboveSystem);mService.mUiHandler.sendMessageDelayed(msg, anrDialogDelayMs);}}}
  • 小结:
    1)在调用得时候发送了延迟HANDLER消息:SERVICE_TIMEOUT_MSG
    2)到了延时时间,还未remove掉此handler消息,那么就会触发SERVICE_TIMEOUT_MSG的处理。
    3)最终调用到AnrHelper的appNotResponding来处理ANR

4.Input触发ANR:

4.1 从linux侧获取input事件

  • 如inputFlinger 讲解的内容,InputReader线程通过EventHub监听/dev/input读取输入事件,监听到事件则把消息传到InputDispatcher(即InputReader把消息写入mInBoundQueue队列)
  • InputDispatcher负责把输入事件分发给 目标应用窗口。

4.2 ANR相关

  • InputDispatcher的流程:
    input的ANR流程

4.3 ANR触发 代码流程梳理

  • InputDispatcher中的方法调用:
    1.有等待获取焦点的应用:当前时间超过Timeout,调用processNoFocusedWindowAnrLocked() 进一步确认
    2.存在window:当前时间超过事件响应的超时时间。调用onAnrLocked() 进一步确认。
> frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp// 检查是否有任何连接的等待队列具有太旧的事件。如果我们等待事件被确认的时间超过窗口超时,
// 请引发 ANR。返回我们下次应该醒来的时间。
nsecs_t InputDispatcher::processAnrsLocked() {const nsecs_t currentTime = now();nsecs_t nextAnrCheck = LONG_LONG_MAX; // 下一次检查anr的时间// 检查我们是否正在等待一个聚焦窗口出现。如果等待时间过长就报 ANRif (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {if (currentTime >= *mNoFocusedWindowTimeoutTime) {// 场景1: 触发noFocusedWindow的anrprocessNoFocusedWindowAnrLocked();mAwaitedFocusedApplication.reset();mNoFocusedWindowTimeoutTime = std::nullopt;return LONG_LONG_MIN;} else {// 请继续等待。我们将在mNoFocusedWindowTimeoutTime到来时放弃该事件。nextAnrCheck = *mNoFocusedWindowTimeoutTime;}}// 检查是否有任何连接 ANR 到期,mAnrTracker 中保存所有已分发事件(未被确认消费的事件)的超时时间nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());if (currentTime < nextAnrCheck) { // 最有可能的情况// 一切正常,在 nextAnrCheck 再检查一次return nextAnrCheck;}// 如果我们到达这里,则连接无响应。sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());// 停止为此无响应的连接唤醒mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());// 场景2: 触发ANRonAnrLocked(connection);return LONG_LONG_MIN;
}

4.4 ANR在InputDispatcher的调用栈

dispatchOnce findFocusedWindowTargetsLocked processAnrsLocked processNoFocusedWindowAnrLocked onAnrLocked postCommandLocked updateLastAnrStateLocked processConnectionUnresponsiveLocked sendMonitorUnresponsiveCommandLocked sendWindowUnresponsiveCommandLocked runCommandsLockedInterruptible command 赋值mNoFocusedWindowTimeoutTime return 触发ANR检查 case 1: 失去焦点时间(mNoFocusedWindowTimeoutTime)过长 onAnrLocked(application) 向mCommandQueue中写入数据 return case 2: 连接过期,触发ANR, onAnrLocked(connection) onAnrLocked(connection) return 记录ANR相关 case 1 case 2 return 捕获 ANR 时 InputDispatcher 状态的记录。 处理postCommandLocked写入的 mCommandQueue 调用command处理 dispatchOnce findFocusedWindowTargetsLocked processAnrsLocked processNoFocusedWindowAnrLocked onAnrLocked postCommandLocked updateLastAnrStateLocked processConnectionUnresponsiveLocked sendMonitorUnresponsiveCommandLocked sendWindowUnresponsiveCommandLocked runCommandsLockedInterruptible command

6.排查思路:

  1. adb shell getevent后,点击屏幕,如果有打印,代表驱动是可以拿到点击事件的。如果没有则需要内核的同事查看。如果有则代表是Android的问题。

相关文章:

Android12的ANR解析

0. 参考&#xff1a; ANR分析 深入理解 Android ANR 触发原理以及信息收集过程 1.ANR的触发分类: ANR分为4类&#xff1a; InputDispatchTimeout&#xff1a;输入事件分发超时5s,包括按键和触摸事件。BroadcastTimeout&#xff1a;比如前台广播在10s内未执行完成&#xff0…...

初学人工智不理解的名词3

TTS领域的名词 from gpt-4o 在 TTS&#xff08;文本到语音合成&#xff09; 领域&#xff0c;以下是 CFM、One-Step 蒸馏 和 ReFlow 的含义和作用的详细解释&#xff1a; 1. CFM&#xff08;Consistent Flow Matching&#xff09; Consistent Flow Matching&#xff08;一致流…...

ADS项目笔记 1. 低噪声放大器LNA天线一体化设计

在传统射频结构的设计中&#xff0c;天线模块和有源电路部分相互分离&#xff0c;两者之间通过 50 Ω 传输线级联&#xff0c;这种设计需要在有源电路和天线之间建立无源网络&#xff0c;包括天线模块的输入匹配网络以及有源电路的匹配网络。这些无源网络不仅增加了系统的插入损…...

J.U.C - 深入解读阻塞队列实现原理源码

文章目录 Pre生产者-消费者模式阻塞队列 vs 普通队列JUC提供的7种适合与不同应用场景的阻塞队列插入操作&#xff1a;添加元素到队列中移除操作&#xff1a;从队列中移除元素。 ArrayBlockingQueue源码解析类结构指定初始容量及公平/非公平策略的构造函数根据已有集合初始化队列…...

【大语言模型学习】LORA微调方法

LORA: Low-Rank Adaptation of Large Language Models 摘要 LoRA (Low-Rank Adaptation) 提出了一种高效的语言模型适应方法,针对预训练模型的适配问题: 目标:减少下游任务所需的可训练参数,降低硬件要求。方法:冻结预训练模型权重,注入低秩分解矩阵,从而在不影响推理…...

Spring Boot【一】

Spring Boot全局配置文件 application.properties 是 Spring Boot 的标准配置文件&#xff0c;用于集中管理应用程序的配置属性。它的主要作用是将配置信息与代码分离&#xff0c;使得应用程序更具可维护性和可配置性。 Application.yaml配置文件 YAML文件格式是JSON超集文件…...

H.265流媒体播放器EasyPlayer.js H.264/H.265播放器chrome无法访问更私有的地址是什么原因

EasyPlayer.js H5播放器&#xff0c;是一款能够同时支持HTTP、HTTP-FLV、HLS&#xff08;m3u8&#xff09;、WS、WEBRTC、FMP4视频直播与视频点播等多种协议&#xff0c;支持H.264、H.265、AAC、G711A、MP3等多种音视频编码格式&#xff0c;支持MSE、WASM、WebCodec等多种解码方…...

【大数据学习 | HBASE高级】rowkey的设计,hbase的预分区和压缩

1. rowkey的设计 ​ RowKey可以是任意字符串&#xff0c;最大长度64KB&#xff0c;实际应用中一般为10~100bytes&#xff0c;字典顺序排序&#xff0c;rowkey的设计至关重要&#xff0c;会影响region分布&#xff0c;如果rowkey设计不合理还会出现region写热点等一系列问题。 …...

Dart:字符串

字符串&#xff1a;单双引号 String c hello \c\; // hello c&#xff0c;单引号中使用单引号&#xff0c;需要转义\ String d "hello c"; // hello c&#xff0c;双引号中使用单引号&#xff0c;不需要转义 String e "hello \“c\”"; // hell…...

平衡二叉搜索树之 红黑 树的模拟实现【C++】

文章目录 红黑树的简单介绍定义红黑树的特性红黑树的应用 全部的实现代码放在了文章末尾准备工作包含头文件类的成员变量和红黑树节点的定义 构造函数和拷贝构造swap和赋值运算符重载析构函数findinsert【重要】第一步&#xff1a;按照二叉搜索树的方式插入新节点第二步&#x…...

2:Vue.js 父子组件通信:让你的组件“说话”

上一篇我们聊了如何用 Vue.js 创建一个简单的组件&#xff0c;这次咱们再往前走一步&#xff0c;讲讲 Vue.js 的父子组件通信。组件开发里&#xff0c;最重要的就是让组件之间能够“说话”&#xff0c;数据能流通起来。废话不多说&#xff0c;直接开干&#xff01; 父组件传数据…...

6. Keepalived配置Nginx自动重启,实现7x24提供服务

一. Keepalived配置Nginx自动重启,实现7x24提供服务 1.编写不停的检查nginx服务器状态,停止并重启,重启失败后则停止keepalived脚本 cd /etc/keepalived/ vim check_nginx_alive_or_not.sh #---内容如下:--------------- #!/bin/bash A=`ps -C nginx --no-header |wc -l...

【PS】蒙版与通道

内容1&#xff1a; 、选择蓝色通道并复制&#xff0c;对复制的蓝色通道ctrli进行反向选择&#xff0c;然后ctrll调整色阶。 、选择载入选区&#xff0c;然后点击rgb。 、点击蒙版 、点击云彩图层调整位置 、点击色相/饱和度&#xff0c;适当调整 、最后使用滤镜等功能添加光圈…...

C++创建型模式之生成器模式

解决的问题 生成器模式&#xff08;Builder Pattern&#xff09;主要解决复杂对象的构建问题。当一个对象的创建过程非常复杂&#xff0c;涉及多个步骤和多个部件时&#xff0c;使用生成器模式可以将对象的构建过程与其表示分离&#xff0c;使得同样的构建过程可以创建不同的表…...

鸿蒙NEXT应用示例:切换图片动画

【引言】 在鸿蒙NEXT应用开发中&#xff0c;实现图片切换动画是一项常见的需求。本文将介绍如何使用鸿蒙应用框架中的组件和动画功能&#xff0c;实现不同类型的图片切换动画效果。 【环境准备】 电脑系统&#xff1a;windows 10 开发工具&#xff1a;DevEco Studio NEXT B…...

postgresql(功能最强大的开源数据库)继承特性和分区实现

PostgreSQL实现了表继承&#xff0c;在多重表继承下&#xff0c;对上亿条不同类别的数据条目进行按型号、按月份双层分区管理&#xff0c;既可在总表查阅所有条目的共有字段&#xff0c;也可在各类型字表查询附加字段&#xff0c;非常高效。 分区是通过继承的方式来实现的&…...

论文笔记(五十六)VIPose: Real-time Visual-Inertial 6D Object Pose Tracking

VIPose: Real-time Visual-Inertial 6D Object Pose Tracking 文章概括摘要I. INTRODACTIONII. 相关工作III. APPROACHA. 姿态跟踪工作流程B. VIPose网络 文章概括 引用&#xff1a; inproceedings{ge2021vipose,title{Vipose: Real-time visual-inertial 6d object pose tra…...

微服务治理详解

文章目录 什么是微服务架构为什么要使用微服务单体架构如何转向微服务架构服务治理服务治理治的是什么服务注册与发现服务熔断降级服务网关服务调用服务负载均衡服务配置中心 微服务解决方案SpringCloud体系EurekaHystrixGatewayOpenFeignRibbonConfig SpringCloud Alibaba体系…...

“南海明珠”-黄岩岛(民主礁)领海基线WebGIS绘制实战

目录 前言 一、关于岛屿的基点位置 1、领海基点 二、基点坐标的转换 1、最底层的左边转换 2、单个经纬度坐标点转换 3、完整的转换 三、基于天地图进行WebGIS展示 1、领海基点的可视化 2、重要城市距离计算 四、总结 前言 南海明珠黄岩岛&#xff0c;这座位于南海的…...

Oracle数据库 创建dblink的过程及其用法详解

前言 ‌dblink是Oracle数据库中用于连接不同数据库实例的一种机制‌。通过dblink&#xff0c;用户可以在一个数据库实例中直接查询或操作另一个数据库实例中的表、视图或存储过程。‌ dblink的作用主要体现在以下几个方面&#xff1a; ‌跨数据库操作‌&#xff1a;允许用户…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

&#x1f9e0; 智能合约中的数据是如何在区块链中保持一致的&#xff1f; 为什么所有区块链节点都能得出相同结果&#xff1f;合约调用这么复杂&#xff0c;状态真能保持一致吗&#xff1f;本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

Angular微前端架构:Module Federation + ngx-build-plus (Webpack)

以下是一个完整的 Angular 微前端示例&#xff0c;其中使用的是 Module Federation 和 npx-build-plus 实现了主应用&#xff08;Shell&#xff09;与子应用&#xff08;Remote&#xff09;的集成。 &#x1f6e0;️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

Docker 本地安装 mysql 数据库

Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker &#xff1b;并安装。 基础操作不再赘述。 打开 macOS 终端&#xff0c;开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

无人机侦测与反制技术的进展与应用

国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机&#xff08;无人驾驶飞行器&#xff0c;UAV&#xff09;技术的快速发展&#xff0c;其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统&#xff0c;无人机的“黑飞”&…...