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

张开发
2026/4/7 17:28:58 15 分钟阅读

分享文章

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.javaframeworks/base/core/java/android/os/Handler.javaframeworks/base/core/java/android/os/Looper.javaframeworks/base/core/java/android/os/MessageQueue.javaAndroid 的 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;// 对象池复用机制避免频繁创建销毁命令对象privatestaticfinalObjectsPoolSyncnewObject();privatestaticMessagesPool;privatestaticintsPoolSize0;privatestaticfinalintMAX_POOL_SIZE50;// 从对象池获取命令对象而非 newpublicstaticMessageobtain(){synchronized(sPoolSync){if(sPool!null){MessagemsPool;sPoolm.next;m.nextnull;m.flags0;// 清除使用标记sPoolSize--;returnm;}}returnnewMessage();}// 命令执行完毕后回收到对象池voidrecycleUnchecked(){flagsFLAG_IN_USE;what0;arg10;arg20;objnull;// ... 重置所有字段synchronized(sPoolSync){if(sPoolSizeMAX_POOL_SIZE){nextsPool;sPoolthis;sPoolSize;}}}}// frameworks/base/core/java/android/os/Handler.javapublicclassHandler{// 发送命令将命令对象放入消息队列publicfinalbooleansendMessage(Messagemsg){returnsendMessageDelayed(msg,0);}publicfinalbooleansendMessageDelayed(Messagemsg,longdelayMillis){if(delayMillis0){delayMillis0;}returnsendMessageAtTime(msg,SystemClock.uptimeMillis()delayMillis);}publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis){MessageQueuequeuemQueue;if(queuenull){returnfalse;}returnenqueueMessage(queue,msg,uptimeMillis);}privatebooleanenqueueMessage(MessageQueuequeue,Messagemsg,longuptimeMillis){// 将当前 Handler 设置为命令的目标执行者msg.targetthis;msg.workSourceUidThreadLocalWorkSource.getUid();returnqueue.enqueueMessage(msg,uptimeMillis);}// post 方式将 Runnable 包装成 Message 命令对象publicfinalbooleanpost(Runnabler){returnsendMessageDelayed(getPostMessage(r),0);}privatestaticMessagegetPostMessage(Runnabler){MessagemMessage.obtain();m.callbackr;// Runnable 作为命令的具体执行体returnm;}// 命令分发Looper 取出 Message 后调用此方法执行publicvoiddispatchMessage(Messagemsg){// 优先级 1Message 自带的 Runnable callbackif(msg.callback!null){handleCallback(msg);}else{// 优先级 2Handler 构造时传入的全局 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(){finalLoopermemyLooper();finalMessageQueuequeueme.mQueue;for(;;){// 从消息队列中取出下一个命令可能阻塞Messagemsgqueue.next();if(msgnull){return;// 队列已退出}// 将命令分发给其目标 Handler 执行msg.target.dispatchMessage(msg);// 命令执行完毕回收到对象池msg.recycleUnchecked();}}}命令模式角色映射命令模式角色Handler 机制对应CommandMessage封装命令类型、参数和执行目标ConcreteCommand携带Runnable的Message或携带特定what值的MessageInvokerLooper从队列取出命令并触发执行ReceiverHandlerhandleMessage或Runnable.run执行具体逻辑CommandQueueMessageQueue命令排队、按时间排序案例二Runnable 作为命令对象源码路径libcore/ojluni/src/main/java/java/lang/Runnable.javaRunnable接口本身就是命令模式中最简洁的命令接口定义。// libcore/ojluni/src/main/java/java/lang/Runnable.javaFunctionalInterfacepublicinterfaceRunnable{// 命令接口只有一个执行方法publicabstractvoidrun();}在 Android 中Runnable被广泛用作命令对象// frameworks/base/core/java/android/view/View.javapublicclassView{// View.post() 将 Runnable 命令投递到主线程执行publicbooleanpost(Runnableaction){finalAttachInfoattachInfomAttachInfo;if(attachInfo!null){// 通过 View 关联的 Handler 将命令入队returnattachInfo.mHandler.post(action);}// View 未 attach 时暂存命令到 RunQueue 中getRunQueue().post(action);returntrue;}// 延迟执行命令publicbooleanpostDelayed(Runnableaction,longdelayMillis){finalAttachInfoattachInfomAttachInfo;if(attachInfo!null){returnattachInfo.mHandler.postDelayed(action,delayMillis);}getRunQueue().postDelayed(action,delayMillis);returntrue;}// 取消命令从队列中移除指定的 RunnablepublicbooleanremoveCallbacks(Runnableaction){if(action!null){finalAttachInfoattachInfomAttachInfo;if(attachInfo!null){attachInfo.mHandler.removeCallbacks(action);}getRunQueue().removeCallbacks(action);}returntrue;}}关键设计分析Runnable将操作封装为对象可以被存储RunQueue、延迟执行postDelayed、取消removeCallbacksView.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(mRetriesMAX_RETRIES){mHandler.sendEmptyMessage(MCS_GIVE_UP);handleServiceError();returnfalse;}// Step 2执行具体的安装操作子类实现handleStartCopy();// Step 3处理安装结果子类实现handleReturnCode();restrue;}catch(RemoteExceptione){mHandler.sendEmptyMessage(MCS_RECONNECT);resfalse;}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:Int0overridefunexecute(){previousBrightnessdevice.brightness device.setBrightness(targetBrightness)}overridefunundo(){device.setBrightness(previousBrightness)}overridevaldescription:Stringget()亮度:$previousBrightness-$targetBrightness}/** * 具体命令切换设备开关状态 */classTogglePowerCommand(privatevaldevice:SmartDevice):DeviceCommand{privatevarwasPoweredOn:Booleanfalseoverridefunexecute(){wasPoweredOndevice.isPoweredOnif(wasPoweredOn)device.powerOff()elsedevice.powerOn()}overridefunundo(){if(wasPoweredOn)device.powerOn()elsedevice.powerOff()}overridevaldescription:Stringget()if(wasPoweredOn)关闭设备else开启设备}/** * 命令调用者管理命令的执行、撤销和重做 */classCommandManager(privatevalmaxHistorySize:Int50){privatevalundoStackArrayDequeDeviceCommand()privatevalredoStackArrayDequeDeviceCommand()funexecute(command:DeviceCommand){command.execute()undoStack.addLast(command)redoStack.clear()// 执行新命令后清空重做栈// 限制历史记录大小if(undoStack.sizemaxHistorySize){undoStack.removeFirst()}}funundo():Boolean{if(undoStack.isEmpty())returnfalsevalcommandundoStack.removeLast()command.undo()redoStack.addLast(command)returntrue}funredo():Boolean{if(redoStack.isEmpty())returnfalsevalcommandredoStack.removeLast()command.execute()undoStack.addLast(command)returntrue}funcanUndo():BooleanundoStack.isNotEmpty()funcanRedo():BooleanredoStack.isNotEmpty()/** 获取操作历史记录 */fungetHistory():ListStringundoStack.map{it.description}}场景二异步命令队列将命令模式与协程结合实现可取消、可重试的异步命令队列。/** * 异步命令接口 */interfaceAsyncCommand{valid:StringvalretryCount:Intget()0suspendfunexecute():ResultUnit}/** * 异步命令队列串行执行命令支持重试和取消 */classAsyncCommandQueue(privatevalscope:CoroutineScope){privatevalqueueChannelAsyncCommand(Channel.UNLIMITED)privatevalpendingCommandsConcurrentHashMapString,AsyncCommand()privatevarprocessingJob:Job?nullinit{startProcessing()}/** 将命令加入队列 */funenqueue(command:AsyncCommand){pendingCommands[command.id]command queue.trySend(command)}/** 取消指定命令如果尚未执行 */funcancel(commandId:String):Boolean{returnpendingCommands.remove(commandId)!null}privatefunstartProcessing(){processingJobscope.launch{for(commandinqueue){// 检查命令是否已被取消if(!pendingCommands.containsKey(command.id)){continue}varattempts0varsuccessfalsewhile(attemptscommand.retryCount!success){valresultcommand.execute()successresult.isSuccessif(!success){attemptsif(attemptscommand.retryCount){// 指数退避重试delay(1000L*(1shl(attempts-1)))}}}pendingCommands.remove(command.id)}}}funshutdown(){queue.close()processingJob?.cancel()}}/** * 具体的异步命令上传设备日志 */classUploadLogCommand(overridevalid:StringUUID.randomUUID().toString(),overridevalretryCount:Int3,privatevaldeviceId:String,privatevallogData:ByteArray,privatevalapiService:ApiService):AsyncCommand{overridesuspendfunexecute():ResultUnitrunCatching{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 机制是这一模式在系统级框架中的典范实现。最佳实践命令对象应自包含命令对象应持有执行所需的全部参数和接收者引用不依赖外部状态。Message通过what、arg1、arg2、obj、data等字段实现了自包含考虑对象池复用在高频创建命令的场景中如 Handler 消息循环使用对象池Message.obtain()避免频繁的对象分配和 GC撤销操作需要保存前置状态如果命令需要支持undo()必须在execute()执行前保存接收者的原始状态宏命令组合多个命令可以组合为一个宏命令CompositeCommand实现批量操作的原子化执行和撤销优先使用函数式接口在 Kotlin 中简单命令可以直接使用 lambda 表达式() - Unit无需定义额外的命令类。仅当需要撤销、持久化等高级特性时才使用完整的命令类命令与队列分离命令对象只负责做什么队列管理排序、调度、重试交给独立的 Invoker 处理。这一分离使得同一组命令可以在不同的调度策略下复用

更多文章