Android中Handler有何作用?如何使用?

本文详细解析了Android中的Handler机制,包括其作用、概念、工作原理及应用。Handler作为Android中实现异步消息处理的重要组件,能够帮助开发者更好地管理和调度线程间的通信。

一  Handler作用和概念

包含线程队列和消息队列,实现异步的消息处理机制,跟web开发的ajax有异曲同工之妙。
  1. 运行在某个线程上,共享线程的消息队列;
  2. 接收消息、调度消息,派发消息和处理消息;
  3. 实现消息的异步处理;

Handler能够让你发送和处理消息,以及Runnable对象;每个Handler对象对应一个Thread和Thread的消息队列。当你创建一个Handler时,它就和Thread的消息队列绑定在一起,然后就可以传递消息和runnable对象到消息队列中,执行消息后就从消息队列中退出。

Handler的作用就是:调度消息和runnable对象去被执行;使动作在不同的线程中被执行。

当一个应用程序中进程被创建时,它的主线程专门运行消息队列(messageQueue),去管理顶层的应用程序相关的对象如:activity,broadcastReceiver,windows等,你可以创建你的Thread,和主线程进行交互——通过Handler,交互的方法就是通过post或者sendMessage。但是在你的新线程中,给定的Message或者Runnable,会在适当的时候的被调度和处理。

(即不会被立即处理——阻塞式)。

实际上就是建立消息处理模型/系统

要学习Handler,看到肯定是和消息有关,可能还是需要先熟悉一下消息系统的构成和简单原理。

下面就先学习一下消息系统的基本原理。

二 消息系统的基本原理和构成

从一般的消息系统模型的建立大致构成以下几个部分:

  • 消息原型
  • 消息队列
  • 发送消息
  • 消息循环
  • 消息获取
  • 消息派发
  • 消息处理

大致模型图如下:

消息系统模型一般会包括以上七个部分(消息原型,消息队列,消息发送,消息循环,消息获取,消息派发,消息处理)。实际上的核心是消息队列和消息循环,其余部分都是围绕这两部分进行的。

从前面文档的分析中我们知道Handler就是用来建立消息处理的系统模型,那么和这里基本消息系统模型相比,那么Handler又是如何囊括这七个部分的呢?

在Android中对这六个部分进行了抽象成四个独立的部分(消息循环):

Handler,Message,MessageQueue,Looper;

  • Message就是消息原型,包含消息描述和数据,
  • MessageQueue就是消息队列,
  • Looper完成消息循环 ,
  • Handler就是驾驭整个消息系统模型,统领Message,MessgeQueue和Looper;

Handler能够实现消息系统模型,那么具体是如何进行工作的呢,下面探究一下这其中工作的方法和原理。

三 Handler工作原理分析

要了解Handler工作原理,先看一下这个系统模型具体组成的层次结构框架是个什么样的。

1,Looper:

实现Thread的消息循环和消息派发,缺省情况下Thread是没有这个消息循环的,即没有Looper;

需要主动去创建,然后启动Looper的消息循环loop;与外部的交互通过Handler进行;

2,MessageQueue:

消息队列,由Looper所持有,但是消息的添加是通过Handler进行;

消息循环和消息队列都是属于Thread,而Handler本身并不具有Looper和MessageQueue;

但是消息系统的建立和交互,是Thread将Looper和MessageQueue交给某个Handler维护建立消息系统模型。所以消息系统模型的核心就是Looper。消息循环和消息队列都是由Looper建立的,而建立Handler的关键就是这个Looper。

一个Thread同时可以对应多个Handler,一个Handler同时只能属于一个Thread。Handler属于哪个Thread取决于Handler在那个Thread中建立。

在一个Thread中Looper也是唯一的,一个Thread对应一个Looper,建立Handler的Looper来自哪个Thread,Handler属于哪个Thread。

故建立Thread消息系统,就是将Thread的Looper交给Handler去打理,实现消息系统模型,完成消息的异步处理。

Handler与Thread及Looper的关系可以用下面图来表示:

Handler并不等于Thread,必须通过Thread的Looper及其MessageQueue,用来实现Thread消息系统模型,依附于Thread上。

在线程建立Handler时:

使Handler满足消息系统需要的条件,将Thread中的Looper和MessageQueue交给Handler来负责维护。

在线程中建立Handler,需要做以下工作:

  1. 获取Thread中的Looper交给Handler的成员变量引用维护;
  2. 通过Looper获取MessageQueue交给Handler的成员变量引用维护。

那么消息系统模型建立完成之后,按照消息系统运行,从Handler来看就是发送消息派发消息,与此线程消息系统的交互都由Handler完成。

消息发送和派发接口:

  1. post(runnable)消息,Runnable是消息回调,经过消息循环引发消息回调函数执行;
  2. sendMessage(Message)消息,经过消息循环派发消息处理函数中处理消息;
  3. dispatchMessage派发消息,若是post或带有回调函数则执行回调函数,否则执行消息处理函数Handler的handleMessage(通常派生类重写)。

以上就是Handler如何实现Thread消息系统模型的大致介绍。下面将具体分析是如何实现消息系统模型运行的。

四 Handler实现流程分析

我们知道Handler就是一个消息系统的外壳,属于某个Thread并包装了Thread的Looper及其MessageQueue;与外部进行交互(同一个线程内或者线程之间),接收派发和处理消息,消息系统模型的核心是Looper。

下面看看Handler是如何建立跑起来的,以msg消息为例,runnable实质是一样。

1,Handler的建立

Handler唯一属于某个Thread,在某个Thread中建立Handler时,需要获取Thread的Looper及其MessageQueue,建立Handler关键是Looper的来源。

Handler提供了好几个构造函数但其本质一致:由外部传入Looper:当前线程或其他线程

  public Handler(Looper looper) {
        //初始化构建消息系统参数
              mLooper = looper;
              mQueue = looper.mQueue;
              mCallback = null;
  }

从当前线程获取:由创建Handler的Thread决定

  public Handler() {
        //初始化构建消息系统参数
              mLooper = Looper.myLooper();
              mQueue = mLooper.mQueue;
              mCallback = null;
  }

  public static Looper myLooper() {
        return sThreadLocal.get();
    }

不管哪种方式,我们知道Thread在默认情况下是没有建立消息循环Looper实例的。要实现消息循环必须确保Thread的Looper建立。如何确保呢?

Looper提供了静态函数:

public static void prepare() {
     if (sThreadLocal.get() != null) {
              throw new RuntimeException("Only one Looper may be created per thread");
     }

     sThreadLocal.set(new Looper());
}

//存储线程的局部变量
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

看到这里刚开始让我很是奇怪和迷惑:

Looper一个独立的类,又不属于某个Thread,而这里创建Looper的函数又是静态的,属于整个Looper类;创建Looper之后交给静态成员变量sThreadLocal保存,获取sThreadLocal.get(),那么一个静态变量属于整个类,属性更改始终有效。一次创建之后

sThreadLocal.get()永远都不等于null!

而Thread和Looper是唯一对应的,那这里岂不是所有的Thread都是用同一个Looper,不可能!所以肯定这个ThreadLocal是有玄机的。网上一查:

ThreadLocal:

维护线程的变量,为每个使用该变量的线程实例提供独立的变量副本,每个线程都能够独立使用该变量,而互不影响。(详细可参考:http://blog.youkuaiyun.com/qjyong/article/details/2158097

所以每一个线程调用Looper.prepare时,都会创建为其唯一的Looper。要建立Handler,需要先创建线程的Looper,才能建立消息系统模型。通过Looper我们建立了Thread上的消息系统模型Handler,可以来进行消息系统的一系列流程了。

2,消息发送

消息发送两种方式:post和sendMessage;

  • post:针对runnable对象;Runnable是一个接口,就是一个回调函数(提供了run方法)
  • sendMessage:针对Message对象;

下面通过代码具体看一下这个过程:

public final boolean post(Runnable r){
       return  sendMessageDelayed(getPostMessage(r), 0);
}

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

看到post和sendMessage发送消息时,仅仅是对象不同而已,Runnable和Message;但实际上都是Message的形式来描述。

这跟我通常理解的消息机制不同:

通常post消息是将消息加入到消息队列中并不立即执行就返回,send消息是立即执行等待消息执行完才返回。而这里post或者send都是将消息放入到消息队列中,然后立即返回,等待消息循环时获取消息被执行。

这里提供了众多的消息发送方法来指定消息的执行时间和顺序,具体可以查看源代码。

消息执行顺序是根据消息队列中消息的排列顺序而定。下面看一下发送消息后将消息加入到消息队列中的代码:

由Handler调用MessageQueue的enqueueMessage方法:

  final boolean enqueueMessage(Message msg, long when) {

              Message p = mMessages;

              if (p == null || when == 0 || when < p.when) {
                     msg.next = p;
                     mMessages = msg;
              }
              else {
                     Message prev = null;
                     while (p != null && p.when <= when) {
                            prev = p;
                            p = p.next;
                     }

                     msg.next = prev.next;
                     prev.next = msg;
              }
              ……
  }

可以看到是按照时间顺序将消息加入到MessageQueue中;

现在将消息加入到消息队列中存储起来,消息并未得到处理,下一步必然是如何派发消息和处理消息。

3,消息派发

建立Thread消息循环由Looper完成,存在一个消息调度死循环:

  public static void loop() {
       MessageQueue queue = me.mQueue;
       while (true) {
              Message msg = queue.next(); // might block
              if (msg != null) {
                     if (msg.target == null) {
                            // No target is a magic identifier for the quit message.
                            return;
                     }

                     //派发消息 到target(Handler)
            msg.target.dispatchMessage(msg);

            //回收Msg到msgPool
                     msg.recycle();
              }
       }
  }

这里看到消息派发是由Message的target完成,这个target是什么呢?是一个Handler。消息系统是通过Handler用来与外部交互,把消息派发出去。可以看到没有这个Handler,消息循环将结束。

消息派发由Looper通过Handler完成:

  public void dispatchMessage(Message msg) {

       //首先判断runnable对象
       if (msg.callback != null) {
              handleCallback(msg);
       }
       else {
              //整个消息系统的回调函数 可以不用实现自己Handler
              if (mCallback != null) {
                     if (mCallback.handleMessage(msg)) {
                            return;
                     }
              }
              //消息处理 通常交给Handler派生类
              handleMessage(msg);
       }
  }

通过消息派发,这样就实现消息的异步处理。

4,消息原型

前面看到消息发送有两种方式:

post(Runnable对象),sendMessage(Message对象),而中间都是通过Message对象

保存在MessageQueue中。然后消息派发时处理方式不同。如果在sendMessage时将将消息对象附上Runnable对象,则post和sendMessage没有区别了。所以这两种方式很好理解基本一致,处理的方式不同罢了。

消息系统模型中,我们的真正的消息原型是什么,都具有那些功能,下面看一下Message中到底包含了那些东西,能有效帮助我们合理的运用消息系统来完成一些任务和处理。

Message消息原型:

  public final class Message implements Parcelable {
         //标识消息
         public int what;
         int flags;
         long when;

         //传递简单数据
         public int arg1;
         public int arg2;

         //传递较复杂数据 对象
         public Object obj;
         Bundle data;

         //处理消息的目标Handler
         Handler target;   

         //消息派发时 执行的Runnable对象
         Runnable callback;  

         //使消息形成链表
         Message next;

         //建立一个消息pool,回收msg,以避免重复创建节约开销
         private static Message sPool;
         private static int sPoolSize = 0;
         private static final int MAX_POOL_SIZE = 10;
  }

看到提供了很丰富的属性来描述消息,针对具体问题选择使用那些属性去怎么样描述消息了。获取新的Message对象时,Message提供了obtain方法:避免我们自己去分配Message新的对象,通过obtain获取,可能从MessagePool中获取,节约开销。

下面看一下这个MessagePool是如何建立的:

通常消息处理完毕的时候,消息也基本上处于无用状态可以释放回收了。对于需要频繁的创建释放的对象来说,创建和释放类实例都是要开销的,太频繁的使开销增大不好,像Message这种很有可能会频繁的创建。于是我们可以将创建的对象用完之后保存在一个Pool里面,以便再重复利用节约频繁创建释放开销。是如何建立的呢?必然是在消息处理完毕之后才能进行。

MessagePool建立:

public static void loop() {
       while (true) {
              //派发消息
              msg.target.dispatchMessage(msg);

              //消息处理完毕 回收
        msg.recycle();
    }
}     

public void recycle() {
       //回收Message 建立全局的MessagePool
       if (sPoolSize < MAX_POOL_SIZE) {
           next = sPool;
           sPool = this;
           sPoolSize++;
       }
}

五 Handler的应用

以上这就是整个Handler作用及消息系统模型的建立。

使用也非常简单,虽然有很多方式,但只要理解Handler是建立在Looper上,实现Thread的消息系统处理模型,实现消息异步处理,我想对与Handler基本应用上没有什么不能理解的了。其他方面可以去看源码了。

Handler使用起来是非常简单的,关键就是如何利用消息的异步处理,来合理的完成我们需要功能和任务。对于一个Thread,我们使用好几个Handler来进行异步处理,也可以创建新的Thread,通过Handler来实现消息异步处理等等,应用场景很多如何用的好用的合理,这就没什么经验了。

转载请注明:Android开发中文站 » Handler有何作用?如何使用?

<think>好的,我现在需要回答用户关于JmDNS和Android NsdManager本质区别的问题。首先,我得回顾之前提供的关于JmDNS源码学习的内容,以及用户提到的引用[1]和[2],这些引用主要涉及Android使用JmDNS的问题,比如类找不到错误和正确构造实例的方法。用户现在想比较两者的核心差异及使用场景,这需要从协议实现、API设计、平台支持等多个方面分析。 首先,JmDNS是一个Java库,实现了mDNS协议,允许在局域网内进行服务发现。而NsdManager是Android系统自带的API,同样用于服务发现,但可能基于不同的底层实现。需要确认NsdManager是否也使用mDNS或者另一种协议。根据之前的知识,NsdManager确实使用mDNS,但可能进行了封装和优化以适应Android环境。 接下来,核心差异可能包括: 1. **协议实现与封装程度**:JmDNS直接使用mDNS协议,开发者需要处理更多底层细节,如多播通信、报文解析等。而NsdManager作为系统API,可能隐藏了这些细节,提供更高层次的抽象,简化开发流程。 2. **平台集成与兼容性**:NsdManager作为Android原生组件,与系统深度集成,可能更好地处理网络权限、生命周期管理和设备兼容性问题。例如,用户提到的引用[2]指出在Android上必须使用特定的构造函数来创建JmDNS实例,否则可能出现问题,而NsdManager可能自动处理这些配置。 3. **线程管理与回调机制**:JmDNS可能需要开发者自行处理多线程环境下的回调,而NsdManager可能内置了线程管理,确保回调在主线程执行,避免UI线程阻塞。 4. **资源释放与生命周期**:JmDNS需要显式调用close()方法释放资源,而NsdManager可能通过绑定到Android组件的生命周期(如Activity的onDestroy)自动管理资源,减少内存泄漏风险。 5. **功能与扩展性**:JmDNS作为开源库,可能提供更多自定义选项,允许开发者修改源码以适应特定需求,如调整缓存策略或扩展服务类型。NsdManager作为系统API,扩展性可能较差,但稳定性和兼容性更有保障。 使用场景方面,如果应用需要跨平台支持(如同时在Android和Java桌面应用中使用服务发现),JmDNS可能更合适。而纯Android应用开发,尤其是对系统集成和稳定性要求较高的场景,NsdManager会是更优选择,避免兼容性问题,如引用[1]中的类找不到错误。 另外,需要验证NsdManager是否真的基于mDNS。查阅Android文档确认,NsdManager确实使用mDNS和DNS-Based Service Discovery (DNS-SD),这与JmDNS的协议基础相同。因此,两者的核心协议一致,但实现方式和API设计不同。 可能还需要考虑性能差异,JmDNS在非Android环境中的表现,以及NsdManager在不同Android版本上的兼容性。例如,旧版本Android是否完全支持NsdManager的所有功能,或者是否存在已知的bug。 总结时,需要将上述分析整理成结构化的回答,突出核心差异和使用场景,并确保引用之前提到的用户引用,如正确构造JmDNS实例的问题,以增强回答的相关性。</think>### JmDNS vs Android NsdManager 核心差异及使用场景分析 JmDNS 和 Android 的 `NsdManager` 虽然都用于实现局域网服务发现,但在实现机制、平台依赖性和开发适配性上存在本质差异。以下从**协议实现层级**、**API 设计模式**、**平台兼容性**和**性能优化**四个维度对比两者的核心差异: --- #### 一、协议实现层级差异 | 特性 | JmDNS | Android NsdManager | |---------------------|--------------------------------|----------------------------------| | **协议基础** | 纯 Java 实现的 mDNS 协议 | 基于系统原生 mDNS/DNS-SD 封装 | | **底层控制** | 直接操作多播报文(如 `DNSIncoming`/`DNSOutgoing`) | 通过 Binder 调用系统服务 | | **报文解析** | 需开发者关注报文格式(如 `DNSRecord` 解析) | 系统自动处理,开发者仅关注回调 | **技术细节**: JmDNS 的协议实现完全开源(见 `javax.jmdns.impl` 包),开发者可自定义缓存策略或扩展协议[^1]。而 `NsdManager` 的底层实现被 Android 系统封装,开发者无法直接干预协议逻辑。 --- #### 二、API 设计模式对比 | 特性 | JmDNS | Android NsdManager | |---------------------|--------------------------------|----------------------------------| | **线程模型** | 回调可能触发在非主线程 | 强制主线程回调(通过 `Handler`) | | **生命周期管理** | 需手动调用 `close()` 释放资源 | 自动绑定到 `Context` 生命周期 | | **错误处理** | 需捕获 `IOException` 等异常 | 通过 `NsdManager.DiscoveryListener` 返回错误码 | **代码示例对比**: ```java // JmDNS 注册服务(需手动管理线程) ServiceInfo serviceInfo = ServiceInfo.create("_http._tcp.local.", "MyService", 8080, "path=/"); jmdns.registerService(serviceInfo); // 可能抛出 IOException // NsdManager 注册服务(系统托管) NsdServiceInfo nsdInfo = new NsdServiceInfo(); nsdInfo.setServiceName("MyService"); nsdInfo.setServiceType("_http._tcp."); nsdInfo.setPort(8080); nsdManager.registerService(nsdInfo, NsdProtocol.TCP, registrationListener); ``` --- #### 三、平台兼容性与适配问题 | 场景 | JmDNS 痛点 | NsdManager 优势 | |---------------------|--------------------------------|----------------------------------| | **网络接口绑定** | Android 需显式指定 IP(引用[2]) | 自动选择可用网络接口 | | **权限问题** | 需声明 `CHANGE_WIFI_MULTICAST_STATE` | 系统隐式处理权限 | | **类加载异常** | 可能因 ProGuard 混淆导致崩溃(引用[1]) | 系统 API 免配置 | **关键问题**: JmDNS 在 Android 上需通过 `JmDNS.create(deviceIpAddress, hostname)` 显式绑定网络接口,否则可能因多播通信失败导致服务不可见[^2]。而 `NsdManager` 内部自动处理网络切换和接口选择。 --- #### 四、性能与扩展性 | 指标 | JmDNS | Android NsdManager | |---------------------|--------------------------------|----------------------------------| | **响应延迟** | 依赖 Java 多播实现,延迟较高 | 系统级 Native 优化,延迟更低 | | **扩展性** | 支持自定义协议扩展(如修改 `DNSCache`) | 仅支持标准 DNS-SD 功能 | | **资源占用** | 需维护独立线程和 Socket | 共享系统服务,资源占用低 | --- ### 使用场景建议 1. **优先选择 NsdManager**: - 纯 Android 应用开发 - 需要自动处理网络切换和生命周期 - 避免 ProGuard 混淆或类加载问题(引用[1]) 2. **选择 JmDNS 的场景**: - 跨平台需求(如 Android + Java 桌面应用互联) - 需深度定制协议逻辑(如修改缓存过期时间) - 支持旧 Android 版本(NsdManager 需 API 16+) --- ### 引用说明 [^1]: JmDNS 的协议实现开放源码,允许开发者直接修改 `DNSCache` 等核心类优化服务发现逻辑。 [^2]: Android 平台使用 JmDNS 必须显式绑定网络接口,避免默认构造函数导致的通信问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值