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

客户端设计(下):场景流派与实战设计方式

客户端架构:为什么、什么时候、怎么做https://blog.csdn.net/mix39/article/details/161257993客户端设计(上):MVC/MVP/MVVM 与高内聚低耦合https://blog.csdn.net/mix39/article/details/161257807客户端设计(中):OOP、SOLID 与设计模式https://blog.csdn.net/mix39/article/details/148036409

客户端设计(下):场景流派与实战设计方式https://blog.csdn.net/mix39/article/details/161322269?spm=1001.2014.3001.5501

前言

ok,我们前面三张文章讲了为什么要做架构、什么时候需要架构和怎么做、常用设计原则和设计模式,感兴趣可以点击上方链接再看看,再讲一下这些年遇到的具体的问题和解决策略。

本文涉及到代码的地方均已微信为例,提供想象空间,并不是真的业务代码,只抽象出这些年见过的一些常见公共通用架构设计能力。写过安卓app或者客户端app代码的人都会,俺做一下整理归纳。

8.6 大型互联网 App

核心矛盾:多团队并行 + 快速迭代 + 稳定性要求高

一句话:互联网 App 的设计重点是"怎么让几百人同时改一个 App 还不炸"。

设计重点

怎么做

组件化

业务组件 + 基础组件,每个组件独立工程,api 模块隔离

路由方案

统一路由表 + 拦截器链,跨组件跳转降级可控

配置中心

所有开关动态下发,支持灰度/AB/秒级回滚

降级体系

每个关键路径多层兜底,动画→图→红点→无

启动治理

有向无环拓扑排序 + 懒加载 + 分级初始化

监控体系

启动/帧率/内存/网络/卡顿/ANR 全链路

通信机制

接口 + 路由 + 事件总线,不直接引用实现类

发版节奏

组件独立发版 + 宿主集成,热修复兜底(有范围限制:不能改资源和类结构)

互联网 App 的设计核心

  • 解耦——几百人改同一个 App,改动不能互相影响
  • 可控——任何功能都能远程开关,任何异常都能降级
  • 可观测——线上发生了什么必须看得见,不然就是完全不可观测
  • 可恢复——出问题能秒级回滚,不用发版

8.7 厂商类型内置 App

核心矛盾:一套代码跑几十个机型/品牌/渠道 + 系统限制多 + 资源受限

一句话:厂商 App 的设计重点是"一套代码跑几十个项目,改一次全部生效"。

设计重点

怎么做

软件共版

抽象平台差异层,一套核心代码 + 多套平台适配

多渠道

构建变体 + productFlavors,渠道差异用配置驱动

多 App

同一系统上运行多个内置/外置应用,厂商自行开发内置应用,共享基础能力

subModule

Gradle 自定义构建变体(flavorDimensions / productFlavors),同一模块构建出不同变体

音源策略

不同设备/场景选不同音源,策略模式 + 配置驱动

资源管控

设备分级,低端机减功能减动画减资源

兼容性

API Level 适配封装层,上层不感知版本差异

包体积极致控制

能内置就内置(断网也要可用),手绘图片/动画替代预制资源,精打精算每个字节

厂商 App 的设计核心

  • 通用——套代码适配所有差异,不是每个场景写一套

  • 可插拔——功能模块可以按需组合,不同 App 不同配置

  • 可配置——同一个二进制跑在不同渠道,行为由配置驱动

  • 极致省——包体积、内存、功耗,每一点都要抠

8.8 具体设计方式详解

8.6 和 8.7 定义了两种场景流派的设计重点,下面展开每个设计方式的详细做法。

21 种设计方式总览

分类

设计方式

一句话

编程范式

8.8.1 现代化编程

声明式 UI + 状态管理 + 异步编程

解耦通信

8.8.2 生产者消费者

队列解耦生产与消费

8.8.9 事件驱动设计

发出事件的人不知道谁在监听

8.8.16 通信设计

强依赖用接口,弱依赖用事件

并发资源

8.8.3 线程池隔离

不同业务用不同线程池

8.8.10 内存防御设计

对外不信任,对内信任

网络缓存

8.8.4 抗弱网

没网不是空白页,慢网不是转圈页

8.8.12 缓存设计

请求→内存→磁盘→网络

架构跨端

8.8.5 Native壳+跨端组件

壳稳定、肉灵活

8.8.6 场景切面(AOP)

横切逻辑集中管理

8.8.7 动态化设计

编译期定死的越少越好

8.8.8 组件化vs模块化

按业务拆vs按功能拆

稳定性

8.8.11 降级设计

L0→L5,必须尽一切努力避免崩溃

8.8.15 防御式编程

假设所有外部输入都可能失败

8.8.13 启动设计

很大程度上取决于设计而非编码

状态流程

8.8.14 状态机设计

行为取决于状态,转换有规则

8.8.18 设计思维

从需求推导到设计

厂商特有

8.8.17 通用能力设计

一次改动,后续加需求改动量最小化

8.8.19 音频焦点策略

出声前申请焦点,拿到才播放

8.8.1 现代化编程

三件套:声明式 UI + 状态管理 + 异步编程

声明式 UI

命令式:告诉 UI 怎么做(setText、setVisibility、setColor……)
声明式:告诉 UI 是什么样(state → UI 映射,框架帮你刷新)

命令式(Android View): if (hasUnread) { badgeView.setVisibility(VISIBLE); badgeView.setText("3"); } else { badgeView.setVisibility(GONE); } 声明式(ArkTS): if (this.hasUnread) { Badge({ count: this.unreadCount }) } // state 变了 UI 自动变 声明式(Vue): <Badge v-if="hasUnread" :count="unreadCount" /> // state 变了 UI 自动变

好处:不用手动同步 UI 和数据——数据变了 UI 自动跟着变,不会出现"数据更新了 UI 忘了刷"的 bug。

代表:ArkTS(鸿蒙)、Compose(KMP)、React、Vue

状态管理

本质是观察者模式的工程化——UI 观察状态,状态是唯一权威。

原则: 1. Single Source of Truth——一个状态只有一个写入者 2. 状态不可变——改状态 = 创建新状态,不是原地改 3. 状态下沉,事件上浮——子组件只读父组件的状态,事件回调给父组件处理 ArkTS:@State / @Prop / @Link 装饰器,AppStorage 全局状态 React:useState + useReducer + Context,Redux / Zustand 全局状态 Vue:ref / reactive + Pinia 全局状态 KMP Compose:MutableStateFlow + StateFlow, ViewModel + stateIn

异步编程

方式

特点

代表

回调

简单但容易嵌套地狱

通用

Promise/Future

链式调用,比回调好,但异常处理分散

Vue/React 的 async 操作

async/await

看起来像同步,本质是协程

ArkTS、Kotlin、JS/TS

协程/Flow

Kotlin 首选,结构化并发,自动取消

KMP

链式编程 + 闭包

链式的本质是流畅接口(Fluent Interface)——方法返回 this 实现链式调用,Builder 模式是链式的一种应用。

badgeManager .tab("chat") .show() .withNum(3) .withAnim(true) .onComplete { log("done") }

闭包让行为参数化——把"做什么"当参数传进去,而不是写死。

代表:ArkTS、React Hooks、KMP Compose、Vue Composition API

8.8.2 生产者消费者模式

生产者和消费者互不知道对方存在,中间靠队列解耦。

Producer → Queue → Consumer (未读数数据源) (消息队列) (未读数展示)

要素

说明

生产者

只管生产数据,不管谁消费、什么时候消费

消费者

只管消费数据,不管谁生产的、什么时候生产的

队列

缓冲区,解耦生产和消费的速度差异

背压

生产速度 > 消费速度时的策略:丢、等、批量消费

客户端场景

场景

生产者

消费者

队列

未读数更新

产品/营销推送

BadgeManager 展示

未读数事件队列

消息通知

消息推送服务

通知栏展示

通知队列

预加载任务

启动流程

各 Tab 初始化

线程池任务队列

埋点上报

业务代码

埋点上报服务

埋点缓冲队列

流式 RPC

服务端流式推送

客户端逐字消费

流式数据缓冲队列

会话消息

长链接/推送

聊天界面展示

消息缓冲队列

编解码相关应用

音视频采集

编码/解码模块

帧/数据缓冲队列

为什么用生产者消费者

  • 解耦——生产者和消费者各自独立演进
  • 异步——生产不等消费,不阻塞主流程
  • 缓冲——峰值时队列兜住,消费端按自己节奏处理
  • 可控——队列大小、消费速率、背压策略都可配置

8.8.3 线程池隔离

不同业务用不同线程池,一个炸了不拖垮其他。

有规模的 App 一般都需要,特别是启动优化那块——不然一启动就噶或者请求个数据一直搁那转圈圈。需要线程管控、资源调度。

大型 App 启动时如果不同业务同时开线程去进行耗时操作,有可能整个 App 都在抢夺资源,抢夺资源失败的业务直接 return 没有处理线程任务。且数量太多 App 可能直接噶掉。

❌ 一个共用线程池: 未读数加载 + 图片下载 + 数据同步 + 埋点上报 全挤在一起 图片下载阻塞了 → 未读数加载不了 → 用户体验炸 ✅ 隔离线程池: IO_POOL:图片、文件 BADGE_POOL:未读数计算 UPLOAD_POOL:埋点上报 DB_POOL:数据库读写 互相不影响

线程切换应有且仅有一条规则

  • 主线程 → 后台:统一一个入口(如 Dispatchers.io() 或统一的 ThreadScheduler)
  • 后台 → 主线程:统一一个入口(如 Dispatchers.main() 或统一的 UiHandler)
  • 不允许业务代码直接 new Thread、直接 runOnUiThread

设计要点

说明

核心线程数

CPU 密集型 = CPU 核数,IO 密集型 = CPU 核数 × 2

队列大小

有界队列,不能无限排——内存会炸

拒绝策略

队列满了怎么办?丢弃 + 打日志 / 调用者线程跑 / 丢弃最老

线程命名

必须命名!出了问题看线程栈知道是谁

监控

队列积压量、活跃线程数、拒绝次数

线程池分配原则

  • 关键路径独占:启动、未读数展示这种用户可感知的用独立池
  • 非关键路径共享:埋点上报、日志这种可以共享一个大池
  • 第三方 SDK 隔离:SDK 用自己的池,别跟你的业务挤在一起

8.8.4 抗弱网

核心思想:没网不是空白页,慢网不是转圈页

层级

策略

网络层

超时分级、重试策略(指数退避)、多路复用

缓存层

强缓存 + 协商缓存、离线数据兜底、过期数据先展示

降级层

接口失败 → 缓存 → 兜底默认值

预加载

WiFi 下预拉取关键数据

增量更新

只拉 diff,减少传输量

数据压缩

请求/响应压缩

请求合并

多个请求合成一个,减少 RTT

断点续传

大文件/长列表支持断点

网络状态感知

WiFi/4G/3G/无网,策略自动切换

客户端弱网设计的黄金法则

每次网络请求都要问自己: 1. 失败了怎么办?→ 兜底值 2. 超时了怎么办?→ 缓存值 3. 没网了怎么办?→ 离线数据 4. 数据过期了怎么办?→ 先展示旧的,后台拉新的 推荐:TTL 过期 + 读时刷新 组合

8.8.5 Native 壳 + 跨端组件业务

壳稳定、肉灵活——平台能力在 Native,业务逻辑在跨端。

设计要点

说明

壳只做壳的事

生命周期、权限、平台能力,不掺业务

业务只做业务的事

不直接调平台 API,通过壳暴露的接口

通信协议统一

不管哪种跨端方案,Native 侧接口统一封装

业务可插拔

不同业务选不同跨端方案

降级兜底

跨端页面加载失败?降级到 Native 或 H5 兜底页

调试体系

跨端调试链路要通

跨端框架的代价:跨端框架省的是编写成本,不省验证成本。选型时要问:双端验证成本、能力边界、抽象泄漏、工具链成熟度、代码归属、三端差异文档化、语法基因。

选型决策线

业务复杂度低 + 组件够用 + 静态页面多 → 跨端框架收益大 业务复杂度高 + 需要动态能力 + 页面交互复杂 → 跨端框架成本 ≥ 原生

8.8.6 场景切面(AOP)

横切逻辑——日志、埋点、权限、性能监控、登录检查——从业务里抽出来集中管理。

✅ 有 AOP: @RequireLogin @Trace("chat_open") @Log fun openChat() { chatManager.open(...) // 只写业务 }

客户端怎么做 AOP

方式

原理

适用场景

限制

注解 + APT

编译期生成代码

路由、依赖注入

编译期,不能改逻辑

注解 + ASM

编译期字节码插桩

埋点、性能监控

编译期,构建变慢

动态代理

运行期代理接口调用

接口层的横切逻辑

只能代理接口

Gradle Transform

编译期改 class

全埋点、方法耗时

构建链路复杂

Kotlin 协程拦截器

协程上下文插入逻辑

异步链路的横切

只在协程内

Lifecycle 感知

生命周期回调

页面级的横切

只在生命周期内

8.8.7 动态化设计

编译期定死的越少越好,运行时可变的越多越好。

动态化三层

  • L1 资源动态化:运行时加载图片/动画/字体 → 最简单
  • L2 布局动态化:服务端下发 UI 描述,客户端渲染 → 中等
  • L3 逻辑动态化:服务端下发脚本/规则,客户端执行 → 最强但风险最大

动态化设计原则:降级兜底、版本兼容、安全校验、大小控制、离线可用、回滚能力。

8.8.8 组件化 vs 模块化

模块化

组件化

粒度

按业务拆

按功能拆

关系

模块之间有上下级依赖

组件之间平级,可独立运行

复用

模块通常只在一个 App 用

组件跨 App 复用

独立运行

模块一般不能单独跑

组件可以单独跑(开发调试用)

组件化设计要点:组件可独立运行、可插拔、组件间通信(路由+接口+事件总线)、组件隔离(api/implementation分离)、资源隔离、组件生命周期。

8.8.9 事件驱动设计

发出事件的人不知道谁在监听,甚至不知道有没有人监听。

解耦程度:直接调用 < 观察者 < 事件驱动

设计原则

  1. 事件命名表达"发生了什么",不是"要做什么"
  2. 事件携带数据,不携带行为
  3. 事件要有生命周期——谁注册谁反注册
  4. 事件总线不是万能的——一对一强依赖用接口,链路需要可追踪用接口
  5. 防事件风暴——事件 A 触发 B,B 触发 C,C 又触发 A,循环了

8.8.10 内存防御设计

内存泄漏不是写错了一行代码,是架构设计没管好生命周期。

防御点

手段

空值

非空断言 + 兜底值

越界

集合操作前检查 size

类型

JSON 反序列化 try-catch

并发

共享变量加锁或用并发容器

生命周期

异步回调前判 isAdded / isFinishing

配置

所有远端配置配本地默认值

第三方

SDK 调用包 try-catch

内存泄漏

对称原则:register 必有 unregister

OOM

图片降采样 + LRU 上限 + onTrimMemory 主动释放

防御原则:对外不信任,对内信任——模块边界全加防御,模块内部正常写。

8.8.11 降级设计

L0: 完美体验 → 全部功能正常 L1: 功能降级 → 核心功能可用,非核心关闭 L2: 内容降级 → 实时数据不可用,用缓存数据 L3: 展示降级 → 富媒体不可用,用文字/占位图 L4: 优雅失败 → 功能完全不可用,提示用户 L5: 崩溃 ← 必须尽一切努力避免

降级设计原则:

  1. 每个关键路径都要有降级方案
  2. 降级是配置驱动的,不是代码写死的
  3. 降级要可观测(埋点上报)
  4. 降级要可恢复(开关关了自动恢复)

8.8.12 缓存设计

缓存三层:请求 → 内存缓存 → 磁盘缓存 → 网络

决策点

怎么选

缓存什么

只读数据、更新频率低的、请求成本高的

缓存多久

TTL 根据业务容忍度:未读数 5 分钟,配置 1 小时

缓存大小

内存 LRU 上限根据业务定(图片缓存的经验值是可用内存 1/8,其他场景需单独评估)

一致性

大部分场景最终一致就行

穿透

加空值缓存防穿透

击穿

热点 key 加锁,只让一个请求穿透

雪崩

TTL 加随机偏移

客户端推荐:TTL 过期 + 读时刷新 组合——先返回旧数据,后台静默拉新。

8.8.13 启动设计

启动很大程度上取决于设计,而非编码。

类型

什么时候

可否延迟

P0 必须

同步阻塞

P1 首帧

首帧渲染前

P2 首屏

首屏展示后

P3 后台

空闲时

设计原则:

  1. 有向无环拓扑排序——依赖不能成环
  2. 核心路径预加载 + 非核心懒加载 + 空闲时预热
  3. 每个阶段耗时打点,启动慢了能定位
  4. 任何任务失败都不能卡住启动

8.8.14 状态机设计

当一个对象的行为取决于它当前的状态,且状态之间有严格的转换规则时。

设计原则:

  1. 状态是有限的、明确的——枚举所有状态,不允许"未知"

  2. 转换是受限的——定义合法转换,非法转换直接拒绝

  3. 副作用在转换上,不在状态上

  4. 状态机优先单调(只能往前走,除非显式重置)

8.8.15 防御式编程

假设所有外部输入都是恶意的,所有外部调用都可能失败。

过度防御:每个方法每个参数都判空,代码 50% 是防御逻辑 → 可读性炸
适度防御:模块边界防御,内部信任 → 清晰 + 安全

原则:对外不信任,对内信任。

8.8.16 通信设计

强依赖用接口,弱依赖用事件。接口调用要超时。事件不能代替接口。跨进程通信最小化。

8.8.17 通用能力设计

一次改动,后续再加需求改动量最小化。

这是厂商类型 App 最看重的设计哲学——通用能力一旦写好,新的 App、新的渠道、新的场景来了,改动量最小化。

通用能力 vs 专用能力

专用能力

通用能力

写法

if (渠道A) { ... } else if (渠道B) { ... }

config.drive(mode)

新渠道

加 else if

加一条配置

测试

每加一个渠道回归所有

只测新配置

维护

代码越改越长

配置表维护

通用能力设计四步法

第 1 步:抽象共性 共性抽象成接口,差异抽象成配置。 第 2 步:配置驱动 所有差异用配置表达,不用代码表达。 配置来源:本地默认值 → 远程下发 → 运行时环境感知 第 3 步:扩展点预留 不确定未来会不会变的地方,留扩展点(回调/策略/插件) 确定不变的地方,坚决不抽象 第 4 步:验证套件 配置校验 → 兜底值 → 异常上报 → 日志追踪

通用能力设计原则

原则

说明

反例

配置 > 代码

差异用配置,不用 if-else

10 个渠道写 10 个 if

接口 > 实现

通用能力定义接口,具体实现可替换

直接 new 一个实现类

策略 > 分支

不同行为用策略模式,不用 switch

switch(type) case A: ... case B: ...

注册 > 硬编码

新功能通过注册接入,不修改通用代码

通用代码里加新模块的引用

事件 > 调用

通用能力通知外部用事件,不直接调用外部

通用模块 import 业务模块

扩展点 > 修改

加功能通过扩展点,不修改通用代码

改通用代码加新逻辑

过度通用的信号:配置项比业务代码还多、为了通用引入了三层抽象、新接一个 App 还是要改通用代码、通用能力的开发比业务开发还慢。出现这些信号,说明抽象过头了,该收缩边界。

8.8.18 设计思维

从需求推导到设计

需求 → 理解问题 → 识别变化点 → 选择模式 → 定义接口 → 写实现

设计评审的核心问题

问题

回答不了 = 设计有问题

这个类只有一个原因变吗?

回答不了 → 职责不单一

加一种新类型要改几个文件?

>3 → 扩展性差

这个方法可能失败吗?失败了怎么办?

不知道 → 没有错误处理

这个方法跑在哪个线程?

不知道 → 线程模型缺失

这个类的依赖能 mock 吗?

不能 → 不可测试

这个模块挂了整不崩溃?

会的 → 没有容错

这个配置写死了吗?

写死了 → 不够灵活

三个月后你自己看得懂吗?

看不懂 → 命名/结构有问题

8.8.19 音频焦点策略

这是厂商类型 App 的核心设计问题之一,也是观察者 + 状态 + 策略的综合实战。

为什么需要音频焦点

系统可能有几十种音源——音乐、导航、电话、倒车提示、系统通知……如果每种音源都自己播放,几秒内可能变化十几次,造成混音 chaos。

音频焦点策略就是让所有音源遵守同一套规则:出声前申请焦点,拿到才播放。

长焦点 vs 短焦点

长焦点

短焦点

何时申请

需要持续播放时

需要短暂提示时

持续时间

持续持有,直到主动释放

播放完自动归还

释放时机

声音暂停后主动释放

播放完成后系统自动归还给上一焦点持有者

典型场景

音乐、视频、电台、CarPlay

提示语、电话铃声、倒车影像、开机音

优先级

一般较低

一般较高(短焦点可抢长焦点)

混音排查流程

1. 两个音源同时播放了 → 混音了 2. 检查两个音源是否都申请了焦点 ├─ 没申请焦点就播放 → 那个业务的问题 └─ 都申请了焦点 3. 检查最后一次焦点在谁手里 ├─ 焦点在A,但B也在播放 → B 的业务问题 └─ 焦点只有一个,另一个没拿到焦点 4. 拿到焦点的音源正常播放,但另一个音源也在响 ├─ 另一个业务调了播放接口 → 那个业务的问题 └─ 另一个业务根本没调播放接口 → Audio框架的问题

音频焦点设计用到的模式

模式

怎么用的

观察者

焦点变化通知:AudioManager.OnAudioFocusChangeListener

状态

音频焦点状态机:持有 / 丢失 / 暂时丢失 / Duck

策略

不同音源类型申请不同焦点策略:长焦点 / 短焦点 / Duck

责任链

焦点请求按优先级传递,高优先级拦截

8.8.20 Pipeline(流水线编排)

把一个复杂流程拆成多个阶段,每个阶段独立处理、独立线程池、阶段间用队列连接。

生产者消费者是"一个生产一个消费",Pipeline 是"一串阶段串起来,每个阶段既是消费者又是生产者"。

输入 → Stage1(解析) → Queue1 → Stage2(校验) → Queue2 → Stage3(处理) → Queue3 → Stage4(输出) → 结果

三要素:Stage(阶段)、Queue(队列)、ThreadPool(线程池)

实战例子:直播推流管线

Camera 采集 → OpenGL 渲染 → MediaCodec 编码 → 封包 → RTMP 推流 30fps GPU 处理 硬编耗时 快 受网络影响

每个阶段速度不一样——Camera 30fps 稳定输出,编码可能跟不上,推流看网络。如果一条线程串下来,推流卡了编码也卡了,编码卡了相机也卡了。用 Pipeline:每个阶段自己的队列兜住,推流慢了编码队列积压,背压传导回相机降帧,而不是全线崩溃。

背压:下游处理不过来,压力向上游传导。

背压不是自动的——队列满了 BlockingQueue.put() 会阻塞生产者线程导致线程饥饿,实际工程中需要显式选择背压策略:丢帧(offer 失败就丢)、等待限时(offer + timeout)、降速(通知上游降频)。不同场景选不同策略,不能无脑用阻塞队列。

什么时候用

适合

不适合

多阶段处理,阶段速度不一样

简单请求-响应,用不着

需要背压保护

阶段间速度差异很小,收益不大

IO 密集型流水线

纯计算且阶段均匀的管线

音视频/直播管线

CRUD

8.8.21 Hook(钩子)

不修改原始代码,在运行时插入自己的逻辑或者替换原始行为。

Hook 不是 GoF 23 种设计模式之一,但它是一种广泛使用的设计思想——预留扩展点,外部插入逻辑。

从温和到暴力,Hook 分很多层

层级

方式

例子

设计层

继承覆写 / 回调接口

Activity.onCreate()、OnClickListener

框架层

拦截器 / 模板方法

OkHttp Interceptor、RecyclerView.Adapter

运行时

反射替换实例

Hook AMS 跳过 Activity 注册检查

加载时

ClassLoader 拦截

插件化加载未注册的类

实战例子1:反射替换(运行时 Hook)

正常流程:App → IActivityManager.startActivity() → 系统检查 Manifest → 拒绝 Hook 后: App → AMSProxy.startActivity() → 替成占坑 Activity → 系统检查通过 → 换回真实 Activity

三步走:找到目标 → 构造替代品 → 偷梁换柱

  1. 反射拿到系统的 AMS 代理对象
  2. 创建自己的代理类(持有原始对象),在 startActivity 里把 Intent 替掉
  3. 把代理塞回去,以后系统调的都是你的代理

本质上就是代理模式,只不过代理对象是运行时偷偷塞进去的。

实战例子2:ClassLoader 拦截(加载时 Hook)

场景:插件化框架加载插件的类。

正常加载:ClassLoader.loadClass("PluginActivity") → 找不到 → 崩溃 Hook 后: ClassLoader.loadClass("PluginActivity") → 拦截 → 用插件 ClassLoader 加载 → 成功

两种 Hook 对比

反射替换

ClassLoader 拦截

Hook 时机

运行时,对象创建后

类加载时,对象创建前

Hook 粒度

替换一个实例/方法

替换整个类

Hook 范围

精确,只改一个对象

全局,所有用到这个类的地方

代表应用

插件化跳转、保活

插件化加载、双开助手

Hook 的设计模式本质:Hook 是目的(插入逻辑改变行为),各种设计模式是实现手段。你在日常开发中写的 onCreate()、setOnClickListener()、Interceptor,都是 Hook——只是你没叫它 Hook。

http://www.jsqmd.com/news/869251/

相关文章:

  • 可迪尔环境(DADAIR)造船喷涂废气治理项目验收,RTO蓄热燃烧炉厂家首选方案
  • 智能电表:解锁智能照明精细化能耗管控新密码
  • 中关村、首体院、京奥电竞三方签约,共探AI+电竞产学研一体化突破
  • 装上这个技能,让你的 OpenClaw 和 Hermes 变身私人旅行规划师
  • 用Gemini镜像站构建技术文档自动生成管道:从代码注释到开发者指南的全流程实践
  • adb常用命令
  • 《AI代码编辑器Cursor最新版深度体验:智能编程实战与VS Code平滑迁移指南》​
  • 2026保定市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • 2026保山市最新黄金 白银 铂金 彩金回收收门店实力排行榜及联系方式推荐 - 大熊猫898989
  • 【芯片测试】:SmarTest 开发环境入门
  • 正版奇迹 mu 荣耀出征 2026 下载|官方认证站点・三端数据互通
  • AndroidX Room 3:Kotlin 多平台时代的持久化库设计深度解析 (介绍篇)
  • AI 大模型未来技术演进方向与应用发展趋势预判
  • Java程序设计(第3版)第四章——成员变量的默认值
  • 牛客周赛 Round 142 C题及D题题解
  • 使用workbuddy 30分钟搭建微信小程序
  • 意法半导体STM32F103RCT6海量库存
  • 音频变压器测试条件与方法全解析
  • 【安卓】抖音 38.4.0 内置多功能模块 无水印下载 去广告
  • 从 CLI 到 GUI:Hermes Agent 的最后一块拼图补齐了
  • AI Agent Harness Engineering 反思机制3大实现路径:日志回溯 vs 强化学习 vs 人工反馈
  • USB外设概率性不识别问题详解
  • 荣耀出征手游官网下载:奇迹MU荣耀出征5月最新官方下载渠道
  • COLMAP实战:如何用命令行搞定无人机航拍图像的三维重建?
  • 618性价比高的灭蚊灯有哪些?室内灭蚊器哪个牌子好?精选2026年家用灭蚊灯十大名牌,全解析
  • 昇腾CANN opbase与算子生态协作:从单一算子到完整计算图
  • 财报录入系统和普通 OCR 录入区别是什么?
  • 磁性轴承尺寸如何精准检测?蓝光扫描仪全尺寸3D检测解析
  • 植树的人数
  • 别再让FFT精度拖后腿了!手把手教你用三点插值法把频率估计误差降到最低