Handle的一些用法

本文详细介绍了Android中Handler机制的工作原理及使用方法,包括Handler、Looper、MessageQueue等核心组件的作用和交互过程,以及如何在主线程和子线程中正确使用Handler进行消息传递。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载:http://www.apkbus.com/blog-815579-61293.html

只是给自己做一下笔记而已。


1、Handler(消息处理者)

        <1>使用handeler的步骤:
                (1)创建一个Handler;
   
  1. private Handler handler = new Handler(){
  2. //用来接收和处理从子线程发送回来的信息,在主线程中接收
  3. public void handleMessage(Message msg) {
  4. String content = (String) msg.obj;
  5. int arg1 = msg.arg1;
  6. int arg2 = msg.arg2;
  7. int what = msg.what;
  8. textView.setText(content+arg1 +arg2 +what);
  9. };
  10. };
                   (2)在需要用到数据传输的地方通过handler得到message对象,通过message携带要传输的数据;
    
  1. //1.通过handler得到一个信息
  2. Message message = handler.obtainMessage();
  3. //2.通过这个信息,携带数据
  4. message.obj = content;
  5. message.arg1 = 100;
  6. message.arg2 = 200;
  7. message.what = 300;
取Message对象的最好方法是调用Message.obtain()或者Handler.obtainMessage(), 这样是从一个可回收对象池中获取Message对象。
                   (3)直接通过handler发送消息,到主线程;
    
  1. //3.利用了handler发送消息,从子线程发送到了主线程
  2. handler.sendMessage(message);
                   (4)通过重写handleMessage()方法,接收传输过来的数据。
    
  1. //用来接收和处理从子线程发送回来的信息,在主线程中接收
  2. public void handleMessage(Message msg) {
  3. String content = (String) msg.obj;
  4. int arg1 = msg.arg1;
  5. int arg2 = msg.arg2;
  6. int what = msg.what;
  7. textView.setText(content+arg1 +arg2 +what);
  8. };
         <2>handleMessage():在主线程中,构造Handler对象时,重写此方法;
    
  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. ......
  4. }
         <3>sendEmptyMessage():用在子线程中,发送空消息;
    
  1. //1.发送一个空消息(不用构造message对象,而是直接给一个消息标记)
  2. handler.sendEmptyMessage(1);
         <4>sendMessage():用在工作线程中,立即发送消息;
   
  1. handler.sendMessage(message);
  2. //发送延迟消息 3000:延迟的时间(单位是毫秒)
  3. //handler.sendMessageDelayed(message, 3000);
  4. //发送延迟空消息
  5. //handler.sendEmptyMessageDelayed(5, 3000);
2、Message(消息)
         <1> 使用Message需要注意4点:
                    (1)Message虽然也可以通过new来获取,但是通常使用Message.obtain()或Handler.obtainMessage()方法来从消息池中获得空消息对象,以节省资源;
   
  1. //2.发送一个带message的信息
  2. Message message = handler.obtainMessage();
  3. //获取到一个信息对象
  4. Message message2 = Message.obtain();
                    (2)如果一个Message只需要携带简单的int型数据,应优先使用arg1和arg2属性来传递数据,这样比其他方式节省内存;
   
  1. message.arg1 = 2; //arg1和arg2都是用来传输整形
  2. message.arg2 = 3;
                    (3)尽可能使用Message.what来标识信息,以便用不同的方式处理Message。
   
  1. message.what = 4; //也是用来传输整数类型,但是一般用来传输message标记
                     (4)如果需要从工作线程返回很多数据信息,可以借助Bundle对象将这些数据集中到一起,然后存放到obj属性中,再返回到主线程。
         <2>arg1:用来存放整型数据
         <3>arg2:用来存放整形数据
         <4>obj:用来存放Object数据
         <5>what:用于指定用户自定义的消息代码,便于主线程接收后,根据消息代码不同而执行不同的操作
3、Thread(将消息发送给主线程)
4、Handler运行原理
         <1> Handler、Looper、Message、MessageQueue之间的关系:
Handler,Looper和MessageQueue的三角关系
 
         <2> Looper和MessageQueue一一对应,创建一个Looper的同时,会创建一个MessageQueue; 而Handler与它们的关系,只是简单的聚集关系,即Handler里会引用当前线程里的特定Looper和MessageQueue;
在一个线程中,只能有一个Looper和MessageQueue,但是可以有多个Handler,而且这些Handler可以共享一个Looper和MessageQueue;
         <3> Message被存放在 MessageQueue中,一个 MessageQueue中可以包含多个Message对象。
        【备注:】
         (1) Looper对象用来为一个线程开启一个消息循环,从而操作MessageQueue;
         (2) 默认情况下,Android创建的线程没有开启消息循环,但是主线程例外。
         (3) 系统自动为主线程创建Looper对象,开启消息循环;
         (4) 所以主线程中使用new来创建Handler对象。而子线程中不能直接new来创建Handler对象就会异常。
         (5)利用handler把数据从主线程中发送到子线程:
                   在主线程中,自带一个有loop对象,并且这个loop对象在循环工作;
                   但是在子线程中接收数据之前先调用Looper.prepare(),再调用Looper.loop()方法。
         (6) 子线程中创建Handler对象,步骤如下:
          Looper.prepare();
          Handler handler = new Handler() {
               //handlemessage(){}
          }
          Looper.loop();
         <4> Looper:消息泵,不断地从MessageQueue中抽取Message执行。一个MessageQueue需要一个Looper
         <5> Hanlder:消息处理者
                      (1) 在新启动的工作线程中发送消息
                      (2) 在主线程中获取、并处理消息
         <6> MeessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行
         <7> Message:消息,包含线程处理的数据和消息参数
         <8> Thread:通过主线程的handler对象发送消息给主线程,并更新UI
<think>我们被要求使用WinDbg中的!handle命令来排查CloseHandle调用缓慢的问题。根据引用[1]中提到,WinDbg的!handle命令可以列出当前进程的所有句柄,这有助于我们分析句柄的状态。在CloseHandle调用缓慢的场景中,我们主要关注两个方面:1.句柄的引用计数:如果句柄被多次复制(例如通过DuplicateHandle),那么CloseHandle需要等待所有引用都被关闭才会真正释放内核对象。2.句柄关联的未完成I/O操作:例如文件或套接字句柄,如果有未完成的异步I/O,CloseHandle会等待这些操作完成(或可取消的情况下被取消)才会返回。使用WinDbg的!handle命令可以查看句柄的详细信息,包括其引用计数和对象类型。以下是具体步骤:步骤1:附加到目标进程在WinDbg中,通过File>AttachtoProcess附加到出现CloseHandle缓慢的进程。步骤2:使用!handle命令查看所有句柄的摘要输入命令:!handle00这个命令会列出当前进程的所有句柄,包括句柄值、类型和对象地址。步骤3:找到导致问题的句柄根据步骤2的输出,我们可以关注那些数量异常多的对象类型(比如文件、事件、线程等),或者根据程序逻辑找到可能被频繁操作的对象。步骤4:查看特定句柄的详细信息如果我们怀疑某个句柄(例如句柄值为0x1c)有问题,可以输入:!handle0x1cf这里的'f'表示显示详细信息。在详细信息中,我们可以查看:-对象类型(如File,Event,Thread等)-引用计数(ReferenceCount)-对于文件对象,可能还会显示当前未完成的I/O数量(如果有的话)步骤5:分析引用计数高的原因如果发现某个句柄的引用计数很高(比如远大于1),说明可能有其他线程或进程(通过DuplicateHandle)还在使用这个对象。我们需要检查代码中是否有地方复制了句柄但没有关闭。步骤6:检查未完成的I/O对于文件、管道、套接字等句柄,如果CloseHandle卡住,很可能是由于未完成的I/O操作。在!handle的详细输出中,不一定直接显示未完成I/O的数量,但我们可以通过以下方法:-使用!objref命令查看对象引用链(但需要先获取对象地址)-使用!irp命令查看挂起的IRP(需要知道驱动相关知识)另一种思路:使用ProcessExplorer工具(来自Sysinternals)可以更直观地看到每个句柄的引用计数和未完成I/O数量。步骤7:结合其他命令如果怀疑是未完成I/O导致的,我们还可以使用:!thread#查看当前线程的状态,看是否在等待某个I/O操作!iochain#查看与线程关联的I/O请求包(IRP)链但是,需要注意的是,CloseHandle的调用线程在等待未完成I/O时,通常会处于等待状态(WaitReason可能是Executive或WrQueue等)。步骤8:使用WPA(WindowsPerformanceAnalyzer)进行更深入的分析引用[3]中提到使用xperf进行性能跟踪,我们可以捕获CloseHandle调用期间的内核事件,分析阻塞点。示例:使用WPA分析CloseHandle的堆栈,看是否有长时间的等待。总结:在WinDbg中,通过!handle命令我们可以:-列出所有句柄,找出可疑句柄(数量多、类型关键)-查看特定句柄的详细信息,特别是引用计数-结合其他命令(如!thread)分析线程状态但是,对于未完成I/O,!handle命令提供的信息有限,需要结合其他工具和方法。引用[1]中提到WinDbg的!handle命令,以及任务管理器查看句柄数量,这些都可以作为辅助手段。另外,引用[2]中的示例代码展示了一个可能导致CloseHandle阻塞的场景:主线程等待一个长时间运行的线程(Sleep一小时)。在这种情况下,CloseHandle(hThread)在WaitForSingleObject返回后立即被调用,所以实际上不会阻塞,因为线程已经结束。但如果我们修改代码,不等待线程结束就直接CloseHandle,那么CloseHandle会立即返回(因为线程句柄的关闭不会影响线程执行)。然而,如果线程句柄被用于其他未完成操作(比如等待线程结束),则WaitForSingleObject会阻塞,而不是CloseHandle。因此,在实际调试中,我们需要根据具体情况分析。下面是一个使用!handle命令的示例:假设我们有一个进程,句柄0x1c是一个文件句柄,我们怀疑它导致CloseHandle缓慢:0:001>!handle0x1cfHandle1cTypeFileAttributes0GrantedAccess0x120089:ReadControl,SynchRead/List,ReadEA,ReadAttrHandleCount2PointerCount3Name\Device\HarddiskVolume2\test.txt这里HandleCount=2表示有两个句柄指向同一个对象(可能是当前进程或其他进程通过DuplicateHandle复制了句柄),PointerCount=3表示内核对象的总引用计数为3(包括内核模式引用)。如果PointerCount大于1,说明还有别的引用存在,CloseHandle调用后,该进程的句柄会被关闭,但对象不会销毁,因此CloseHandle不会阻塞。但是,如果这个文件句柄有未完成的异步I/O,那么CloseHandle会等待I/O完成,即使引用计数为1。因此,要排查未完成I/O,我们可能需要更深入的工具,如WPA或使用驱动调试。对于未完成I/O,我们可以尝试使用以下命令:!fileobj<对象地址>注意:!fileobj是WinDbg的一个扩展命令,需要安装Windows调试工具包,并且需要符号支持。它能够显示文件对象的详细信息,包括当前挂起的IRP。示例:首先通过!handle获取文件对象的地址(在输出中,Handle信息里通常会包含对象地址,如Object:xxxxxxxx)然后执行:!fileobjxxxxxxxx在输出中,我们可以查看当前是否有未完成的IRP。综上所述,排查CloseHandle缓慢的步骤:1.使用!handle00列出所有句柄,观察是否有异常。2.对可疑句柄使用!handle<handle>f获取详细信息,关注引用计数。3.如果是文件句柄,使用!fileobj<对象地址>查看未完成IRP。4.如果是线程或进程句柄,注意线程/进程是否已经终止(未终止的线程/进程不会导致CloseHandle阻塞,除非有未完成的操作,但通常不会)。5.使用WPA进行性能跟踪,捕获CloseHandle调用时的内核堆栈,分析等待事件。引用[1]中提到的WinDbg!handle命令是基础,引用[3]中的xperf命令(即WPA的前端)是更深入分析的工具。最后,根据引用[1]中的最佳实践,在代码中应确保异步I/O操作完成或显式取消后再关闭句柄,避免阻塞。</think>### 在 WinDbg 中使用 `!handle` 命令排查 CloseHandle 调用缓慢问题 #### 排查步骤及命令详解 1. **获取目标进程句柄列表** ```bash !handle 0 0 # 列出所有句柄的摘要信息 ``` **输出解析**: ``` Handle 0000000000000004 Type File GrantedAccess 0x120089 HandleCount 2 PointerCount 3 Name \Device\HarddiskVolume2\test.txt ``` - **关键字段**: - `HandleCount`:用户模式引用计数(>1 表示存在未释放引用) - `PointerCount`:内核模式总引用计数 - `Name`:对象关联资源路径(用于识别阻塞源) 2. **查看特定句柄的详细信息** ```bash !handle <HandleValue> f # 示例:!handle 0000000000000004 f ``` **关键诊断点**: - **引用计数异常**: ``` Object Specific Information File Object 0xffffe001a1b2b080 CurrentByteOffset 0 ``` `PointerCount` 值高表示存在未释放引用[^1] - **未完成 I/O 操作**: ``` Flags 0x40000 Asynchronous I/O ``` 出现 `Asynchronous I/O` 标志需检查挂起操作 3. **检查内核对象引用** ```bash !object <ObjectAddress> # 从!handle输出获取对象地址 ``` **输出分析**: ``` Object: ffffe001a1b2b080 Type: (ffff9480f1d0b0c0) File HandleCount: 1 PointerCount: 3 ``` `PointerCount > HandleCount` 表明存在内核驱动未释放引用 4. **定位 I/O 阻塞源** ```bash !irpfind <ObjectAddress> # 查找关联的IRP包 !irp <IrpAddress> # 分析I/O状态 ``` **阻塞场景判断**: - `Irp->CurrentLocation != 0`:I/O 未完成 - `Irp->Cancel = true`:取消操作被挂起 #### 典型问题诊断案例 1. **异步 I/O 未完成** ```bash !handle 0 0 # 发现文件句柄Flags含"Asynchronous I/O" !thread # 查看线程状态 !irpfind # 查找挂起IRP ``` **解决方案**: ```c CancelIoEx(hFile, NULL); // 显式取消I/O CloseHandle(hFile); ``` 2. **对象引用泄漏** ```bash !handle 000000000000000c f # 输出:PointerCount=5, HandleCount=1 !object ffffe001a1b2b080 ``` **解决方法**: - 检查跨进程 `DuplicateHandle()` 调用 - 使用 Process Explorer 定位持有者进程 3. **驱动资源阻塞** ```bash !handle <DeviceHandle> f # 输出含"\Driver\AVDriver"等第三方驱动名 !locks # 检查内核锁争用 ``` **处理建议**: - 更新或禁用问题驱动 - 检查驱动过滤栈 `!filtarget` #### 诊断流程图 ```mermaid graph TD A[CloseHandle卡顿] --> B[!handle 0 0] B --> C{存在高引用计数?} C -->|是| D[!object 分析内核引用] C -->|否| E{存在异步I/O标志?} E -->|是| F[!irpfind 查挂起操作] E -->|否| G[检查系统资源瓶颈] D --> H[定位未释放的引用源] F --> I[取消或等待I/O完成] G --> J[检查内存/锁争用] ``` #### 补充工具建议 1. **实时监控**: ```bash .logopen c:\handle_log.txt # 开启日志记录 !htrace -enable # 启用句柄跟踪 # 重现问题后 !htrace -snapshot # 分析句柄变化 ``` 2. **性能分析**: ```bash xperf -on PROC_THREAD+LOADER -stackwalk CloseHandle ``` 捕获内核调用栈(需 WPA 工具分析)[^3] 通过上述方法,可精确识别 CloseHandle 阻塞的根本原因,针对性解决句柄泄露、异步 I/O 未完成或驱动兼容性问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值