Android Handler消息传递机制
Android中只允许UI线程(也就是主线程)修改Activity里的UI组件。实际开发中,新启动的线程需要周期性地改变界面组件的属性值就需要借助Handler的消息传递机制。
Handler类
Handler类的主要作用:
- 在新启动的线程中发送消息
- 在主线程中获取、处理消息
Handler类包含如下方法用于发送、处理消息。
- handleMessage(Message msg):处理消息的方法。该方法通常用于被重写。
- hasMessages(int what):检查消息队列中是否包含what属性为指定值的消息。
- hasMessages(int what,Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
- 多个重载的 Message obtainMessage():获取消息。
- sendEmptyMessage(int what):发送空消息。
- sendEmptyMessageDelayed(int what,long delayMillis):指定多少毫秒之后发送空消
- sendMessage(Message msg):立即发送消息。
- 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对象,可以分如下两种情况处理。
- 在主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息了。
- 程序员自己启动的子线程,必须自己创建一个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的步骤如下:
- 调用Looper的 prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。
- 有了Looper之后,创建 Handler子类的实例,重写 handleMessage(方法,该方法负责处理来自其他线程的消息。
- 调用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消息传递机制相关推荐
- 【Android开发】线程与消息处理-Handler消息传递机制之Looper
在前面已经介绍了在Android中如何创建.开启.休眠和中断线程.不过,此时并没有在新创建的子线程中对UI界面上的内容进行操作,如果应用前面介绍的方法对UI界面进行操作,将抛出异常. 为此,Andro ...
- android handler 传递对象,Android之Handler消息传递机制详解
前言 在Android开发中,多线程应用是非常频繁的,其中Handler机制随处可见. 下面就本人对Handle的一些理解与大家一起分享,共同回顾下Handle异步消息传递机制. 1.Handler是 ...
- Android进阶知识树——Android Handler消息机制
1.概述 在安卓程序启动时,会默认在主线程中 运行程序,那如果执行一些耗时的操作则UI就会处于阻塞状态,出现界面卡顿的现象,再者用户的多种操作,系统是如何做到一一处理的,系统又是如何管理这些任务的,答 ...
- Android Handler处理机制 ( 三 ) ——Handler,Message,Looper,MessageQueue
在android中提供了一种异步回调机制Handler,使用它,我们可以在完成一个很长时间的任务后做出相应的通知 handler基本使用: 在主线程中,使用handler很简单,new一个Handle ...
- Android Handler消息机制源码分析
一,前言 众多周知, Android 只允许在主线程中更新UI,因此主线程也称为UI线程(ActivityThread). 如此设计原因有二: (1) 由于UI操作的方法都不是线程安全的,如果多个线程 ...
- Android Handler消息机制不完全解析
1.Handler的作用 Android开发中,我们经常使用Handler进行页面的更新.例如我们需要在一个下载任务完成后,去更新我们的UI效果,因为AndroidUI操作不是线程安全的,也就意味着我 ...
- android handler的机制和原理_Android完整知识体系路线(菜鸟-资深-大牛必进之路)
前言 移动研发火热不停,越来越多人开始学习Android 开发.但很多人感觉入门容易成长很难,对未来比较迷茫,不知道自己技能该怎么提升,到达下一阶段需要补充哪些内容.市面上也多是谈论知识图谱,缺少体系 ...
- Android Handler消息机制源码解析
好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础.下面我们分析一下Handler的源码实现. Handler消息机制有4个 ...
- 【安卓学习笔记】Android Handler 消息机制探究
一.概述 1.android消息机制的含义: Android消息机制,其实指的就是 Handler 的运行机制,而 Handler 要正常运作,又需要底层的 MessageQueue , Looper ...
最新文章
- Ubuntu 常用操作
- PicGo 配置Gitee 图床
- leetcode 22. 括号生成
- CHUNGHOP k-6868万能空调遥控器的自己家用电器的代码+自己家里的宽带账号+机顶盒型号+桌子+椅子+垫子高度
- linux编码 form表单,Linux curl 模拟form表单提交信息和文件
- miniob :相关环境配置
- [MS bug]安装SQL Server 2008 错误:is not a valid login or you do not have permission
- JavaScript中数组去重汇总
- reduceByKey与GroupByKey,为什么尽量少用GroupByKey
- jmeter中build和jmeter-results-detail-report_30.xsl以及jmeter.results.shanhe.me.xsl
- mina框架详解-小白收藏
- android加载dex方法,android Dex文件的加载
- c++创建一个linux deamon进程
- 如何在 Excel 中使用 SUMIF 函数?
- react 脚手架创建后暴漏配置文件 运行yarn eject 报错 (已解决)
- 一款免费的Veracrypt加密软件---U盘加密功能
- Charles抓包的使用步骤
- IFS认证|国际食品IFS认证优势与审核标准
- 两台Exadata搭建RAC+DG
- c语言版五指棋,linux终端运行