`
xiaofanqingzjj
  • 浏览: 4250 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Android Handler机制详解

阅读更多
Android Handler机制详解

消息循环

要理解Handler机制就必须先理解什么是消息循环。初学编程一般从C语言开始,C语言程序从main函数开始,执行main函数的第一行代码开始,到main的最后一行代码结束,这时候程序就运行结束了,是一个线性的执行过程。我们从以前的算法,或者数据结构方面的小程序都是通过一个main函数来驱动测试的。这时候的程序比较简单,一般没有消息循环的概念,大部分都是程序运行完了就结束了,程序运行的结果显示在屏幕上。

我们后来学习了图形用户界面,VB,DELPHI或者JAVA Swing,在图形用户界面下,程序启动后,会弹出一个与用户交互的界面,用户不对界面作操作,程序不会作响应而且永远不会退出。我们都知道程序如果是从main开始,一定会执行到main函数的最后一行代码的。但是那是什么让程序可以等待用户操作,而不执行不到main函数的最后一行代码呢?

我们或许能够隐隐约约的感觉到程序执行到某一个地方启动了一个循环,一直在等待用户操作,用户不作操作则程序一直处于循环内部等待用户发送消息,并在循环内部循环处理用户发送的消息,这就是一个典型的消息循环机制。




消息循环也是一个典型的生产者-消费者模式,生产者不停的向消息队列中生产消息,消费中不停的从消息队列中取消息,并处理消息。生产者-消费者模式是一个很典型的多线程协调的例子,在对发送消息或者取消息的时候需要消息队列做同步处理,而且当消费者向队列取消息的时候,如果没有消息,取消息线程要进入等待状态,并等待被唤醒。当生产者线程想消息队列中发送消息的时候需要唤醒当前正在等待消息队列的线程。

生产者消费者模式最主要的问题就是线程安全和线程协调。

生产者-消费着模式和线程安全

我们可以举生活中的一个例子,桌子上有一个盘子,有一个人专门往盘子里放苹果,有一个人专门从盘子里拿苹果吃,我们称放苹果的人为生产者,我们暂时取代号A,吃苹果的人为消费者,我们取其代号为B。

我们首先看吃苹果例子中的线程安全问题

A往盘子中放苹果,B往盘子中拿苹果,他们各司其责,好像没什么问题,如果A在往盘子中放苹果的过程中,B这时正好也从盘子中拿苹果。如果A正在放苹果的同时,B来拿苹果,这时可能苹果会被掰开,因为可能A的苹果还没放入盘子,B已经从盘子里那苹果了,如果这个场景是程序在运行,就可能出现程序不一致的情况了。
为什么会出现这种情况呢,是因为A和B执行任务的时候出现的了公共资源,实例中的盘子,公共资源的访问没有做任何的控制。这就是所谓的线程安全问题。线程安全问题主要是由于不同任务之间资源共享引起的。比如说例子中的盘子便是两个任务的公共资源。
解决线程安全问题
如何规避这个问题呢?我们可以把盘子变成一个带锁的盒子。盒子的旁边放一个开锁的钥匙。如果A来放苹果,他拿桌子上的钥匙打开盒子,然后带着钥匙进入盒子放苹果。当放好苹果之后再把钥匙放回桌子上。B来拿苹果的时候,首先也要拿桌子上的钥匙打开盒子,如果A正在放苹果,B是拿不到钥匙的,他知道这时候已经有人在用盒子了,所以他在盒子边上等。等钥匙被还回来的时候他才能用钥匙打开盒子,然后在盒子里面拿苹果。这样就有效的解决了上面说的多任务共享资源的问题。一般我们把多个任务中共享资源的那段代码称为临界区。

我们把盘子换成盒子,并且在盒子上加一把带钥匙的锁,成功解决了两个人吃苹果时同时访问盘子的问题,这就是所谓线程安全问题,线程安全问题就是把要使用到公共内存的代码区域称为临界区,所有进入临界区的代码都要进行加锁处理,具体代码级别的例子参看后面章节。但是我们还有个新的问题。

如果B吃苹果的速度比A放苹果的速度要快。这时B拿到钥匙进入盒子的时候,如果发现盒子里没苹果,这时候该怎么办呢?有一点我们是可以肯定,就是B不能在盒子里等,如果B在盒子里等,那么A就永远不能进入盒子放苹果了,那么B也永远吃不到苹果了,因为他拿着盒子的钥匙在盒子里边等别人放苹果。

我们考虑解决办法,B进入盒子之后发现里面没苹果。就出来把钥匙放回桌子,等别人来放苹果。这时候B该做些什么呢?B只能等待,因为B除了吃苹果已经没别的工作了。但是如果B在等待了,又有谁来通知他盒子里已经有苹果了呢?

我们可以在锁上加一个排队器,B进入盒子后,发现里面没苹果,B出来,把钥匙交还然后在锁后边排队等待有人来放苹果。A进入来放如苹果后,通知所有在锁后面排队的人去取苹果。

现在所有的流程就可以连接起来了,A每次来放苹果的时候首先拿盒子边上的钥匙,进入盒子放好苹果后,把钥匙放回原处,并通知所有正在排队的人,现在已经有苹果了。B每次来吃苹果的时候,首先拿到盒子的钥匙,然后进入盒子里拿苹果,拿到苹果后,把钥匙放回盒子边上,如果进盒子拿苹果的时候发现里面有苹果,则出来把钥匙放会盒子边上,并在 钥匙后面排队等候,等待放苹果的人来呼叫。 这种情况甚至可以是有多人同时放苹果和多个人同时吃苹果。这就是一个典型的生产者消费者模式的解决方案。

下面是一个JAVA的生产者¬-消费者的实例:
消息机制也是一个典型的生产者——消费者模式,发送消息的任务我们可以认为是上面放苹果的人A,处理消息的任务可以认为是吃苹果的人B。A每次都向制定的内存地址PUSH一个消息对象,B每次都从指定的内存获取一个消息进行处理。A和B是两个独立运行的线程。

下面开始分析android Handler机制

我们结合android源代码来详细分析android handler的实现原理和机制。Android中与handler机制相关的类有4个:Hanlder、Looper、Message、MessageQueue。

Message(消息对象,上面例子中的苹果)

Message表示handler一次要处理的的消息,Message一般聚合4个属性:what、arg1、arg2、obj。
What:用来标识消息,却分不同的消息参数
Arg1和arg2:附带的2个整型参数
Obj:如果需要附带更多更大的参数就可以放在这里

Message的对象创建非常频繁,为了节省对象的创建时间,这里使用了对象池模式来创建Message的对象。即Message提供了一个obtain()的静态方法来创建Message对象,abtain方法的内部实现的一般原理是:维护了一个固定大小的容器用来存放一些预先创建号的对象,用的时候直接返回一个对象就可以了,如果对象被使用完毕,再放会池子。一般使用recycle方法对不再使用的Message对象放会对象池。

Message对象本省也是一个链表结构。

MessageQueue(消息队列,上面例子中的盒子)

消息队列对象维护了一个线程安全的队列。有两个方法enqueueMessage和next,即向队列存入消息和从队列取出消息的方法。
这个方法可能会在不同的线程中调用,所以就会产生上面讲的吃苹果引起的并发问题(线程安全问题)。这里分析一下代码,看看JAVA是如何具体来实现生产者-消费者模式的。

在分析之前先诠释一个概念即synchronized关键字。在讲这个之前先讲讲锁的概念和条件变量的概念。

在上面吃苹果的的故事中,讲到一个临界区的概念,即一次只能有一个线程可以进入的代码区域,一般该代码区域包含了公共的资源。比如说上面吃苹果和放苹果的操作都是临界区的操作。我们讲解了加锁和加锁排队的实现。用伪代码实现如下:

放苹果:
1.给盒子开锁;(如果有人在吃苹果,是拿不到钥匙的,因为钥匙被人拿着进入盒子了,所以只能等待)
2.拿钥匙进入盒子吃苹果;
3.通知所有等待吃苹果的人可以从新取拿苹果了
4.出来把钥匙放回盒子旁边;

吃苹果:
1.给盒子开锁;(如果有人在放苹果,是拿不到钥匙的,因为钥匙别人拿了)
2.拿钥匙进入盒子吃苹果;
3.如果发现盒子里没苹果;
4.出来把钥匙放会盒子旁边,并在钥匙旁边排队,等待有人进入盒子放苹果。

JAVA代码的实现:

放苹果:
Try{
mLock.lock();
放苹果;
mLock.notifyAll();
} finally {
mLock.unlock();
}
吃苹果:
Try{
mLock.lock();
if(没苹果) {
mLock.wait();
}
吃苹果;
} finally {
mLock.unLock();
}

这就是一个典型的JAVA伪代码实现了吃苹果的场景。但是这和synchronized有什么关系呢?JAVA的所有类内部都有一个锁对象,如果一个方法被synchronized修饰,则可以认为代码如下:
Public synchronized void method() {
方法的实现;
}
等价与:
Public void method() {
Try {
对象内部锁.lock();

方法的实现;
} finally {
对象内部锁.unLock();
}
}
对象内部锁也有一个内部的条件变量,可以直接来调用对象的wait方法和notifyAll方法来使线程进入等待状态和唤醒所有等待的线程。

有了上面的分析,对MessageQueue的方法实现就很容易理解了。我们把放苹果的操作当作是向消息队列放消息,把吃苹果的操作当作消息处理。

下面贴出源代码:
放苹果,即向消息队列中放消息:

public boolean enqueueMessage(Message msg, long when) {
msg.when = when;
// 临界区
synchronized (this) {
Message p = mMessages;
printMessageQueue(p);

// 把消息插入消息队列中,并通知等待线程
if ( p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
this.notify();
} else {
while (p.next != null && p.when <= when) {
p = p.next;
}
msg.next = p.next;
p.next = msg;
this.notify();
}
}
return true;
}

吃苹果:

public Message next() {
while (true) {
long now = 0;
synchronized (this) {
now = SystemClock.uptimeMillis();

Message msg = pullNextLocked(now);
           if (msg != null) {
           return msg;
          }
}

// 临界区
synchronized (this) {
try {
if (mMessages != null) {
if (mMessages.when > now) {
this.wait(mMessages.when - now);
}


} else {
// 如果没有消息则使线程进入等待状态
this.wait();
}
} catch (InterruptedException e) {
// ignore
}
}
}
}

到这里为止就知道JAVA是如何实现生产者消费者模式了,而且如果在具体的代码中来应用。

Handler(在Android的消息机制里,放苹果和吃苹果都是同一人)

消息处理器和消息发送器
Handler即是消息的发送者,也是消息的处理者,Handler必须在消息循环线程中被创建,在创建的时候会拿到当前线程的Looper对象,这样就可以同时拿到MessageQueue对象。
public Handler() {
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = null;
}

Handler发送消息即把Message放入其在创建时的MessageQueue对象。代码如下:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
boolean send = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
send = queue.enqueueMessage(msg, uptimeMillis);
}
return send;
}

Message在发送的时候注意上面标黄部分的代码,即个handler自身给了Message的target属性,即告诉Message由哪个handler来处理该消息。如何被handler调用见下面的Looper对象的实现。

Looper只管调用handler处理message的方法,具体如果来处理消息还是要handler自己来实现。Looper对象在loop()方法中会调用message.target.dispatchMessage(currentMsg)方法,即调用了Message对应的handler的dipatchMessage方法,具体的实现代码如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
handleMessage(msg);
}
}

Handler的handleMessage(Message msg)方法,我想就是大家很熟悉的代码了,我们一般在创建handler的时候一般都会调用该方法。


Looper

消息循环

这个类才是整个消息循环的主控制类,一般要想使得一个线程变成消息循环线程的代码是这样的:

Looper.prepare();
Looper.loop();

Prepare()方法创建一个线程私有(即ThreadLocal变量)的Looper对象,Looper内部聚合了一个MessageQueue,代码如下:
private Looper() {
mQueue = new MessageQueue();
}

public static void prepare() {
sThreadLocal.set(new Looper());
}
Loop()方法使得线程进入消息循环,并永远不退出,具体的实现原理也很简单,也就是不停的检测消息队列是否为空,如果不为空则处理消息,如果消息队列为空则线程睡眠,如果有消息本放入消息队列,则唤醒线程。

具体的实现代码如下:
public static void loop() {
Looper currentLooper = myLooper();
MessageQueue queue = currentLooper.mQueue;

while (true) {
// 从消息队列中取出一个消息
Message currentMsg = queue.next();

// 处理消息
if (currentMsg != null) {
if (currentMsg.target == null) {
return ;
}

//调用Message所对应的Handler对象分发消息
currentMsg.target.dispatchMessage(currentMsg);
currentMsg.recycle();
}
}
}

Loop方法首先拿到消息队列,然后进入一个死循环,代码不断的从消息队列中取出消息,然后处理消息。
分享到:
评论
2 楼 svygh123 2016-03-13  
例子很贴切,分析的很好
1 楼 qaghan 2013-04-02  
分析很到位。

相关推荐

    android多线程handler/message机制详解

    主要对handler 、message机制进行了详解,如果想了解更多android相关知识,可以去我博客看看

    详解Android Handler 机制 (一)用法全解

    ps:这是关于Android Handler 机制的第一篇文章,主要来说一下Handler的用法,本文尽量归纳完全,如有缺漏,欢迎补充。 Handler的主要作用是切换线程,以及隐式的充当接口回调的作用,当子线程网络请求结束后,通过...

    Android handler message奇怪用法详解

    android的多线程消息处理机制核心成员handler,基本用法很简单,相关资料也很多。本例子给大家带来handler的奇葩用法,与大家一起分享。

    Android Handler 机制实现原理分析

    本文主要介绍 Android Handle机制实现的原理,这里整理了详细的关于Handler的资料以及工作流程和实际应用,有兴趣的小伙伴可以参考下

    Android的消息处理机制--Looper,Handler

    详细描述了Android的消息处理机制中,Looper和handler类详解

    android线程消息机制之Handler详解

    android线程消息机制主要由Handler,Looper,Message和MessageQuene四个部分组成。平常在开发中,我们常用来在子线程中通知主线程来更新,其实整个安卓生命周期的驱动都是通过Handler(ActivityThread.H)来实现的。 ...

    详解Android Handler 机制 (三)内存泄漏

    ps:看本文之前最好先了解一下Handler源码 常用写法 我们一般使用Handler使用匿名内部类的写法,也就是: private Handler mHandler = new Handler() { @Override public void handleMessage(@NonNull Message msg...

    Android消息处理机制Looper和Handler详解

    Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。 Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法...

    Android handler 详解(面试必问)

    Handler为Android提供了一种异步消息处理机制,当向消息队列中发送消息 (sendMessage)后就立即返回,而从消息队列中读取消息时会阻塞,其中从消息队列中读取消息时会执行Handler中的public void handleMessage...

    Android Handler多线程详解

     Android的消息传递机制是另外一种形式的“事件处理”,这种机制主要是为了解决Android应用中多线程的问题,在Android中不 允许Activity新启动的线程访问该Activity里的UI组件,这样会导致新启动的线程无法改变UI...

    Android 消息机制以及handler的内存泄露

    主要介绍了Android 消息机制以及handler的内存泄露的相关资料,需要的朋友可以参考下

    Android异步消息处理机制详解及源码分析 - 工匠若水 - 博客频道 - CSDN1

    摘要视图订阅标签: Handler消息机制分类:目录(?Android异步消息处理机制详解及源码分析16244人阅读评论(21)收藏举报版权声明:本文为博主原创

    深入Android Handler,MessageQueue与Looper关系

    一说到Android的消息机制,自然就会联想到Handler,我们知道Handler是Android消息机制的上层接口,因此我们在开发过程中也只需要和Handler交互即可,很多人认为Handler的作用就是更新UI,这也确实没错,但除了更新UI...

    Android消息机制Handler的工作过程详解

    主要为大家详细介绍了Android消息机制Handler的工作过程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    详解Android中Handler的内部实现原理

    本文主要是对Handler和消息循环的实现原理进行源码分析,如果不熟悉Handler可以参见博文《详解Android中Handler的使用方法》,里面对Android为何以引入Handler机制以及如何使用Handler做了讲解。 概括来说,Handler...

    Android 消息机制详解及实例代码

    Android 消息机制 1.概述 Android应用启动时,会默认有一个主线程(UI线程),在这个线程中会关联一个消息队列(MessageQueue),所有的操作都会被封装成消息队列然后交给主线程处理。为了保证主线程不会退出,会将...

    Android异步消息机制详解

    Android中的异步消息机制分为四个部分:Message、Handler、MessageQueue和Looper。 其中,Message是线程之间传递的消息,其what、arg1、arg2字段可以携带整型数据,obj字段可以携带一个Object对象。 Handler是处理者...

Global site tag (gtag.js) - Google Analytics