安卓Handler的学习笔记哦

先导知识

  1. Message:这是一个携带数据的容器类,用于在不同线程间传递数据。你可以在Message对象中放入任何你需要传递的数据,如文本、图片或者对象引用等。 它的 what 字段来标识消息的类型。

    产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。

    区分:Socket 用于进程间通信,而 Handler 消息机制用于同进程的线程间通信。可以说只要有异步线程与主线程通信的地方就一定会有 Handler。

  2. MessageQueue:这是一个消息队列,用于存放通过Handler发送的所有Message对象。它是线程安全的,并遵循先进先出(FIFO)原则,在 enqueueMessage()发送消息的时候,要指明 Message 的 target。

  3. Handler:这是开发者直接使用的对象,用于发送和处理消息。你可以通过Handler实例将一个或多个包含数据的Message对象发送到与之关联的线程的消息队列中。同时,Handler也包含了一个回调方法handleMessage(Message msg),用于处理从消息队列中取出的消息。

  4. 线程:一段可以执行的代码,当可执行的代码结束之后,线程生命周期也就终止,线程将会退出。但是对于 Android 主线程这种场景,我们绝对不希望执行一段时间之后主线程就自己停止掉,那么如何保证线程一直执行下去呢?简单的做法就是在线程中执行死循环,让线程一直工作下去不会停止退出。

  5. Looper:每个线程只能有一个Looper,它负责管理该线程的消息队列(MessageQueue)。Looper会不断地检查消息队列中是否有新消息,如果有则将其取出并分发给对应的Handler来处理。主线程(UI线程)默认已经有一个Looper,而在其他线程中使用Handler之前,需要先调用Looper.prepare()方法来初始化Looper。

    在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。

    为什么用prepare(),不直接new?

    直接 new Looper(), 可能是无法保证 Looper 唯一,而用 Looper.prepare() 能保证唯一性。

    如果当前线程还没有 Looper 实例,Looper.prepare() 会创建一个新的 Looper 实例。如果已经有一个 Looper 实例,Looper.prepare() 将直接返回它,而不会创建新的实例。因此,Looper.prepare() 确保了每个线程在其生命周期内只有一个 Looper 实例。

    死循环不会卡死线程吗?

    Looper 不是一直拼命干活的傻小子,而是一个有活就干没活睡觉的老司机,所以主线程的死循环并不是一直占据着 CPU 的资源不释放,不会造成过度消耗资源的问题。

    在 next 方法中 nativePollOne 方法会在消息为空的时候讲线程置为等待状态,直到有新的消息到来才会再次唤醒线程。所以 Looper.loop 虽然是死循环也不会卡死。

    那他什么时候结束循环?

    因为主线程的所有生命周期都是由 Handler 机制完成的,所以这个主线程中死循环不允许开发者手动退出,什么时候 App 退出了,这个死循环才会被退出。

    而子线程中没有这机制,所以在使用完毕后必须手动退出,否者线程会一直处于等待状态,不会被 GC。

Handler是什么?

Handler是Android中的一个消息处理器、消息分发对象、是Android系统提供的一套用来更新UI的机制,它可以接收并处理其他线程发来的消息。简单来说,Handler就是一个用来处理消息的工具类,它可以将消息发送给其他线程,也可以接收其他线程发送的消息进行处理。

Handler的执行机制

图片

图片

  1. 当一个线程需要使用Handler来处理消息时,首先需要创建一个Looper对象,并调用其prepare()方法创建一个与当前线程关联的消息队列。

  2. 接着,通过Looper的loop()方法启动消息循环,开始循环读取消息队列中的消息。

  3. 当有消息通过Handler的sendMessage()方法发送到消息队列时,Looper会不断从消息队列中取出消息,并将其传递给对应的Handler(调用 Message 的 target,即附属的 Handler 的 dispatchMessage方法)进行处理。

  4. Handler在接收到消息后,会调用自己的handleMessage()方法来处理消息。

  • handleMessage(Message) 方法中,我们可以编写各种不同的逻辑,并对当前情况下的消息进行处理。这通常包括对消息类型的检查以及消息携带的数据的解析和操作。

  • dispatchMessage(Message)

    当一个消息通过HandlersendMessage()或者其他相关方法发送后,该消息会先被加入到与该Handler关联的MessageQueue中排队。随后,Android的主线程(对于UI线程而言)中的Looper会不断地从这个消息队列中取出消息,并调用HandlerdispatchMessage()方法来分发处理这个消息。

    核心代码是判断 Message.target 是否为 null,不为 null 则将消息传递给目标 Handler,如果为 null,则直接抛出异常。

MessageQueue 是一个包含 Message 元素的队列, 从其名字翻译为队列,实际上是一个单向链表,因为每个 Message 对象中的 next 成员会指向下一个 Message 对象。

图片

Android 消息循环 MessageQueue 中消息处理的一般流程:

  1. 调用 nativePollOnce 进入阻塞MessageQueuenext() 方法会调用 Looperloop() 方法,而 loop() 方法内部会调用 nativePollOnce() 方法。这个方法用于等待新消息的到来,如果没有新消息,它会阻塞当前线程直到有新消息到达或者超时。

  2. 拿到 Message 列表的第一个消息: 当 nativePollOnce() 返回时,它可能会返回一个新消息,MessageQueue 会检查这个新消息,并准备进行处理。

  3. 查找消息列表中的异步消息: 如果队头的消息 (Message) 的 targetnull,这意味着它是一个异步消息。在这种情况下,MessageQueue 会查找队列中的第一个非空 target 的消息,因为异步消息总是优先于同步消息被处理。

  4. 处理队头的消息: 如果队头消息的 target 不为空,这意味着消息是一个同步消息,需要在特定线程中处理。MessageQueue 会检查这条消息的执行时间。如果当前时间已经达到或超过了消息的执行时间,MessageQueue 将立即处理这条消息。

  5. 休眠等待消息: 如果队头消息的执行时间还未到,MessageQueue 会根据这个时间计算出需要休眠的时间,然后调用 nativePollOnce() 并传入这个时间,使线程进入休眠状态直到消息应该被处理的时间点或者有新消息到达。

    1. 检查执行时间

      MessageQueue 会获取当前系统时间(通常是系统启动以来的毫秒数)。

      然后,它会将当前时间与队头消息的执行时间进行比较,这个执行时间是通过 MessagesetTargetTime(long uptimeMillis) 方法设置的,它代表了消息应该被处理的具体时间点。

    2. 判断是否处理消息

      如果当前时间等于或晚于消息的执行时间(即 SystemClock.uptimeMillis() >= message.targetTime),MessageQueue 将认为现在是处理这条消息的正确时间。

      在这种情况下,MessageQueue 会从消息队列中移除这条消息,并将其分发给对应的 Handler 进行处理。

    3. 休眠等待

      如果当前时间早于消息的执行时间,MessageQueue 将不会立即处理这条消息。相反,它会计算出需要休眠的时间,然后调用 nativePollOnce() 并传入这个时间,使当前线程休眠直到消息应该被处理的时间点,或者有新消息到达。

  6. 处理 Idle Handler: 当消息队列中没有更多待处理的消息时,MessageQueue 会检查是否有注册的 IdleHandler。如果有,MessageQueue 将调用它们的 queueIdle() 方法。IdleHandler 可以用来执行一些低优先级的任务,如内存清理或后台维护任务。

  7. 退出循环或等待下一次调用: 如果没有更多的消息和 IdleHandler 需要处理,MessageQueue 将退出当前的 next() 方法调用。Looperloop() 方法会再次调用 MessageQueuenext() 方法,继续等待和处理消息。

这个流程确保了 Android 应用能够以高效和有序的方式处理各种消息,包括 UI 更新、异步事件和低优先级的后台任务。通过优先处理异步消息,系统可以快速响应外部事件;通过在消息队列空闲时调用 IdleHandler,系统可以执行一些不需要立即完成的任务,从而提高应用的整体性能。

什么是安卓的Handler?

  1. 目标平台与环境

    • Android Handler:专为Android操作系统设计,紧密集成于Android框架中,主要解决Android应用开发中的线程间通信问题,特别是主线程(UI线程)与其他工作线程之间的消息传递。

    • 其他Handler:在更广泛的编程环境中,如Java的Socket编程或事件驱动编程模型中,Handler可能被用来处理网络通信的数据包、用户输入事件或其他类型的异步事件。这些Handler通常不涉及特定于移动平台的UI更新限制或线程模型。

  2. 线程模型与消息队列

    • Android Handler:与Looper和MessageQueue深度结合,形成了Android特有的消息循环处理机制。每个使用Handler的线程必须有其自己的Looper,这在主线程中默认存在,而在其他线程中需要手动初始化。

    • 其他Handler:在非Android环境中,Handler可能采用不同的线程管理和消息传递机制,比如基于事件监听器模型、回调函数、或者更通用的Future/Promise模式等,它们不一定依赖于特定的消息队列和循环器架构。

  3. UI更新与线程安全

    • Android Handler:特别强调在主线程安全地更新UI。由于Android框架规定UI组件只能在主线程修改,Handler成为从工作线程向主线程发送UI更新指令的关键工具。

    • 其他Handler:在非Android的软件开发中,虽然也可能存在类似的UI更新需求,但处理方式更为多样,可能直接在UI框架提供的线程安全机制下操作,或者通过事件总线、观察者模式等解耦合方式处理。

  4. 功能聚焦

    • Android Handler:其设计目的较为明确,即为了跨线程通信,尤其是为了处理后台任务结果并安全地更新UI。

    • 其他Handler:在其他编程领域,"Handler"这个概念可能更加泛化,可以指代任何处理特定类型事件、数据或消息的组件,其具体实现和用途更为广泛和多样化。

总的来说,Android的Handler具有鲜明的平台特性和应用场景,专注于解决Android平台上的多线程交互和UI更新问题,而其他编程领域的Handler概念则根据具体的技术栈和应用场景有着不同的实现和侧重点。

为什么用Handler?

  • 线程间通信:Android应用通常会涉及到多个线程的并发执行,而Handler机制提供了一种简单而高效的方式来实现线程间的通信。通过Handler,我们可以将消息发送到目标线程的消息队列中,并在目标线程中处理消息,从而实现线程间的通信和协作。

  • 异步消息处理:在Android开发中,许多任务需要在后台线程中执行,而将结果更新到UI界面上。Handler机制允许我们在后台线程中处理任务,并通过Handler将结果发送到UI线程,以便更新UI界面。这种异步消息处理的机制在保持UI的响应性和避免阻塞主线程方面起到了至关重要的作用。

  • 定时任务处理:Handler机制还可以用于处理定时任务。我们可以使用Handler的postDelayed()方法来延迟执行代码块或发送延时消息。这对于实现定时刷新、定时执行任务等场景非常有用。

Handler 的设计目的是为了解决不能在 Android 主线程中做耗时操作而又只有主线程才能访问 UI 的矛盾。通过 Handler 消息机制可以让开发者在子线程中完成耗时操作的同时在主线程中更新UI。

这里要思考一个问题:为什么 Android 非要规定只有主线程才能更新 UI 呢?

因为 Android 的所有 View 控件都不是线程安全的,如果多个线程尝试同时访问和修改UI组件,这可能导致竞争条件、数据不一致,甚至应用程序崩溃。

对于加锁这种方案也不可取,首先加锁之后会让 UI 访问逻辑变的很复杂,开发者需要时刻考虑多线程并发将会带来的问题,其次锁机制太重了它会严重影响 UI 访问效率。介于这两个缺点,最简单且高效的方法就是采用单线程的方式访问 UI。

Handler 机制应运而生,如果子线程想更新UI,需要通过 Handler发送消息给主线程,进而达到更新UI的目的。

Handler怎么用?

使用Handler的基本流程为:把消息发送到队列—> 然后喝茶等待—> 接收消息—> 分发消息—> 在回调中处理。

创建Handler对象

在使用Handler之前,需要先创建一个Handler对象。创建Handler对象的方式有两种:

  • 在主线程中创建Handler对象:

    在主线程中创建Handler对象非常简单,只需要在主线程中创建一个Handler对象即可,无需担心线程安全性问题,因为主线程默认具有一个消息循环器(即主线程的Looper)。

     Handler handler = new Handler();
  • 在子线程中创建Handler对象:

    在子线程中创建Handler对象需要先获取到主线程的Looper对象,然后使用Looper对象来创建Handler对象。

    每个线程都有自己的消息队列和 Looper 对象。

    当你在子线程中创建一个 Handler 对象时,如果没有指定 Looper,它默认会使用当前线程的 Looper,而不是主线程的。首先,我们的应用场景是要更新UI,而UI更新的操作只能在主线程里执行,所以这里必须指定Looper。

     Handler handler = new Handler(Looper.getMainLooper());

发送消息

创建Handler对象之后,就可以使用它来发送消息了。发送消息的方式有两种:

  • 使用Handler的post()方法:

    我们将 Runnable 对象发送到主线程的Handler所在的消息队列中。这告诉 Handler,我们希望它在主线程中稍后执行 Runnablerun() 方法。

     handler.post(new Runnable() {
         @Override
         public void run() {
             // 在Handler所在的线程中执行的代码
         }
     });
  • 使用Handler的sendMessage()方法:

    使用Handler的sendMessage()方法可以将一个Message对象发送到Handler所在的消息队列中。Message对象中可以携带一些数据,用于在Handler中进行处理。

    post() 方法不同,sendMessage() 不仅可以发送一个 Runnable 对象,还可以发送一个包含数据的 Message 对象。

     Message message = new Message();
     message.what = 1;
     message.obj = "Hello World!";
     handler.sendMessage(message);
使用场景
  • 如果你只需要执行一个简单的无状态的操作,或者不需要携带额外数据,使用 post()

  • 如果你需要携带数据,或者需要根据不同的消息类型执行不同的操作,使用 sendMessage()

  • handler.post()方法最终调用的还是sendMessage方法。

处理消息

当其他线程发送消息到Handler所在的消息队列中时,Handler就会接收到这些消息并进行处理。处理消息的方式有两种:

  • 重写Handler的handleMessage()方法:

    重写Handler的handleMessage()方法可以处理其他线程发送的消息。handleMessage()方法中的代码会在Handler所在的线程中执行。

     Handler handler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
             // 根据msg.what来区分消息类型
             switch (msg.what) {
                 case 1:
                     // 假设消息携带了一个字符串对象
                     String message = (String) msg.obj;
                     // 处理消息,例如更新UI
                     textView.setText(message);
                     break;
                 // 可以添加更多的case来处理不同类型的消息
                 default:
                     // 可以在这里处理未知消息或者记录日志
                     break;
             }
         }
     };
         `what` 字段只是一个整数,它本身不包含任何具体的意义。它的意义完全取决于你的应用逻辑和如何使用它。在设计消息处理逻辑时,你应该定义一个清晰的命名规范或文档,以确保 `what` 值的使用是一致和可维护的。

    例如,在一个聊天应用中,你可能会有如下的 what 值定义:

    • WHAT_MESSAGE_RECEIVED = 1:表示接收到新消息。

    • WHAT_USER_CONNECTED = 2:表示用户已连接到服务器。

    • WHAT_USER_DISCONNECTED = 3:表示用户已从服务器断开连接。

    ​ 在你的 handleMessage() 方法中,你会根据这些 what 值来执行相应的逻辑:

     @Override
     public void handleMessage(Message msg) {
      switch (msg.what) {
          case WHAT_MESSAGE_RECEIVED:
              // 处理接收到的消息
              break;
          case WHAT_USER_CONNECTED:
              // 处理用户连接事件
              break;
          case WHAT_USER_DISCONNECTED:
              // 处理用户断开连接事件
              break;
          default:
              // 处理未知消息
              break;
      }
     }

    记住,what 字段只是一个标识符,它的值应该根据你的应用逻辑来定义。

ThreadLocal

使用 ThreadLocal 可以为每个线程提供一个独立的 Handler 实例,这样每个线程都可以安全地发送和处理消息,而不会影响到其他线程的消息队列。

 // 创建了一个 ThreadLocal 类型的变量,它用来存储 Handler 对象。
 private static final ThreadLocal<Handler> threadLocalHandler = new ThreadLocal<Handler>() {
     @Override
     // 在 ThreadLocal 对象第一次被访问时调用,用来提供一个初始值。返回一个新的 Handler 实例,这个Handler实例与主线程的Looper关联。这意味着Handler将会在主线程的消息队列中处理消息。
     protected Handler initialValue() {
         // 这里使用主线程的 Looper 来创建 Handler
         return new Handler(Looper.getMainLooper());
     }
 };
 ​
 public void sendMessageToCurrentThread(Message message) {
     // 将一个 Message 对象发送到与 Handler 关联的消息队列中。由于 Handler 与主线程的 Looper 关联,这个消息将在主线程中处理。
     threadLocalHandler.get().sendMessage(message);
 }

注意:ThreadLocal 可能会引起内存泄漏,如果线程长时间运行或者线程的生命周期结束但 ThreadLocal 变量没有被清理。确保在不再需要时调用 threadLocalHandler.remove() 来清除线程局部变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值