当前位置: 首页 > article >正文

消息传递机制之Handler使用总结

一.Handler基础知识

       Handler消息处理机制是一个功能强大的数据传递机制,主要功能是用来把子线程的数据传递给主线程,让主线程进行UI操作。
       android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与Message Queue打交道,因此我没将其作为核心类。下面一一介绍:

(一)线程的魔法师 Looper

       Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓 Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

public class LooperThread extends Thread { @Override 3.         
public void run() { // 将当前线程初始化为Looper线程 Looper.prepare();// ...其他处理,如实例化handler 
// 开始循环处理消息队列 Looper.loop(); } 
}
  • 1

       通过上面两行核心代码,你的线程就升级为Looper线程了!
       在主线程中,上面这两个Looper的方法,系统已经帮我们做好了,直接实例化Handler对象就可以使用了,但是在子线程中,需要我们自己写这两个方法来升级为Looper线程。

注意: Looper.loop()之后的方法不会再执行到

1.Looper.prepare()

Looper预处理操作,效果如下图所示:

通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。

注意,一个Thread只能有一个Looper对象。

Looper.prepare() 方法的简单代码:

public static final void prepare() {if (sThreadLocal.get() != null) { // 试图在有Looper的线程中再次创建Looper将抛出异常 throw new RuntimeException("Only one Looper may be created per thread");} sThreadLocal.set(new Looper()); }}
  • 1

这个方法确保一个线程只用一个Looper对象。

2. Looper.loop() 保持循环接收信息的方法

任务执行的示意图:

调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也 叫任务)执行。

3.除了prepare()和loop()方法,Looper类还提供了一些有用的方法,

(1)Looper.myLooper()得到当前线程looper对象:
public static final Looper myLooper()
(2)getThread()得到looper对象所属线程:
public Thread getThread()
(3)quit()方法结束looper循环:
public void quit()

4.Looper总结

       每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行 Looper使一个线程变成Looper线程。

(二)异步处理大师 Handler

       什么是Handler?
       Handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务 (handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法 将关联当前线程的looper,不过这也是可以set的。
       下面是Handler消息处理的示意图:

可以看到,一个线程可以有多个Handler,但是只能有一个Looper!

有了Handler对象之后,我们就可以使用下面一些方法来发送消息

1.post(Runnable)
2.postAtTime(Runnable,long)
3.postDelayed(Runnable,long)
4.sendEmptyMessage(int)
5.sendMessage(Message)
6.sendMessageAtTime(Message,long)
7.sendMessageDelayed(Message,long)
       这些方法就可以向 MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对 象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了。使用post(Runnable)方法后,子线程被系统调用启动线程,不需要我们去start。

(三)Message类

       Message是线程之间传递信息的载体,包含了对消息的描述和任意的数据对象。Message被 存放在MessageQueue中,一个MessageQueue中可以包含多个Message对象,Message中 包含了两个额外的 int字段和一个object字段,这样在大部分情况下,使用者就不需要再做内 存分配工作了。
       虽然Message的构造函数是public的,但是最好是使用Message.obtain()或Handler.obtainMessage()函数来获取Message对象,因为 Message的实现中包含了回收再利用的机制,可以提供效率。

1. arg1–int 用来存放整型数据

2. arg2–int 用来存放整型数据

3. obj–Object 用来存放发送给接收器的Object类型的任意对象

4. replayTo–Messenger 用来指定此Message发送到何处的可选Messager对象

5. what–int 用于指定用户自定义的消息代码,这样接收者可以了解这个消息的信息

注意:使用Message类的属性可以携带int类型数据,如果要携带其它类型的数据,可以先将要携带的数据保存到Bundle对象中。然后通过Message类对象的setDate()方法将其添加到Message中。
如果一个Message只需要携带简单的int型信息,应优先使 用Message.argl和Message.arg2属性来传递信息,这比用Bundle更省内存。 尽可能使用Message.what来标识信息,以便用不同方式处理Message。

下面通过两个示例来进一步介绍Handler的使用方法。

二.使用Handler的post方法来调动线程的示例

本示例并没有很复杂的资源下载代码,使用的是倒入程序的图片,主要是通过post(Runnable run)方法实现ImageView的图片资源不断替换。

(一)布局文件代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.lwz.handlerRunnable.MainActivity"><ImageViewandroid:id="@+id/main_iv"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitXY"android:src="@mipmap/a1" /></RelativeLayout>
  • 1

(二)java代码

package com.lwz.handlerRunnable;import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;/**** 实现图片轮播的效果*/
public class MainActivity extends AppCompatActivity {//定义布局内的组件ImageView imageView;//当前的显示的图片的游标值int current = 0;//图片资源int[] images = {R.mipmap.a1, R.mipmap.a2, R.mipmap.a3, R.mipmap.a4, R.mipmap.a5,R.mipmap.a6, R.mipmap.a7, R.mipmap.a8, R.mipmap.a9, R.mipmap.a10};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);imageView = (ImageView) findViewById(R.id.main_iv);//一秒后开始线程handler.postDelayed(runnable, 1000);}Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {//接收到子线程中的数据what,并更换图片资源值imageView.setImageResource(images[msg.what % images.length]);//使用handler对象开始线程方法handler.post(runnable);}};Runnable runnable = new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);//休眠一秒} catch (InterruptedException e) {e.printStackTrace();}current++;//给Handler对象发送数据handler.sendEmptyMessage(current);}};}
  • 1

       上面程序使用的是取巧的方法让线程一直在执行。实际上线程只运行一次,但是结束后又开启了一条线程!
循环运行的思路:
1.让主程序的handler对象开启子线程
2.在子线程中发送handler消息
3.主线程的handler对象接收到方法后,改变UI界面
4.接下来一直循环上面的3个步骤。从而实现ImageView图片资源的一直变化的显示结果。
这里要知道的是第一次启动子线程是在onCreate方法里面,以后开始子线程都是在handlerMessage方法里面开启线程。
上面程序只是介绍post()方法的使用,但是一般不会用来一直循环线程,会造成关闭程序出现各种问题出现!

三.Handler的一个应用示例

程序设计:
用户只要在输入框输入关键字,会马上有相关的文本提示供选择。
分析:
如果只是使用文件变化的监听器,文本一直输入,提示框会不断的变化。这样设计如果在网络中会比较消耗资源。
本程序中使用的方法是,文本变化后隔0.5钟后再搜索相关内容,并显示提示文本。如果是在0.5秒内,文本一直在变化,这里要取消上次搜索的任务。

(一)布局文件设计

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.example.administrator.lesson16_delaysearch.MainActivity"><EditTextandroid:id="@+id/et"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="输入要搜索的java关键字"android:padding="10dp" /><ListViewandroid:id="@+id/lv"android:layout_width="match_parent"android:layout_height="match_parent" />
</LinearLayout>
  • 1

(二)java代码设计

package com.example.administrator.lesson16_delaysearch;import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;import java.util.ArrayList;
import java.util.List;public class MainActivity extends AppCompatActivity implements TextWatcher {public static final String[] search ={"abstract 表明类或者成员方法具有抽象属性","assert 用来进行程序调试","boolean 基本数据类型之一,布尔类型","break 提前跳出一个kua","byte 基本数据类型之一,字节类型","case 用在switch语句之中,表示其中的一个分支","catch 用在异常处理中,用来捕捉异常","char 基本数据类型之一,字符类型","class 类","const 保留关键字,没有具体含义","continue 回到一个块的开始处","default 默认,例如,用在switch语句中,表明一个默认的分支","do 用在do-while循环结构中","double 基本数据类型之一,双精度浮点数类型","else 用在条件语句中,表明当条件不成立时的分支","enum 枚举","extends 表明一个类型是另一个类型的子类型,这里常见的类型有类和接口","final 用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变","finally 用于处理异常情况,用来声明一个基本肯定会被执行到的语句块","float 基本数据类型之一,单精度浮点数类型","for 一种循环结构的引导词","goto 保留关键字,没有具体含义","if 条件语句的引导词","implements 表明一个类实现了给定的接口","import 表明要访问指定的类或包","instanceof 用来测试一个对象是否是指定类型的实例对象","int 基本数据类型之一,整数类型","interface 接口","long 基本数据类型之一,长整数类型","native 用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的","new 用来创建新实例对象","null 用来标识一个不确定的对象","package 包","private 一种访问控制方式:私用模式","protected 一种访问控制方式:保护模式","public 一种访问控制方式:共用模式","return 从成员方法中返回数据","short 基本数据类型之一,短整数类型","static 表明具有静态属性","strictfp 用来声明FP_strict(单精度或双精度浮点数)表达式遵循IEEE754算术规范","super 表明当前对象的父类型的引用或者父类型的构造方法","switch 分支语句结构的引导词","synchronized 表明一段代码需要同步执行","this 指向当前实例对象的引用","throw 抛出一个异常","throws 声明在当前定义的成员方法中所有需要抛出的异常","transient 声明不用序列化的成员域","try 尝试一个可能抛出异常的程序块","void 声明当前成员方法没有返回值","volatile表明两个或者多个变量必须同步地发生变化","while 用在循环结构中",};ListView lv;EditText et;List<String> mList = new ArrayList<>();ArrayAdapter<String> adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);lv = (ListView) findViewById(R.id.lv);et = (EditText) findViewById(R.id.et);adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mList);lv.setAdapter(adapter);et.addTextChangedListener(this);}@Overridepublic void beforeTextChanged(CharSequence s, int start, int count, int after) {}@Overridepublic void onTextChanged(CharSequence s, int start, int before, int count) {//只要文本改变,去匹配// 延迟搜索// 输入完毕后,1秒之后,在去匹配//只要输入,就会执行该方法//我们需要取消前一次搜索,重新开始延迟搜索;handler.removeCallbacks(run);handler.postDelayed(run, 1000);}Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);}};private Runnable run = new Runnable() {@Overridepublic void run() {mList.clear();String s = et.getText().toString();if (s.length() > 0) {//匹配字符串for (String s1 : search) {if (s1.contains(s)) {//classmList.add(s1.toString());}}}adapter.notifyDataSetChanged();}};@Overridepublic void afterTextChanged(Editable s) {}
}
  • 1

程序运行后的显示结果:

输入ab这两字符时,显示界面:

上面就是Handler的简单应用。

http://www.lryc.cn/news/2417677.html

相关文章:

  • 一分钟快速过安卓四大组件——Activity篇
  • 2024年最新GIMP(Linux下的Photoshop)-KOS安装教程_linux photoshop
  • 19 张图概览 Spring Cloud(收藏夹吃亏系列)
  • linux后台运行nohup | 进程查看、终止 | linux基础命令记录
  • Mybatis-Plus理解及使用
  • 前端进阶之路——域名(domain)
  • win11下配置visual studio 2022+PCL1.13.0
  • 50个常用的 Numpy 函数详解!
  • kali简介
  • Java | final关键字快速上手【通俗易懂,看这篇就够了】
  • UUID介绍与生成方法
  • fps游戏战斗相关漫谈(五)
  • 安卓工程师必须了解的Gradle知识
  • ASCII码对照表(包括十六进制、十进制和字符)
  • 如何使用Chat GPT
  • 适合小白入门!Sqlite数据库学习(附操作过程截图)
  • 大白话讲vuex
  • 随记——ELK部署
  • Linux whois命令教程:查询域名所有者信息(附案例详解和注意事项)
  • SqlServer数据库安装及使用(第一篇)
  • Arduino入门
  • (1-4)TensorFlow深度学习基础:TensorFlow开发流程
  • 【实践篇】手把手教你落地DDD
  • Swiper的安装及使用
  • JS中的Promise(秒懂如何使用promise进行异步操作)
  • SSH远程链接
  • JavaScript的简介及基本语法
  • jQuery(一)jQuery基本语法
  • Spark的概念、特点、应用场景
  • RabbitMQ介绍及部署(超详细讲解)