香港高速VIP云机房火爆上线啦!无需备案,开通即用!配套《巅云自助建站系统3.0》将带给您飞一般的可视化拖拽建站体验,欢迎免费体验。

建站专题

Handler与异步消息处理

一佰互联网站开发设计(www.yinxi.net) 发布日期 2019-05-26 10:11:13 浏览数: 17

Handler 在 Android 中的应用很广泛,基本上每个 Android 开发人员都会使用到它。本篇文章将会介绍 Handler 和异步消息机制相关的使用方法,下一篇会从源码的角度分析 Android 中异步消息处理的流程。

Android 中的异步消息处理框架由 Handler 、MessageQueue、Looper 和 ThreadLocal 等组成。Handler 是我们使用最多的一个类,主要负责发送和处理消息,MessageQueue 是一个队列,用来存储 Handler 发送来的消息,而 Looper 则是一个死循环。

Handler 的使用场景

由于 Android 系统不允许在主线程进行耗时任务,因此网络请求等一般都会开新的线程执行,然而,Android 中的控件不是线程安全的,因此只能在主线程中访问 UI 控件,否则就会报错。那么从子线程中得到的数据怎么返回到主线程给 UI 使用呢?答案很简单,大家都知道,可以使用 Handler 将数据返回到主线程。

但有时候你会发现,有些项目里面没有开启子线程,却在使用 Handler,这就是 Handler 的另一个使用场景:消息处理。如果你有一个个的任务需要排队处理,那么使用 Handler 是很合适的。

Handler 的使用

Handler 必须与一个 Looper 关联才能使用,而且是在 Handler 创建的时候就要传入一个 Looper 的。由于主线程中默认创建了一个 Looper,因此在主线程中不用传入 Looper。如果不传 Handler,那么系统就会去获取当前线程的 Looper,如果找不到,就会报错。因此,如果在子线程没有手动创建 Looper 的情况下直接使用 Handler handler = new Handler() 的话,是会报错的。

// 不传入 Looper,系统会自己去获取当前线程中的 Looper。 Handler handler = new Handler();

// 在子线程中,可以传入自定的 Looper。 Handler handler = new Handler(Looper);

每一个线程有且只能有一个 Looper,在创建 Looper 的时候,系统会检查该线程是否已经有 Looper 对象了,已经有 Looper 对象了,则会报错。

// 创建一个 Looper 很简单。 Looper.prepare();

再次强调: Handler 必须和 Looper 相关联才能使用 。一个 Handler 只能关联一个 Looper,而且一旦关联上了,就不能更改。当然,一个 Looper 可以关联多个 Handler。

在子线程中创建 Handler

一般情况下,我们都是在主线程中创建 Handler,但是有时候也需要在子线程中处理消息队列。由于主线程是唯一一个自带了 Looper 的线程。因此在主线程中创建和使用 Handler 相对是比较简单的,也是最常见的。但是还有些情况是需要在子线程中创建一个消息队列的,在子线程中使用 Handler 需要提供一个 Looper,于是代码就应该像下面那样:

// 为异步消息处理框架准备一个线程 class LooperThread extends Thread {
	public Handler mHandler;
	public void run() { // 准备一个 Looper Looper.prepare(); // Handler 对象会和该线程的 Looper 关联起来。 mHandler = new Handler() {
			public void handlerMessage(Message msg) { // 在这里处理传入的消息 }
		}; // 使用该 Looper,启动一个循环的消息队列。 Looper.loop();
	}
}

使用 HandlerThread

可以使用 HandlerThread 来简化在子线程中创建 Handler 的流程。HalderThread 是一个自带了 Looper 的线程类,

public class MyHandlerThread extends HandlerThread { // 你只需要添加一个 Handler private Handler handler;
	public MyHandlerThread(String name) { super(name)
	}
}

HandlerThread 也并不神秘,它只是帮你调用了 Looper.prepare() 方法和 Looper.loop() 方法而已。也就是说如果你一个类继承了 ThreadHandler,你可以像在主线程那样使用 Handler。

发送和处理消息

准备好 Handler 的环境后,就可以使用 Handler 来发送和处理消息了。处理消息是在 Handler 的 handleMessage() 方法中进行 的,而发送消息有两种方法:发送一个 Message 对象,和投递一个 Runnable 对象。下面分别介绍这两类方法。

发送 Message 对象

Message 对象可以包含一些简单的数据,并且可以通过 Handler 进行发送和处理。Message 对象可以通过 Message.what(int) 方法中的 int 参数来标志该对象。Message 对象使用两个 int 类型的字段来存储需要发送的信息,也可以使用一个 Object 类型的对象来存储一个简单对象的信息。

  • Message.what :标识一个 Message 对象。
  • Message.arg1 :需要传递的 int 类型的数据。
  • Message.arg2 :需要传递的 int 类型的数据。
  • Message.obj :存储任意数据类型(Object)的对象。

怎么创建一个 Message 对象呢?你当然可以使用 Message msg = new Message()方法来创建一个 Message 对象,但是不建议这么做。因为我们有更高效的方法来获得 Message 对象:使用 Message msg = MMessage.obtain() Handler.obtainMessage() 来获取一个 Message。这个 Message 对象被 Handler 发送到 MessageQueue 之后,并不会被销毁,可以重复利用,因此比使用 new 方法来创建一个 Message 对象效率更高。

当你需要将消息传递到一个后台线程时,建议使用 Handler.obtainMessage 来创建一个 Message 对象:

int what = 0; String hello = "Hello!"; // 获取一个和后台线程关联的 Message 对象 Message msg = mHandler.obtainMessage(what, hello); // 发送一个消息到后台线程。 mHandler.sendMessage(msg);

sendMessage() 的相关方法

发送 Message 对象的方法同投递一个 Runnable 对象的方法很类似:

  • Handler.sendMessage( Message msg ) :在 MessageQueue 中添加一个 Message 对象。
  • Handler.sendMessageAtFrontOfQueue( Message msg ) :添加一个 Message 对象到 MessageQueue 的前面。
  • Handler.sendMessageAtTime ( Message msg, long timeInMills ) :在指定的时间发送一个 Message 对象。
  • Handler.sendMessageDelayed( Message msg, long timeInMillis ) :在指定的时间之后,发送 Message 对象。

投递一个 Runnable 对象

使用 Handler 发送任务的另一个方法就是投递一个 Runnable 对象(“投递”一词,主要是翻译 post() 方法)。创建一个 Runnable 对象必须要实现 run() 方法,因此,我们需要投递执行的任务就要写在 run() 方法中。

// 声明一个 Runnable Runnable r = new Runnable() { @Override public void run() { // 任务的具体内容 }
}

在 Handler 中,有多种方式投递一个 Runnable 对象:

  • Handler.post(Runnable r) :在 MessageQueue 中添加一个 Runnable 对象。
  • Handler.postAtFrontOfQueue :在 MessageQueue 的头部添加一个 Runnable 对象。
  • Handler.postAtTime(Runnable r, long timeMillis) :在指定的时间将 Runnable 对象添加到 MessageQueue 中。
  • Handler.postDelay(Runnable r, long delay) :经过了指定的时间后,将 Runnable 对象添加到 MessageQueue 中。
// 投递一个 Runnable 对象 Handler handler = new Handler();
handler.post( new Runnable() {
		@Override public void run() {
			// 任务的具体内容
		}
	});

当然,你也可以使用 Activity.runOnUiThread() 来投递一个 Runnable 对象。

Activity.runOnUiThread (
	new Runnable() { @Override public void run() { // 任务的具体内容 }
	});

该方法如果是在主线程上调用的,那么会立即执行。如果是在其他线程上使用的,那么就会将该 Runnable 对象投递到主线程的消息队列中去。

有一点需要记住, Runnable 对象和 Message 对象不同, Runnable 对象不能重复使用。

实例:倒计时

下面,我们用 Handler 来实现倒计时功能。先来分析一下 倒计时 的相关功能需求:

  • 需要知道总时间和每一步的间隔时间。
  • 每次到了间隔时间就回调通知,总时间结束后也要回调通知。

根据这个需求,我们可以大致猜想一下如何实现:

  • 需要提供两个构造参数的构造方法,一个是总时间,一个是间隔时间。
  • 需要提供两个抽象方法供子类实现,一个是每次到了间隔时间的回调通知,另一个是总时间结束后的回调通知。

有了这个思路,实现就很简单了,下面直接给出代码:

public abstract class Timer { private long mMillisInFuture; private long mCountdownInterval; private long mStopTimeInFuture; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // 计算剩余时间 long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime(); if (millisLeft <= 0) { // 如果剩余时间小于或等于 0, 则回调 onFinish() 方法。 onFinish();
            } else if (millisLeft < mCountdownInterval) { // 当剩余时间小于一次间隔时间时,不回调 onTick() 方法,直接等到最后再发送 sendMessageDelayed(obtainMessage(), millisLeft);
            } else { // 回调 onTick() 方法,并在一次间隔时间后再次发送消息。 onTick(millisLeft);
                sendMessageDelayed(obtainMessage(), mCountdownInterval);
            }
        }
    }; public Timer(long millisInFuture, long countdownInterval) {
        mMillisInFuture = millisInFuture;
        mCountdownInterval = countdownInterval;
    } /**
     * 调用 start() 方法以启动倒计时
     * @return */ public Timer start() { if (mMillisInFuture < 0) {
            onFinish(); return this;
        } // 获取倒计时完成的系统时间 mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture; // 发送第一个消息 mHandler.sendMessage(mHandler.obtainMessage()); return this;
    } /**
     * 每经过一次间隔时间就回调一次 onTick 方法
     * @param millionLeft   剩余时间
     */ abstract void onTick(long millionLeft); /**
     * 倒计时完成后,回调 onFinish() 方法
     */ abstract void onFinish();
}

其实上面的代码是 Android 系统中 CountDownTimer 类的简化版, CountDownTimer增加了线程安全机制并处理了一些特殊情况,有兴趣的朋友可以自行查阅。

一佰互联是全国知名建站品牌服务商,我们有九年网站建设、网站制作、网页设计、php开发和域名注册及虚拟主机服务经验,提供的自助建站服务更是全国有名。近年来还整合团队优势自主开发了可视化多用户”巅云建站系统“3.0平台版,拖拽排版网站制作设计,轻松实现pc站、手机微网站、小程序、APP一体化全网营销网站建设 ,已成功的为全国上百家网络公司提供自助建站平台搭建服务。

相关新闻more

05
04月
做网站站群网站的目的

很多企业初涉互联网,刚刚建立自己最基础的官方网站,有的还仅仅是企业产品的展销平台,但随着互联网的发展和其对企业销售影响越来越大,企业需要在互... >>详情

23
04月
docker中修改镜像容器的存放目录的方法

最近在学习docker的路上,今天遇到了个问题,在网上查找了一下资料,顺便留个笔记在默认情况下,Docker镜像和容器的默认存放位置为: /... >>详情

28
04月
php查询ip所在地的方法

本文实例讲述了php查询ip所在地的方法。分享给大家供大家参考。具体实现方法如下:复制代码 代码如下:<?php /** *@... >>详情

15
04月
专业网站制作,一个好的网站有哪些原则呢?

专业网站制作,一个好的网站有哪些原则呢?先讲一下设计,设计是什么,设是目标,计是方法是策略,我们设计的目标是什么,目的又是什么,解决方法解决... >>详情

营业执照. cdn加速服务 备案系统认证 网络安全协会 我们的支付方式AAA认证
上海 北京 深圳 广州 天津 杭州 南京 武汉 成都 沈阳 大连 长沙 济南 青岛 苏州 福州 无锡 哈尔滨 宁波 重庆 大庆 厦门 西安 长春 珠海 郑州 海口 昆明 太原 石家庄 温州 合肥 乌鲁木齐 南宁 南通 合肥 兰州 呼和浩特 贵阳 烟台 秦皇岛 包头 唐山 银川 汕头 连云港 威海 西宁 湛江 北海 万州 涪陵 长寿 黔江 永川 丰都 忠县 江津 南川 开县 云阳 万盛 梁平 垫江 巫山 城口 建站宝盒 免费建站 门户网站建设 微信网站 手机网站 门户网站制作

7x24小时服务电话:18581389571 传真:023-85725751 免费建站交流群:236412099 139947842(自助建站交流) E-Mail:post@yinxi.net 网站投诉:
重庆楚捷科技有限公司 一佰互联©版权所有 自助建站(www.yinxi.net,Inc.) 2001-2020 All Rights Reserved 本站程序受法律保护,网站法律顾问:ITLAW-庄毅雄律师
中华人民共和国信息产业部网站备案号:渝ICP备12000592号