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

01-16-16 命令模式 - Handler消息机制的命令封装

01-16-16 命令模式 - Handler消息机制的命令封装

模式定义

命令模式(Command Pattern)属于行为型设计模式,其核心思想是:将一个请求封装为一个对象,从而使请求的发送者与请求的执行者解耦。请求被封装后,可以被存储、传递、排队、撤销或重做。

解决的问题

在软件系统中,方法的调用者往往需要知道接收者是谁、调用哪个方法、传入什么参数。这种直接耦合使得调用者难以在不修改自身代码的情况下更换执行逻辑。命令模式通过引入"命令对象"作为中间层,将"发起请求"和"执行请求"彻底分离,使得调用者只需持有命令对象即可,无需了解执行细节。

类图说明

┌──────────┐ ┌──────────────┐ ┌──────────┐ │ Client │──────>│ Invoker │ │ Receiver │ └──────────┘ │──────────────│ │──────────│ │ - command │ │ + action()│ │ + invoke() │ └────┬─────┘ └──────┬───────┘ │ │ holds │ ┌──────┴───────┐ │ │ Command │ │ │──────────────│ │ │ + execute() │────────────┘ │ + undo() │ calls receiver.action() └──────┬───────┘ │ implements ┌─────────┴─────────┐ │ │ ┌──────┴──────┐ ┌──────┴──────┐ │ ConcreteCmd1 │ │ ConcreteCmd2 │ └─────────────┘ └─────────────┘

模式中的四个角色:

  • Command(命令接口):声明执行操作的接口
  • ConcreteCommand(具体命令):持有接收者引用,调用接收者的具体方法来完成请求
  • Invoker(调用者):持有命令对象,在适当时机触发命令执行
  • Receiver(接收者):实际执行业务逻辑的对象

Android源码中的实现

案例一:Handler/Message 消息机制

源码路径

  • frameworks/base/core/java/android/os/Message.java
  • frameworks/base/core/java/android/os/Handler.java
  • frameworks/base/core/java/android/os/Looper.java
  • frameworks/base/core/java/android/os/MessageQueue.java

Android 的 Handler 消息机制是命令模式的典型实现。Message作为命令对象封装了请求的所有信息,Handler同时承担了命令的创建者和执行者角色,MessageQueue是命令队列,Looper负责从队列中取出命令并分发执行。

// frameworks/base/core/java/android/os/Message.javapublicfinalclassMessageimplementsParcelable{// 命令标识:what 字段标识命令类型publicintwhat;// 命令参数:携带执行所需的数据publicintarg1;publicintarg2;publicObjectobj;Bundledata;// 命令的目标执行者(Invoker + Receiver)/*package*/Handlertarget;// 另一种命令封装形式:直接持有 Runnable/*package*/Runnablecallback;// 链表指针:支持命令在消息队列中排队/*package*/Messagenext;// 命令的定时执行信息/*package*/longwhen;// 对象池复用机制:避免频繁创建销毁命令对象privatestaticfinalObjectsPoolSync=newObject();privatestaticMessagesPool;privatestaticintsPoolSize=0;privatestaticfinalintMAX_POOL_SIZE=50;// 从对象池获取命令对象,而非 newpublicstaticMessageobtain(){synchronized(sPoolSync){if(sPool!=null){Messagem=sPool;sPool=m.next;m.next=null;m.flags=0;// 清除使用标记sPoolSize--;returnm;}}returnnewMessage();}// 命令执行完毕后回收到对象池voidrecycleUnchecked(){flags=FLAG_IN_USE;what=0;arg1=0;arg2=0;obj=null;// ... 重置所有字段synchronized(sPoolSync){if(sPoolSize<MAX_POOL_SIZE){next=sPool;sPool=this;sPoolSize++;}}}}
// frameworks/base/core/java/android/os/Handler.javapublicclassHandler{// 发送命令:将命令对象放入消息队列publicfinalbooleansendMessage(Messagemsg){returnsendMessageDelayed(msg,0);}publicfinalbooleansendMessageDelayed(Messagemsg,longdelayMillis){if(delayMillis<0){delayMillis=0;}returnsendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis);}publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis){MessageQueuequeue=mQueue;if(queue==null){returnfalse;}returnenqueueMessage(queue,msg,uptimeMillis);}privatebooleanenqueueMessage(MessageQueuequeue,Messagemsg,longuptimeMillis){// 将当前 Handler 设置为命令的目标执行者msg.target=this;msg.workSourceUid=ThreadLocalWorkSource.getUid();returnqueue.enqueueMessage(msg,uptimeMillis);}// post 方式:将 Runnable 包装成 Message 命令对象publicfinalbooleanpost(Runnabler){returnsendMessageDelayed(getPostMessage(r),0);}privatestaticMessagegetPostMessage(Runnabler){Messagem=Message.obtain();m.callback=r;// Runnable 作为命令的具体执行体returnm;}// 命令分发:Looper 取出 Message 后调用此方法执行publicvoiddispatchMessage(Messagemsg){// 优先级 1:Message 自带的 Runnable callbackif(msg.callback!=null){handleCallback(msg);}else{// 优先级 2:Handler 构造时传入的全局 Callbackif(mCallback!=null){if(mCallback.handleMessage(msg)){return;}}// 优先级 3:子类覆写的 handleMessagehandleMessage(msg);}}privatestaticvoidhandleCallback(Messagemessage){message.callback.run();// 执行 Runnable 命令}// 子类覆写此方法处理不同 what 值的命令publicvoidhandleMessage(Messagemsg){// 默认空实现}}
// frameworks/base/core/java/android/os/Looper.javapublicfinalclassLooper{// 命令分发循环:不断从队列取出命令并执行publicstaticvoidloop(){finalLooperme=myLooper();finalMessageQueuequeue=me.mQueue;for(;;){// 从消息队列中取出下一个命令(可能阻塞)Messagemsg=queue.next();if(msg==null){return;// 队列已退出}// 将命令分发给其目标 Handler 执行msg.target.dispatchMessage(msg);// 命令执行完毕,回收到对象池msg.recycleUnchecked();}}}

命令模式角色映射

命令模式角色Handler 机制对应
CommandMessage(封装命令类型、参数和执行目标)
ConcreteCommand携带RunnableMessage,或携带特定what值的Message
InvokerLooper(从队列取出命令并触发执行)
ReceiverHandlerhandleMessageRunnable.run执行具体逻辑)
CommandQueueMessageQueue(命令排队、按时间排序)

案例二:Runnable 作为命令对象

源码路径libcore/ojluni/src/main/java/java/lang/Runnable.java

Runnable接口本身就是命令模式中最简洁的命令接口定义。

// libcore/ojluni/src/main/java/java/lang/Runnable.java@FunctionalInterfacepublicinterfaceRunnable{// 命令接口:只有一个执行方法publicabstractvoidrun();}

在 Android 中,Runnable被广泛用作命令对象:

// frameworks/base/core/java/android/view/View.javapublicclassView{// View.post() 将 Runnable 命令投递到主线程执行publicbooleanpost(Runnableaction){finalAttachInfoattachInfo=mAttachInfo;if(attachInfo!=null){// 通过 View 关联的 Handler 将命令入队returnattachInfo.mHandler.post(action);}// View 未 attach 时,暂存命令到 RunQueue 中getRunQueue().post(action);returntrue;}// 延迟执行命令publicbooleanpostDelayed(Runnableaction,longdelayMillis){finalAttachInfoattachInfo=mAttachInfo;if(attachInfo!=null){returnattachInfo.mHandler.postDelayed(action,delayMillis);}getRunQueue().postDelayed(action,delayMillis);returntrue;}// 取消命令:从队列中移除指定的 RunnablepublicbooleanremoveCallbacks(Runnableaction){if(action!=null){finalAttachInfoattachInfo=mAttachInfo;if(attachInfo!=null){attachInfo.mHandler.removeCallbacks(action);}getRunQueue().removeCallbacks(action);}returntrue;}}

关键设计分析

  • Runnable将操作封装为对象,可以被存储(RunQueue)、延迟执行(postDelayed)、取消(removeCallbacks
  • View.post()方法巧妙地处理了 View 未 attach 到窗口的边界情况——命令先暂存在HandlerActionQueue中,待 attach 后批量执行
  • 这正是命令模式的典型优势:命令对象的创建时机与执行时机解耦

案例三:PackageManagerService 的安装命令

源码路径frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

应用安装过程使用了命令模式来管理复杂的安装流程。

// frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java// 安装命令的基类(简化)abstractclassHandlerParams{// 命令参数finalInstallSourceinstallSource;finalintinstallFlags;// 命令执行入口finalbooleanstartCopy(){booleanres;try{// Step 1:检查重试次数if(++mRetries>MAX_RETRIES){mHandler.sendEmptyMessage(MCS_GIVE_UP);handleServiceError();returnfalse;}// Step 2:执行具体的安装操作(子类实现)handleStartCopy();// Step 3:处理安装结果(子类实现)handleReturnCode();res=true;}catch(RemoteExceptione){mHandler.sendEmptyMessage(MCS_RECONNECT);res=false;}returnres;}// 抽象方法:子类实现具体的拷贝逻辑abstractvoidhandleStartCopy()throwsRemoteException;abstractvoidhandleReturnCode();}// 具体命令:安装参数classInstallParamsextendsHandlerParams{finalOriginInfoorigin;@OverridevoidhandleStartCopy()throwsRemoteException{// 决定安装位置(内部存储 or 外部存储)// 执行 APK 文件拷贝// 验证签名等}@OverridevoidhandleReturnCode(){// 安装完成后通知调用方// 发送安装结果广播}}

关键设计分析

  • HandlerParams将安装请求封装为命令对象,包含安装来源、安装标志等所有参数
  • 命令对象通过PackageHandler(一个 Handler 子类)进行排队和调度
  • 多个安装请求可以排队执行,后台串行处理,避免并发安装导致的文件冲突
  • 安装失败时支持重试(mRetries),这正是命令对象可被重复执行的优势

源码设计分析

为什么 Android 选择命令模式

Handler/Message 场景:Android UI 框架要求所有 UI 操作必须在主线程执行。工作线程需要将 UI 更新操作"打包"后投递到主线程执行,这天然需要将操作封装为对象。Message作为命令对象,承载了跨线程传递操作的职责。

消息队列的必要性:多个线程可能同时产生 UI 更新请求,MessageQueue将这些请求序列化为单线程顺序执行,消除了并发竞争。命令模式中的"命令队列"特性与此需求完美契合。

延迟执行需求postDelayed()需要将操作与其执行时间绑定后存储。只有将操作封装为对象,才能附加时间戳并在队列中按时间排序。

优缺点权衡

优点

  • 解耦发送与执行:工作线程只需构造 Message,无需知道主线程如何处理
  • 支持排队:MessageQueue 按时间排序,保证命令按预期顺序执行
  • 支持撤销removeCallbacks()/removeMessages()可以取消尚未执行的命令
  • 延迟执行:命令对象可以携带执行时间,实现定时触发
  • 对象池复用Message.obtain()避免频繁 GC,适合高频场景

缺点

  • 类膨胀:每种命令都可能需要定义不同的what常量和参数约定,代码维护成本增加
  • 调试追踪困难:命令从创建到执行可能跨越多个线程,call stack 不连续
  • 内存开销:每个命令都是一个对象,高频场景下需要对象池机制来缓解

实战应用

场景一:支持撤销/重做的操作系统

在 IoT 设备控制场景中,用户可能需要撤销对设备的误操作。

/** * 命令接口:设备控制命令 */interfaceDeviceCommand{/** 执行命令 */funexecute()/** 撤销命令 */funundo()/** 命令描述,用于显示在操作历史中 */valdescription:String}/** * 具体命令:调节设备亮度 */classAdjustBrightnessCommand(privatevaldevice:SmartLight,privatevaltargetBrightness:Int):DeviceCommand{privatevarpreviousBrightness:Int=0overridefunexecute(){previousBrightness=device.brightness device.setBrightness(targetBrightness)}overridefunundo(){device.setBrightness(previousBrightness)}overridevaldescription:Stringget()="亮度:$previousBrightness->$targetBrightness"}/** * 具体命令:切换设备开关状态 */classTogglePowerCommand(privatevaldevice:SmartDevice):DeviceCommand{privatevarwasPoweredOn:Boolean=falseoverridefunexecute(){wasPoweredOn=device.isPoweredOnif(wasPoweredOn)device.powerOff()elsedevice.powerOn()}overridefunundo(){if(wasPoweredOn)device.powerOn()elsedevice.powerOff()}overridevaldescription:Stringget()=if(wasPoweredOn)"关闭设备"else"开启设备"}/** * 命令调用者:管理命令的执行、撤销和重做 */classCommandManager(privatevalmaxHistorySize:Int=50){privatevalundoStack=ArrayDeque<DeviceCommand>()privatevalredoStack=ArrayDeque<DeviceCommand>()funexecute(command:DeviceCommand){command.execute()undoStack.addLast(command)redoStack.clear()// 执行新命令后清空重做栈// 限制历史记录大小if(undoStack.size>maxHistorySize){undoStack.removeFirst()}}funundo():Boolean{if(undoStack.isEmpty())returnfalsevalcommand=undoStack.removeLast()command.undo()redoStack.addLast(command)returntrue}funredo():Boolean{if(redoStack.isEmpty())returnfalsevalcommand=redoStack.removeLast()command.execute()undoStack.addLast(command)returntrue}funcanUndo():Boolean=undoStack.isNotEmpty()funcanRedo():Boolean=redoStack.isNotEmpty()/** 获取操作历史记录 */fungetHistory():List<String>=undoStack.map{it.description}}

场景二:异步命令队列

将命令模式与协程结合,实现可取消、可重试的异步命令队列。

/** * 异步命令接口 */interfaceAsyncCommand{valid:StringvalretryCount:Intget()=0suspendfunexecute():Result<Unit>}/** * 异步命令队列:串行执行命令,支持重试和取消 */classAsyncCommandQueue(privatevalscope:CoroutineScope){privatevalqueue=Channel<AsyncCommand>(Channel.UNLIMITED)privatevalpendingCommands=ConcurrentHashMap<String,AsyncCommand>()privatevarprocessingJob:Job?=nullinit{startProcessing()}/** 将命令加入队列 */funenqueue(command:AsyncCommand){pendingCommands[command.id]=command queue.trySend(command)}/** 取消指定命令(如果尚未执行) */funcancel(commandId:String):Boolean{returnpendingCommands.remove(commandId)!=null}privatefunstartProcessing(){processingJob=scope.launch{for(commandinqueue){// 检查命令是否已被取消if(!pendingCommands.containsKey(command.id)){continue}varattempts=0varsuccess=falsewhile(attempts<=command.retryCount&&!success){valresult=command.execute()success=result.isSuccessif(!success){attempts++if(attempts<=command.retryCount){// 指数退避重试delay(1000L*(1shl(attempts-1)))}}}pendingCommands.remove(command.id)}}}funshutdown(){queue.close()processingJob?.cancel()}}/** * 具体的异步命令:上传设备日志 */classUploadLogCommand(overridevalid:String=UUID.randomUUID().toString(),overridevalretryCount:Int=3,privatevaldeviceId:String,privatevallogData:ByteArray,privatevalapiService:ApiService):AsyncCommand{overridesuspendfunexecute():Result<Unit>=runCatching{apiService.uploadLog(deviceId,logData)}}

与其他模式的对比

命令模式 vs 策略模式

维度命令模式策略模式
意图将请求封装为对象,支持排队、撤销封装可替换的算法
关注点做什么(请求的参数化)怎么做(算法的选择)
生命周期命令对象可被存储、延迟执行、撤销策略对象通常在使用时即刻执行
是否持有状态通常持有执行所需的全部参数通常无状态或仅依赖输入参数
Android 典型案例Message/RunnableInterpolator/Comparator

命令模式 vs 观察者模式

二者都实现了发送者与接收者的解耦,但解耦的维度不同。命令模式是一对一的关系——一个命令对象对应一个接收者;观察者模式是一对多的关系——一个事件通知多个观察者。Android 中Handler.post(Runnable)是命令模式,LiveData.observe()是观察者模式。

命令模式 vs 责任链模式

责任链模式中,请求沿着链条传递,由某个节点处理或全部节点都不处理;命令模式中,命令有明确的目标执行者(msg.target)。Android 的事件分发机制(dispatchTouchEvent)是责任链模式,Handler.dispatchMessage()是命令模式。


总结与最佳实践

命令模式的核心价值在于将操作参数化为对象,使操作具备存储、传递、排队、撤销的能力。Android 的 Handler/Message 机制是这一模式在系统级框架中的典范实现。

最佳实践

  1. 命令对象应自包含:命令对象应持有执行所需的全部参数和接收者引用,不依赖外部状态。Message通过whatarg1arg2objdata等字段实现了自包含

  2. 考虑对象池复用:在高频创建命令的场景中(如 Handler 消息循环),使用对象池(Message.obtain())避免频繁的对象分配和 GC

  3. 撤销操作需要保存前置状态:如果命令需要支持undo(),必须在execute()执行前保存接收者的原始状态

  4. 宏命令组合:多个命令可以组合为一个宏命令(CompositeCommand),实现批量操作的原子化执行和撤销

  5. 优先使用函数式接口:在 Kotlin 中,简单命令可以直接使用 lambda 表达式(() -> Unit),无需定义额外的命令类。仅当需要撤销、持久化等高级特性时才使用完整的命令类

  6. 命令与队列分离:命令对象只负责"做什么",队列管理(排序、调度、重试)交给独立的 Invoker 处理。这一分离使得同一组命令可以在不同的调度策略下复用

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

相关文章:

  • 2026华东口碑不错的高强度、耐高温复材粘接剂公司一览,四甲基二乙烯基硅氮烷,高强度、耐高温复材粘接剂企业有哪些 - 品牌推荐师
  • 收录排名查询 SEO 软件哪个好用
  • 【MLLM】Qwen-Omni系列全模态模型架构和训练
  • 告别MSE!用能量模型(EBM)做行为克隆,让机器人模仿学习更精准(附PyTorch代码)
  • 2025最权威的五大AI论文方案推荐
  • 终极图像分类指南:从海豚到多类别的机器学习实战
  • odiff在大型项目中的应用:处理25000+图像快照的最佳实践
  • vokoscreenNG:专业级开源屏幕录制解决方案的5大核心优势
  • 2026年GitHub中比较热门的skills技能
  • 告别环境配置噩梦:用Docker Desktop + WSL2在Windows上5分钟搞定vLLM运行环境
  • 2026室内灯具品牌发展趋势及品质之选 - 品牌排行榜
  • PETRV2-BEV训练效果对比展示:nuscenes高精度vs xtreme1泛化挑战
  • Win11Debloat深度解析:让Windows重获新生的系统优化神器
  • AI-toolkit 实战:Flux.1-kontext 训练LoRA核心参数调优指南
  • Windows11下用g管理多版本Go的完整配置流程(含国内镜像设置)
  • 理性审视天津雅思培训格局:天津雅思培训机构应该怎么选 - 大喷菇123
  • Qt实现车载多媒体项目,包含天气、音乐、视频、地图、五子棋功能模块,免费下载源文件!
  • GPCC数据不止看趋势:手把手教你用MATLAB做降水信号的谐波分析(附周年振幅相位代码)
  • 2026北京刑事律师优质推荐榜 - 讯息观点
  • 通义千问2.5-7B省钱部署案例:GGUF量化仅4GB,3060流畅运行
  • rabbitmq新手福音,快马ai生成带详解注释的入门代码,轻松理解消息队列
  • 2026年预归类资质公司推荐及服务解析 - 品牌排行榜
  • postprocessing高级技巧:自定义效果开发与Shader编写教程
  • Nunchaku-flux-1-dev在教育领域的应用:自动生成教学示意图
  • TPXO9-atlas-v5数据保姆级下载与注册指南:手把手教你给Egbert教授发邮件申请
  • 2026年国产压电纳米平台哪家性价比高?精度不输进口,价格减半的厂家推荐 - 品牌推荐大师
  • 如何快速实现pycodestyle与Bitbucket Pipelines集成:Python代码检查自动化完整指南
  • Flagger自定义指标开发终极指南:扩展监控能力的10个最佳实践
  • PipelineDB滑动窗口聚合:实现时间敏感的数据分析
  • 线段树板子,懒标记,区间乘法,单点加法,区间求和