记Android12上一个原生bug引起的system_server crash
一. 现象描述
近日测试上报一个几乎必现的crash,描述如下:
现象: launcher编辑状态与锁屏解锁交互时系统概率性重启
操作步骤:
- 进入launcher组件编辑状态
- 按电源键灭屏后亮屏,锁屏界面上滑解锁
- launcher编辑状态向右或向左滑动
- 重复1,2,3 步骤多次操作,观察系统重启情况
二. 初步分析
随后在提供的日志中搜索“crash”,果然发现了以下的报错信息打印:
从日志中大概能分析出3点重要的信息
- crash 发生在system_server进程
- 报错打印是Could not find consume time for seq=3513
- crash大概发生在input时间在被client端处理完毕后向 inputdispatchar 发送 finsh的阶段
除此之外,在大概5s后,crash进一步引起了anr,根据anr的提示信息:** ANR in gesture monitor owned by pid:1045. Reason: PointerEventDispatcher0** 可进一步推断,**crash发生在 手势处理的过程中向 inputdispatchar 发送 finsh的阶段
**
三. 分析crash发生的原因
猜想1
根据报错的提示信息和堆栈打印,在结合代码,报错发生在frameworks/native/libs/input/InputTransport.cpp中
上文提到发生crash发生在 手势处理的过程中向 inputdispatchar 发送 finsh的阶段,大概流程如下
- client端梳理完事件代用finishInputEvent随后进一步来到图中1的位置,
- 根据seq在容器mConsumeTimes中尝试获取一个时间戳
- 向input端发送finish消息
- 从容器mConsumeTimes中移除该seq对应的对象
crash发生在阶段2,从容器mConsumeTime获取不到该seq对应的对象,然后报错 Could not find consume time for seq=3513,然后根据该类的其他代码得知,mConsumeTime 是一个map类型的容器,在事件分发前,会以事件的seq为key,当前时间为value添加一条记录,当时间被client端处理完毕后然后从该容器中移除对应seq的记录。
结合代码和报错的注释,初步猜想是对应事件的finish调用了两次引发了crash:
为了验证猜想,随后放开了input相关的日志,进行复现,发现发生crash时 对应seq的事件确实调用了两次finish:
猜想是正确的,现在的问题变成了为什么status_t InputConsumer::sendFinishedSignal发生了两次,
进一步猜想
进一步阅读代码得知frameworks/base/core/jni/android_view_InputEventReceiver.cpp中会调用到 InputConsumer::sendFinishedSigna.
对于普通的touch事件来说,调用到InputConsumer::sendFinishedSigna.有两种情况:
- 情况1
事件被java层的frameworks/base/core/java/android/view/InputEventReceiver.java正常处理完毕后,主动调用finishInputEvent随后再来到natvie层调用到 InputConsumer::sendFinishedSigna. 如下图
- 情况二
在时间分发阶段,构造了一个java层的事件对象,然后交给java层去处理,java层处理阶段出现了异常,native层的NativeInputEventReceiver捕获到了该异常,NativeInputEventReceiver会发起一次InputConsumer.sendFinishedSignal
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {.......bool skipCallbacks = false;for (;;) {uint32_t seq;........jobject inputEventObj;switch (inputEvent->getType()) {// 构造 MotionEvent 对象case AINPUT_EVENT_TYPE_MOTION: {if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());}MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {*outConsumedBatch = true;}inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);break;}........default:assert(false); // InputConsumer should prevent this from ever happeninginputEventObj = nullptr;}if (inputEventObj) {if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());}env->CallVoidMethod(receiverObj.get(),// 调用java方法,分发事件gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);if (env->ExceptionCheck()) {// 捕获到java层的异常,使得skipCallbacks = true;ALOGE("Exception dispatching input event.");skipCallbacks = true;}env->DeleteLocalRef(inputEventObj);} else {ALOGW("channel '%s' ~ Failed to obtain event object.",getInputChannelName().c_str());skipCallbacks = true;}}if (skipCallbacks) {ALOGD("NativeInputEventReceiver consumeEvents seq=%u ", seq);// 因为前面捕获到异常,skipCallbacks == ture。 调用mInputConsumer.sendFinishedSignalmInputConsumer.sendFinishedSignal(seq, false);}}
}
在上文两处中添加log,复现crash log如下:
可见对于seq 3513 ,正常情况和发生异常的情况都调用了一次InputConsumer.sendFinishedSignal!!!
总结一下目前的线索:在桌面手势分发seq == 3513的事件阶段出现了异常,异常被native层的NativeInputEventReceiver得知,然后发起了一次sendFinishedSignal, 同时java层在对seq ==3513事件处理结束后,主动调用了一次sendFinishedSignal,两次调用sendFinishedSignal进而引发crash。
有新的疑问涌出:
- 桌面手势分发seq == 3513的事件阶段出现了什么异常
- 为什么正常的情况和异常的情况都触发?
四. 发现端倪
在native检测到异常的分支里面添加对错误的打印,然后复现问题,发现如下打印,原来是上层发生可空指针问题
但,这个空指针问题怎么会使得 异常分支和正常分支的sendFnishSignal都触发呢?随后在PointerEventDispatcher这个类中发现端倪:
frameworks/base/services/core/java/com/android/server/wm/PointerEventDispatcher.java
@Overridepublic void onInputEvent(InputEvent event) {try {if (event instanceof MotionEvent&& (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {MotionEvent motionEvent = (MotionEvent) event;if (ENABLE_PER_WINDOW_INPUT_ROTATION) {final int rotation = mDisplayContent.getRotation();if (rotation != Surface.ROTATION_0) {mDisplayContent.getDisplay().getRealSize(mTmpSize);motionEvent = MotionEvent.obtain(motionEvent);motionEvent.transform(MotionEvent.createRotateMatrix(rotation, mTmpSize.x, mTmpSize.y));}}PointerEventListener[] listeners;synchronized (mListeners) {if (mListenersArray == null) {mListenersArray = new PointerEventListener[mListeners.size()];mListeners.toArray(mListenersArray);}listeners = mListenersArray;}for (int i = 0; i < listeners.length; ++i) {listeners[i].onPointerEvent(motionEvent);}}} finally { // catch呢?????finishInputEvent(event, false);}}
如上,分发完事件后,无论是否发生异常都会在finally中调用finishInputEvent,同时虽然有try, final,但没有catch,这就意味着该段逻辑并未捕获任何类型的异常!!!!,真相大白:
- 为什么正常情况的sendFnishSignal会被触发? 事件处理结束后触发了 finally 执行了finishInputEvent,随后进一步触发sendFnishSignal
- 为什么异常情况的sendFnishSignal也会被触发? 首先事件分发阶段发生了异常,但PointerEventDispatcher并没有catch住相关的异常,随后异常被native层的NativeInputEventReceiver获知,进入了异常分支也触发了一次sendFnishSignal。
但,疑问还在继续,这似乎是google的原生的一个bug,为什么谷歌的代码这样处理?无论正常还是异常情况下 java层调用finishInputEvent 触发一次sendFnishSignal,然后native层检测到异常 再触发一次 sendFnishSignal,Java层和native层的代码不像同一个人写的,都考虑到了异常情况要去触发sendFnishSignal,然后就发生了在分发阶段出现异常时同一事件就发生了两次 sendFnishSignal
这么明显的bug,谷歌应该能发现吧? 果然:
谷歌的修复: 发生异常时native层不再触发sendFnishSignal
相关文章:
记Android12上一个原生bug引起的system_server crash
一. 现象描述 近日测试上报一个几乎必现的crash,描述如下: 现象: launcher编辑状态与锁屏解锁交互时系统概率性重启 操作步骤: 进入launcher组件编辑状态按电源键灭屏后亮屏,锁屏界面上滑解锁launcher编辑状态向右或向左滑动重复1,2&#x…...
代码随想录算法训练营第六天|Leetcode454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和
15. 三数之和 建议:本题虽然和 两数之和 很像,也能用哈希法,但用哈希法会很麻烦,双指针法才是正解,可以先看视频理解一下 双指针法的思路,文章中讲解的,没问题 哈希法很麻烦。 题目链接/文章讲…...
大数据环境(单机版) Flume传输数据到Kafka
文章目录 前言一、准备二、安装三、配置环境变量四、修改配置4.1、kafka配置4.2、Flume配置 五、启动程序5.1、启动zk5.2、启动kafka5.3、启动flume 六、测试6.1、启动一个kafka终端,用来消费消息6.2、写入日志 其他 前言 flume监控指定目录,传输数据到…...
计算机毕业设计SpringBoot+Vue.js高校教师科研管理系统(源码+文档+PPT+讲解)
温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…...
C++课程设计【宿舍管理查询软件】
宿舍管理查询软件 一、题目描述二、源码以及说明宿舍管理查询软件设计与实现1. 系统设计思路1.1 功能需求1.2 数据结构2. 系统实现3. 代码说明3.1 数据结构3.2 功能实现3.3 文件存储4. 示例运行输入输出5. 总结其他QT文章推荐一、题目描述 (一)问题描述 为宿舍管理人员编写一…...
数据挖掘校招面经一
写在前面:其实数据挖掘、风控、机器学习算法与搜广推的八股还是有重合的部分,毕竟都是面对结构化数据。特别是我自己是做竞赛的,平时LGBM、CatBoost用的挺多的,所以感觉这些八股还是有必要看看,建议大家也可以看一下。…...
迷你世界脚本对象库接口:ObjectLib
对象库接口:ObjectLib 迷你世界 更新时间: 2023-04-26 20:21:09 具体函数名及描述如下: 序号 函数名 函数描述 1 getAreaData(...) 获取区域数据 2 getPositionData(...) 获取位置数据 3 getLivingData(...) 获取生物数据 4 getItemDat…...
VSCode知名主题带毒 安装量900万次
目前微软已经从 Visual Studio Marketplace 中删除非常流行的主题扩展 Material Theme Free 和 Material Theme Icons,微软称这些主题扩展包含恶意代码。 统计显示这些扩展程序的安装总次数近 900 万次,在微软实施删除后现在已安装这些扩展的开发者也会…...
C#—csv文件格式操作实例【在winform表格中操作csv】
C#—csv文件格式操作实例【在winform表格中操作csv】 实例一 实例效果 当在winform界面中点击读取按钮时 将csv中的所有数据读取出来放置在datagridview控件,可以在datagridview控件中编辑数据,当点击保存按钮时 将datagridview控件中的所有数据存储在…...
Redis设计与实现-数据结构
Redis数据结构 1、RedisObject对象2、简单动态字符串2.1 SDS定义2.2 SDS与C语言的区别2.3 SDS的空间分配策略2.3.1 空间预分配2.3.2 惰性空间释放 2.4 SDS的API 3、链表3.1 链表的定义3.2 链表的API 4、字典4.1 字典的定义4.2 哈希算法4.3 哈希表的扩缩4.3.1 哈希表扩缩的判断依…...
Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器
Ubuntu20.04双系统安装及软件安装(四):国内版火狐浏览器 Ubuntu系统会自带火狐浏览器,但该浏览器不是国内版的,如果平常有记录书签、浏览记录、并且经常使用浏览器插件的习惯,建议重装火狐浏览器为国内版的…...
C语言100天练习题【记录本】
C语言经典100题(手把手 编程) 可以在哔哩哔哩找到 已解决的天数:一,二,五,六 下面的都是模模糊糊的 可以学学这些算法,我是算法白痴,但是我不是白痴,可以学ÿ…...
基于CURL命令封装的JAVA通用HTTP工具
文章目录 一、简要概述二、封装过程1. 引入依赖2. 定义脚本执行类 三、单元测试四、其他资源 一、简要概述 在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一款很强大的http命令行工具。它支持文件的上传和下载,是综合传输工具&…...
SQL刷题:自连接(Self-Join)--通过将 同一张表连接两次,比较不同行之间的数据关系
例题: 表:Employee ---------------------- | Column Name | Type | ---------------------- | id | int | | name | varchar | | salary | int | | managerId | int | ---------------------- id 是该表的主键…...
避坑!用Docker搞定PHP开发环境搭建(Mac、Docker、Nginx、PHP-FPM、XDebug、PHPStorm、VSCode)
本次更新主要是对环境版本进行了更新,例如php 7.3.7升级到了7.3.8,另外之前的版本有同学踩了坑,主要是官方docker镜像php:7.3.7-fpm和php:7.3.8-fpm使用了不同版本的debian,后面会提到,请各位同学留意。 因为最近换电脑…...
第七节:基于Winform框架的串口助手小项目---协议解析《C#编程》
介绍 文章上所说的串口助手,工程文件资源-CSDN文库 目标 代码实现 private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e){if (isRxShow false) return;// 1,需要读取有效的数据 BytesToReadbyte[] dataTemp new byte[serialPor…...
pt-archiver删除数据库的数据表/各种报错类型
这篇帖子是前面文的一部分延申 mysqlimport导入一亿数据的csv文件/一行命令删除表-CSDN博客 如需转载,标记出处 目录 pt-archiver命令格式 如果执行后出现下面报错 1)Cannot find an ascendable index in table at /usr/bin/pt-archiver line 3233. …...
STM32Cubemx配置E22-xxxT22D lora模块实现定点传输
文章目录 一、STM32Cubemx配置二、定点传输**什么是定点传输?****定点传输的特点****定点传输的工作方式****E22 模块定点传输配置****如何启用定点传输?****示例** **应用场景****总结** **配置 1:C0 00 07 00 02 04 62 00 17 40****解析** …...
模块和端口
1、模块 模块内部的5个组成是:变量声明 数据流语句 低层模块实例 函数和任务 行为语句 SR锁存器 timescale 1ns / 1psmodule SR_latch(input wire Sbar ,input wire Rbar ,output wire Q ,output wire Qbar);nand…...
Android+SpringBoot的老年人健康饮食小程序平台
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统介绍 我将从经济、生活节奏、技术融合等方面入手,详细阐述居家养老管理模式兴起的…...
[machine learning] MACS、MACs、FLOPS、FLOPs
本文介绍机器学习中衡量一个模型计算复杂度的四个指标:MACS、MACs、FLOPS、FLOPs。 首先从含义上讲,可以分类两类:MACS/FLOPS和MACs/FLOPs。MACs/FLOPs表示总的操作数(后缀s可以看成是表示复数),MACS/FLOPS表示每秒可以执行的操作…...
PostgreSQL10 物理流复制实战:构建高可用数据库架构!
背景 PostgreSQL 10 在高可用架构中提供了物理复制,也称为流复制(Streaming Replication),用于实现实例级别的数据同步。PostgreSQL 复制机制主要包括物理复制和逻辑复制:物理复制依赖 WAL 日志进行物理块级别的同步&…...
STM32---FreeRTOS中断管理试验
一、实验 实验目的:学会使用FreeRTOS的中断管理 创建两个定时器,一个优先级为4,另一个优先级为6;注意:系统所管理的优先级范围 :5~15 现象:两个定时器每1s,打印一段字符串&#x…...
Linux常见操作命令(1)
(一)常用命令: 1.Tab 键可以实现自动补齐和提示,要合理使用 2.方向键(上下)来切换前后执行过的命令 (二)查看命令 一共有三个:ls, cd , pwd 。 1.ls:列出目录内容,包括参数-l(详细…...
SPI驱动(二) -- SPI驱动程序模型
文章目录 参考资料:一、SPI驱动重要数据结构1.1 SPI控制器数据结构1.2 SPI设备数据结构1.3 SPI驱动数据结构 二、SPI 驱动框架2.1 SPI控制器驱动程序2.2 SPI设备驱动程序 三、总结 参考资料: 内核头文件:include\linux\spi\spi.h 一、SPI驱…...
Qt中txt文件输出为PDF格式
main.cpp PdfReportGenerator pdfReportGenerator;// 加载中文字体if (QFontDatabase::addApplicationFont(":/new/prefix1/simsun.ttf") -1) {QMessageBox::warning(nullptr, "警告", "无法加载中文字体");}// 解析日志文件QVector<LogEntr…...
SpringBoot 校园新闻网站
收藏关注不迷路!! 🌟文末获取源码数据库🌟 感兴趣的可以先收藏起来,还有大家在毕设选题(免费咨询指导选题),项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多…...
JAVA面经2
ConcurrentHashMap 并发程序出现问题的根本原因 线程池 线程池的执行原理(核心参数) 线程池的常见阻塞队列 ArrayBlockingQueue插入和删除数据,只采用了一个lock,而LinkedBlockingQueue则是在插入和删除分别采用了putLock和takeL…...
NVIDIA(英伟达) GPU 芯片架构发展史
GPU 性能的关键参数 CUDA 核心数量(个):决定了 GPU 并行处理能力,在 AI 等并行计算类业务下,CUDA 核心越多性能越好。 显存容量(GB):决定了 GPU 加载数据量的大小,在 AI…...
C++设计一:日期类Date实现
一、引言与概述 1 引言 日期操作是软件开发中的常见需求,如日程管理、数据统计等场景均需处理日期的比较、偏移及合法性校验。为简化此类操作,本文设计了一个高效且类型安全的C日期类Date。 该类通过构造函数内嵌合法性检查,确保对象初始状…...
关于2023新版PyCharm的使用
考虑到大家AI编程的需要,建议大家安装新版Python解释器和新版PyCharm,下载地址都可以官网进行: Python:Download Python | Python.org(可以根据需要自行选择,建议选择3.11,保持交流版本一致&am…...
【Azure 架构师学习笔记】- Azure Databricks (15) --Delta Lake 和Data Lake
本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 接上文 【Azure 架构师学习笔记】- Azure Databricks (14) – 搭建Medallion Architecture part 2 前言 ADB 除了UC 这个概念之外,前面【Azure 架构师学习笔记】- Azure Databricks (1…...
一文了解Conda使用
一、Conda库频道 conda的软件频道是存储软件包的远程位置,当在Conda中安装软件包时,它会从指定的频道中下载和提取软件包。频道包含了各种软件包,不同的频道可能提供不同版本的软件包,用户可以根据需要选择适合的版本。 常见 Co…...
SP导入智能材质球
智能材质球路径 ...\Adobe Substance 3D Painter\resources\starter_assets\smart-materials 放入之后就会自动刷新...
记录一次Spring事务失效导致的生产问题
一、背景介绍 公司做的是“聚合支付”业务,对接了微信、和包、数字人民币等等多家支付机构,我们提供统一的支付、退款、自动扣款签约、解约等能力给全国的省公司、机构、商户等。 同时,需要做对账功能,即支付机构将对账文件给到…...
腾讯云物联网平台(IoT Explorer)设备端使用
1、直接看图流程 2、跑起来demo,修改产品id,设备名称,设备秘钥。 3、连接部分 4、修改默认地址和端口 sdk里面的地址默认是带着产品ID拼接的,咱们现在中铁没有泛域名解析,要改下这里。把+productID都去掉,然后地址里的.也去掉。...
ML.NET库学习023: ONNX Runtime 中 C++ 辅助函数解析:Span 类与张量操作
文章目录 ML.NET库学习023: ONNX Runtime 中 C 辅助函数解析:Span 类与张量操作主题项目主要目的和原理项目概述实现的主要功能关键函数代码结构 主要功能与步骤Span 类的实现张量大小计算数据加载与处理准确性评估 数据集的使用以下是逐步解释ÿ…...
利用opencv_python(pdf2image、poppler)将pdf每页转为图片
1、安装依赖pdf2image pip install pdf2image 运行.py报错,因为缺少了poppler支持。 2、安装pdf2image的依赖poppler 以上命令直接报错。 改为手工下载: github: Releases oschwartz10612/poppler-windows GitHub 百度网盘: 百度网盘…...
告别GitHub连不上!一分钟快速访问方案
一、当GitHub抽风时,你是否也这样崩溃过? 😡 npm install卡在node-sass半小时不动😭 git clone到90%突然fatal: early EOF🤬 改了半天hosts文件,第二天又失效了... 根本原因:传统代理需要复杂…...
学习DeepSeek V3 与 R1 核心区别(按功能维度分类)
一、定位与架构 V3(通用型模型) 定位:多模态通用大模型,擅长文本生成、多语言翻译、智能客服等多样化任务12。架构:混合专家(MoE)架构,总参数 6710 亿,每次…...
Linux总结
1 用户与用户组管理 1.1 用户与用户组 //linux用户和用户组 Linux系统是一个多用户多任务的分时操作系统 使用系统资源的用户需要账号进入系统 账号是用户在系统上的标识,系统根据该标识分配不同的权限和资源 一个账号包含用户和用户组 //用户分类 超级管理员 UID…...
web高可用集群项目(数据库主从同步、文件共享存储、nginx动静分离+负载均衡+高可用)
一、项目环境 二、环境准备 主机名IP地址备注openEuler-1192.168.121.11主负载调度器openEuler-2192.168.121.12副负载调度器openEuler-3192.168.121.13web-1(静态)openEuler-4192.168.121.14web-2(静态)openEuler-5192.168.121.…...
如何快速上手RabbitMQ 笔记250304
如何快速上手RabbitMQ 要快速上手 RabbitMQ,可以按照以下步骤进行,从安装到基本使用逐步掌握核心概念和操作: 1. 理解核心概念 Producer(生产者):发送消息的程序。Consumer(消费者)…...
PPT小黑第26套
对应大猫28 层次级别是错的,看着是十页,导入ppt之后四十多页 选中所有 红色蓝色黑色 文本选择标题:选择 -格式相似文本(检查有没有漏选 漏选的话 按住ctrl 点下一个) 要求新建幻灯片中不包含原素材中的任何格式&…...
甘特图开发代码(测试版)
场景:要实现的功能就是单行数据能左右拖动。 流程五个:ABCDE。(对应:Charter开发、概念和计划、初样开发、正样开发、验证) 1、A有开始时间,结束时间。B的开始时间必须是A的结束时间(相等或者…...
鸿蒙与DeepSeek深度整合:构建下一代智能操作系统生态
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 https://www.captainbed.cn/north 目录 技术融合背景与价值鸿蒙分布式架构解析DeepSeek技术体系剖析核心整合架构设计智能调度系统实现…...
Docker Desktop常见问题记录
1.docker pull报错,无法连接https://registry-1.docker.io/v2/ 报错信息如下: Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection(Client.Timeout exceeded …...
Qt 的 Lambda 捕获局部变量导致 UI 更新异常的分析与解决
1. 问题描述 在 Qt 开发中,我们通常会使用 QTimer 进行周期性 UI 更新。例如,下面的代码用于在检测游戏窗口时,在 UI 界面上显示动态变化的“正在检测游戏窗口...”的文本,每 300 毫秒更新一次。 void MainWindow::detectAndPopulateGameList() {ui->game_record_stac…...
RAGflow采用docker-compose-continuous方式pull,把服务器充满了
采用docker-compose-continuous在后台下载,导致服务器被充满。 原因分析: 如果网络不稳定,可能导致下载任务异常中断,而 systemd 服务会不断重启并重新下载,从而占用大量空间。如果网络问题无法解决,可以…...
【第12节】C++设计模式(结构型模式)-Proxy(代理)模式
一、问题背景 使用 Proxy 模式优化对象访问 在某些情况下,直接访问对象可能会导致性能问题或安全性问题。Proxy 模式(代理模式)通过引入一个代理对象来控制对原始对象的访问,从而解决这些问题。以下是几种典型的应用场景…...