Android中只允许UI线程(也就是主线程)修改Activity里的UI组件。实际开发中,新启动的线程需要周期性地改变界面组件的属性值就需要借助Handler的消息传递机制。

Handler类

Handler类的主要作用:

  • 在新启动的线程中发送消息
  • 在主线程中获取、处理消息

Handler类包含如下方法用于发送、处理消息。

  1. handleMessage(Message msg):处理消息的方法。该方法通常用于被重写。
  2. hasMessages(int what):检查消息队列中是否包含what属性为指定值的消息。
  3. hasMessages(int what,Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
  4. 多个重载的 Message obtainMessage():获取消息。
  5. sendEmptyMessage(int what):发送空消息。
  6. sendEmptyMessageDelayed(int what,long delayMillis):指定多少毫秒之后发送空消
  7. sendMessage(Message msg):立即发送消息。
  8. sendMessageDelayed(Message msg,long delayMillis):指定多少毫秒之后发送消息。

借助于上面这些方法,程序可以方便地利用Handler来进行消息传递。
关于Handler的源码解读,可参考别人写的《Android 多线程之 Handler 源码分析》

实例:自动轮播图片

本实例通过一个新线程来周期性的修改ImageView所显示的图片(因为不允许其他线程访问Activity的界面组件,故在程序中发送消息通知系统更新ImageView组件,故不需要实例Looper),布局文件非常简单,故直接给程序代码:

package com.example.testapp1.activity;import android.os.Bundle;
import android.os.Handler;
import android.os.Message;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;import com.example.testapp1.R;
import com.example.testapp1.control.RoundImageView;import java.lang.ref.WeakReference;
import java.util.Timer;
import java.util.TimerTask;public class NextActivity extends AppCompatActivity {private RoundImageView imageShow;static class ImageHandler extends Handler {private WeakReference<NextActivity> nextActivityWeakReference;public ImageHandler(WeakReference<NextActivity> nextActivityWeakReference) {this.nextActivityWeakReference = nextActivityWeakReference;}private int[] imageIds = new int[]{R.drawable.a383f7735d8cd09fb81ff979b2f3d599, R.drawable.b6ab4abe4db592b27ea678345b0c3416, R.mipmap.head1, R.drawable.b6ab4abe4db592b27ea678345b0c3416};private int currentImageId = 0;@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if (msg.what == 0x1233) {nextActivityWeakReference.get().imageShow.setImageResource(imageIds[currentImageId++ % imageIds.length]);}}}ImageHandler imageHandler = new ImageHandler(new WeakReference<>(this));@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.next);imageShow = findViewById(R.id.headImg);new Timer().schedule(new TimerTask() {@Overridepublic void run() {imageHandler.sendEmptyMessage(0x1233);}}, 0, 2000);}
}

上述代码中,TimeTask对象的本质就是启动一条新线程。

Handler、Loop、MessageQueue的工作原理

  • Message: Handler接收和处理的消息对象。
  • Looper:每个线程只能拥有一个Looper。它的loop方法负责读取 MessageQueue中的消息,读到信息之后就把消息交给发送该消息的Handler进行处理。
  • MessageQueue:消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时,会在它的构造器中创建MessageQueue对象。Looper的构造器源代码如下:
private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}

该构造器使用了private修饰,表明程序员无法通过构造器创建Looper对象。从上面的代码不难看出,程序在初始化Looper时会创建一个与之关联的 MessagQueue,这个MessageOuee就负责管理消息。

  • Handler:它的作用有两个,即发送消息和处理消息,程序使用Handler发送消息,由Handler发送的消息必须被送到指定的MessageQueue。也就是说,如果希望Handler正常工作,必须在当前线程中有一个MessageQueue;否则消息就没有 MessageQueue进行保存了。不过MessageQueue是由Looper负责管理的,也就是说,如果希望Handler正常工作,必须在当前线程中有一个Looper对象。为了保证当前线程中有Looper对象,可以分如下两种情况处理。
  1. 在主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息了。
  2. 程序员自己启动的子线程,必须自己创建一个Looper对象,并启动它。创建 Looper对象调用它的prepare(方法即可。
    prepare()方法保证每个线程最多只有一个Looper对象。prepare()方法的源代码如下:
    public static void prepare() {prepare(true);}private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));}

接下来调用Looper的静态loop()方法来启动它。loop()方法使用一个死循环不断取出MessageQueue中的消息,并将取出的消息分给该消息对应的Handler进行处理。下面是Looper类的loop()方法的源代码:

   /*** Run the message queue in this thread. Be sure to call* {@link #quit()} to end the loop.*/public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;final MessageQueue queue = me.mQueue;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// Allow overriding a threshold with a system prop. e.g.// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride =SystemProperties.getInt("log.looper."+ Process.myUid() + "."+ Thread.currentThread().getName()+ ".slow", 0);boolean slowDeliveryDetected = false;for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " " +msg.callback + ": " + msg.what);}// Make sure the observer won't change while processing a transaction.final Observer observer = sObserver;final long traceTag = me.mTraceTag;long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);final boolean needStartTime = logSlowDelivery || logSlowDispatch;final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;Object token = null;if (observer != null) {token = observer.messageDispatchStarting();}long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (slowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");slowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.slowDeliveryDetected = true;}}}if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();}}

归纳起来,Looper、MessageQueue、Handler各自的作用如下:

  • Looper:每个线程只有一个Looper,它负责管理MessageQueue,会不断地从MessageQueag中取出消息,并将消息分给对应的Handler处理。
  • MessageQueue:由Looper负责管理。它采用先进先出的方式来管理Message。
  • Handler:它能把消息发送给 Looper管理的MessageQueue,并负责处理 Looper分给它的消息。

在线程中使用Handler的步骤如下:

  1. 调用Looper的 prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。
  2. 有了Looper之后,创建 Handler子类的实例,重写 handleMessage(方法,该方法负责处理来自其他线程的消息。
  3. 调用Looper的loopO方法启动Looper。

实例:使用新线程实现点击图片弹出图片内容

1.布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/constraintlayout2"xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_gravity="center"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/imageView"android:layout_width="50dp"android:layout_height="50dp"tools:ignore="MissingConstraints"tools:src="@drawable/ic_launcher_foreground" /><TextViewandroid:id="@+id/textView3"android:layout_width="100dp"android:layout_height="50dp"android:gravity="center"android:visibility="gone"app:layout_constraintStart_toEndOf="@+id/imageView"app:layout_constraintTop_toTopOf="@+id/constraintlayout2"tools:text="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"tools:visibility="visible" /></androidx.constraintlayout.widget.ConstraintLayout>

布局文件比较简单,就是使用约束布局,在其中放入一个图片控件和文本控件(不展示)。
JAVA代码:

    private ImageThread imageThread;class ImageHandler extends Handler {@Overridepublic void handleMessage(@NonNull Message msg) {super.handleMessage(msg);if(msg.what == 0x123){String imageText = msg.getData().getString("ImageText");Toast.makeText(mContext, imageText, Toast.LENGTH_LONG).show();}}}class ImageThread extends Thread {private Handler mHandler;@Overridepublic void run() {Looper.prepare();mHandler = new ImageHandler();Looper.loop();}}

上述代码定义了一个线程的子类和Handler的子类,在Android Studio的比较新的版本不能直接使用Handler类实例对象并重新handleMessage(已废弃,旧版本可以),必须通过Handler子类实例对象
在Activity的onCreate()或者Fragment的onCreateView()方法中加入以下代码:启动新线程,监听图片的点击事件,向新线程中的Handler发送消息。

imageView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {Message msg = new Message();msg.what = 0x123;Bundle bundle = new Bundle();bundle.putString("ImageText", imageData.getImageText());msg.setData(bundle);imageThread.mHandler.sendMessage(msg);}});imageThread = new ImageThread();imageThread.start();

Android Handler消息传递机制相关推荐

  1. 【Android开发】线程与消息处理-Handler消息传递机制之Looper

    在前面已经介绍了在Android中如何创建.开启.休眠和中断线程.不过,此时并没有在新创建的子线程中对UI界面上的内容进行操作,如果应用前面介绍的方法对UI界面进行操作,将抛出异常. 为此,Andro ...

  2. android handler 传递对象,Android之Handler消息传递机制详解

    前言 在Android开发中,多线程应用是非常频繁的,其中Handler机制随处可见. 下面就本人对Handle的一些理解与大家一起分享,共同回顾下Handle异步消息传递机制. 1.Handler是 ...

  3. Android进阶知识树——Android Handler消息机制

    1.概述 在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答 ...

  4. Android Handler处理机制 ( 三 ) ——Handler,Message,Looper,MessageQueue

    在android中提供了一种异步回调机制Handler,使用它,我们可以在完成一个很长时间的任务后做出相应的通知 handler基本使用: 在主线程中,使用handler很简单,new一个Handle ...

  5. Android Handler消息机制源码分析

    一,前言 众多周知, Android 只允许在主线程中更新UI,因此主线程也称为UI线程(ActivityThread). 如此设计原因有二: (1) 由于UI操作的方法都不是线程安全的,如果多个线程 ...

  6. Android Handler消息机制不完全解析

    1.Handler的作用 Android开发中,我们经常使用Handler进行页面的更新.例如我们需要在一个下载任务完成后,去更新我们的UI效果,因为AndroidUI操作不是线程安全的,也就意味着我 ...

  7. android handler的机制和原理_Android完整知识体系路线(菜鸟-资深-大牛必进之路)

    前言 移动研发火热不停,越来越多人开始学习Android 开发.但很多人感觉入门容易成长很难,对未来比较迷茫,不知道自己技能该怎么提升,到达下一阶段需要补充哪些内容.市面上也多是谈论知识图谱,缺少体系 ...

  8. Android Handler消息机制源码解析

    好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...

  9. 【安卓学习笔记】Android Handler 消息机制探究

    一.概述 1.android消息机制的含义: Android消息机制,其实指的就是 Handler 的运行机制,而 Handler 要正常运作,又需要底层的 MessageQueue , Looper ...

最新文章

  1. Ubuntu 常用操作
  2. PicGo 配置Gitee 图床
  3. leetcode 22. 括号生成
  4. CHUNGHOP k-6868万能空调遥控器的自己家用电器的代码+自己家里的宽带账号+机顶盒型号+桌子+椅子+垫子高度
  5. linux编码 form表单,Linux curl 模拟form表单提交信息和文件
  6. miniob :相关环境配置
  7. [MS bug]安装SQL Server 2008 错误:is not a valid login or you do not have permission
  8. JavaScript中数组去重汇总
  9. reduceByKey与GroupByKey,为什么尽量少用GroupByKey
  10. jmeter中build和jmeter-results-detail-report_30.xsl以及jmeter.results.shanhe.me.xsl
  11. mina框架详解-小白收藏
  12. android加载dex方法,android Dex文件的加载
  13. c++创建一个linux deamon进程
  14. 如何在 Excel 中使用 SUMIF 函数?
  15. react 脚手架创建后暴漏配置文件 运行yarn eject 报错 (已解决)
  16. 一款免费的Veracrypt加密软件---U盘加密功能
  17. Charles抓包的使用步骤
  18. IFS认证|国际食品IFS认证优势与审核标准
  19. 两台Exadata搭建RAC+DG
  20. c语言版五指棋,linux终端运行

热门文章

  1. Git 进阶之底层相关
  2. 深入Oracle的left join中on和where的区别详解
  3. 《信息物理融合系统(CPS)设计、建模与仿真——基于 Ptolemy II 平台》——3.3 小结...
  4. JMeter 测试计划
  5. 使用Hibernate操作数据库
  6. H.264宣布永远不会对使用者收费
  7. 【新媒体讨论】关联趋势和“就是不服”
  8. 在CentOS7上编译GreenPlum5.3.0
  9. zabbix 安装使用
  10. ue4 材质表达式分类