尊重原创:http://blog.csdn.net/yuanzeyao/article/details/38408493

作为Android开发者,Handler这个类应该是再熟悉只是了。由于差点儿不论什么App的开发。都会使用到Handler这个类,有些同学可能就要说了,我全然能够使用AsyncTask取代它,这个确实是能够的,可是事实上AsyncTask也是通过Handler实现的,详细的大家能够去看看源代码即可了,Handler的主要功能就是实现子线程和主线程的通信。比如在子线程中运行一些耗时操作。操作完毕之后通知主线程跟新UI(由于Android是不同意在子线程中跟新UI的)。

以下就使用一个简单的样例開始这篇文章吧

public class MainActivity extends Activity
{
public static final int MSG_DOWNLOAD_FINISH=0X001;
//创建一个Handler的匿名内部类
private Handler handler=new Handler()
{ @Override
public void handleMessage(Message msg)
{
switch(msg.what)
{
case MSG_DOWNLOAD_FINISH:
Log.v("yzy", "handler所在的线程id是-->"+Thread.currentThread().getName());
break;
}
} }; @Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} //启动一个下载线程
public void download(View view)
{
new Thread()
{
public void run() {
try
{
Log.v("yzy", "下载子线程的Name是--->"+Thread.currentThread().getName());
//在子线程运行,模拟一个下载任务
Thread.sleep(2000);
//下载完毕后,发送下载完毕消息
handler.sendEmptyMessage(MSG_DOWNLOAD_FINISH);
} catch (InterruptedException e)
{
e.printStackTrace();
}
};
}.start();
}

运行结果:
08-03 16:31:46.418: V/yzy(30486): 下载子线程的Name是--->Thread-22588
08-03 16:31:48.421: V/yzy(30486): handler所在的线程Name是-->main

上面样例就是模拟了一个下载任务,当下载完毕后,通过Handler对象发送一个消息,最后被handlerMessage方法接收并处理。通过运行结果能够发现。download方法是在子线程中完毕的。而handlerMessage是在UI线程中被调用的。

到了这里一个简单的Handler使用案例就结束了,假设你已经明确了上面的代码,那么说明你已经明确了Handler的最基本使用。
以下再来一个简单的样例:

public void postRun(View view)
{
new Thread(){
public void run() {
try
{
Log.v("yzy", " 下载子线程的Name是--->"+Thread.currentThread().getName());
Thread.sleep(2000);
handler.post(new Runnable()
{ @Override
public void run()
{
Log.v("yzy", "handler post run -->"+Thread.currentThread().getName());
}
});
} catch (InterruptedException e)
{
e.printStackTrace();
} }; }.start(); }

运行结果:
08-03 16:31:52.528: V/yzy(30486):  下载子线程的Name是--->Thread-22589
08-03 16:31:54.531: V/yzy(30486): handler post run -->main

在Handler中除了能够发送Message对象还能够发送Runnable对象。从运行结果来看发送的Runnable也是在main线程中运行的
那么是不是通过handler发出的Message和Runnable都是在UI线程中运行的呢,这个可不一定。如今我在onCreate方法中创建一个handler2

HandlerThread thread=new HandlerThread("yzy");
thread.start(); handler2=new Handler(thread.getLooper())
{
public void handleMessage(Message msg) {
switch(msg.what)
{
case MSG_DOWNLOAD_FINISH:
Log.v("yzy", "handler所在的线程Name是-->"+Thread.currentThread().getName());
break;
} };
};

然后将上面代码中使用handler的地方换为handler2,然后运行代码:代码中创建了一个名称为"yzy"的线程。然后再yzy线程中初始化了handler2
结果例如以下:
08-03 17:07:10.571: V/yzy(8224): 下载子线程的Name是--->Thread-23005
08-03 17:07:12.574: V/yzy(8224): handler所在的线程Name是-->yzy

我们发现通过handler2发出的Message和Runnable都是在yzy线程中运行的。

假设对于上面的代码和运行结果你都知道原因。那么说明你已经对Handler的使用非常熟悉了。假设不清楚上面的运行结果,那么请往下继续吧

開始分析原因之前我想提出以下几个问题:
1、Handler发出的Message和Runnable是怎样传递到UI线程的?
2、在什么情况下通过Handler发出的Message和Runnable不会传递到UI线程?
3、为什么我是在HandlerThread初始化一个Handler?

以下我们就逐一解决上述问题吧,在解决这个问题之前我们须要分析一个Hanlder这个类的源代码。

首先看看Handler的构造函数,它有好几个构造函数。我们一个个的看吧

//无參构造函数
public Handler() {
//检查Handler是否是static的,假设不是的。那么有可能导致内存泄露。详细能够百度
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
//非常重要的一个属性。临时就将Looper理解成一个消息队列的管理者吧。用来从消息队列中取消息的,稍后会分析这个类
mLooper = Looper.myLooper();
if (mLooper == null) {
//这个异常非经常见哦。Handler一定要在有Looper的线程上运行,这个也就是为什么我在HandlerThread中初始化Handler
//而没有在Thread里面初始化,假设在Thread里面初始化须要先调用Looper.prepare方法
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//将mLooper里面的消息队列拷贝到自身的mQueue,这也就意味着Handler和Looper是公用一个消息队列
mQueue = mLooper.mQueue;
//回调函数默认是Null
mCallback = null;
} //这个和上面一样的。仅仅只是传入了一个回调函数
public Handler(Callback callback) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
} mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
} /**
* 传入一个Looper,并和Looper公用一个消息队列
*/
public Handler(Looper looper) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = null;
} /**
* 和上面一个几乎相同,就不在赘述
*/
public Handler(Looper looper, Callback callback) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
}

看了Handler的构造函数。发现Hanlder里面有一个消息队列,这个消息队列是从Looper里面传入的,而且Handler须要在有Looper的线程中调用。

既然它和Looper关系这么紧密,我们看看Looper究竟是什么东西吧。打开Looper的源代码,顶部看到这种一个样例:

*  class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
*

这个就是为了说明Handler仅仅能在有Looper的线程中创建,UI线程是有Looper的。对于没有Looper的线程,须要调用Loope.prepare,Looper.loop等函数,如上例。

那我们正式进入Looper的源代码看看吧

//Looper的构造函数,主要是对消息队列等属性初始化
private Looper() {
mQueue = new MessageQueue();
mRun = true;
//记录所在线程
mThread = Thread.currentThread();
} //在上面的样例中,看到当在一个没有Looper的线程中创建Handler,就须要运行这个函数,
//这个函数主要是new 一个Looper,燃火放入ThreadLocal中保存。做到一个线程就创建一次。 第二次调用这种方法会抛出异常的
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
} //prepare是创建并保存。这种方法就是取出Looper
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
} //这种方法是给系统调用的,UI线程通过调用这个线程,从而保证UI线程里有一个Looper
//须要注意:假设一个线程是UI线程,那么myLooper和getMainLooper是同一个Looper,通过这个代码非常好理解
public static final void prepareMainLooper() {
prepare();
setMainLooper(myLooper());
if (Process.supportsProcesses()) {
myLooper().mQueue.mQuitAllowed = false;
}
} //获得UI线程的Looper,通常我想Hanlder的handleMessage在UI线程运行时一般会new Handler(getMainLooper());
public synchronized static final Looper getMainLooper() {
return mMainLooper;
} /**
* 上面的样例是运行了这种方法的,这种方法是一个死循环。一直从消息队列中读取消息,并分发出去
*/
public static final void loop() {
//得到本线程的Looper
Looper me = myLooper();
//拿到消息队列
MessageQueue queue = me.mQueue;
while (true) {
//从消息队列中拿一个消息
Message msg = queue.next(); // might block
//取到了一个消息
if (msg != null) {
//这个一般不等于Null,通常就是发出这个Message的Handler
if (msg.target == null) {
// No target is a magic identifier for the quit message.
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
//调用Handler的dispatchMessage方法
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
//将消息放入消息池
msg.recycle();
}
}
} //返回Looper的消息队列
public static final MessageQueue myQueue() {
return myLooper().mQueue;
}

看了Looper的源代码之后总结一下:
  在一个线程的run方法里面先调用prepare方法。主要保证了该线程里面有了一个Looper对象,假设在这个线程里面创建一个Hanlder,那么看看上面Handler的空的构造方法。Handler里面的Looper就是线程里面保存的Looper,从而能够创建的Handler和线程公用一个Looper,也就是公用一个消息队列。

说的更详细一点就是Hanlder和Looper所在的线程公用一个消息队列。

最后调用loop方法。这个线程不断从消息队列取消息。然后调用Hanlder的dispatchMessage方法。
  
最后看看Handler的其它几个比較重要的方法吧

//在Handler中发送一个消息到消息队列。相似的方法非常多,我仅仅选了这一个
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
//将target设置成了Handler
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
//这种方法是在loop方法中被调用的
public void dispatchMessage(Message msg) {
//首先检查msg中callback(是一个Runnable对象)是否为Null,假设不为null,则直接调用callback中的run方法
if (msg.callback != null) {
handleCallback(msg);
} else {
//检查Handler中的callback是否为空,假设不为空,则直接调用callback中的handleMessage,假设返回TRUE,则直接返回不在调用Handler中的handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//最后调用Handler中的handlerMessage
handleMessage(msg);
}
}

在前面的样例中,我们以前使用过Handler中的post方法,我们看看它是怎么实现的

 public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
} private final Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
} public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

我想假设前面明确了的,这个就不用我说了的吧。

事实上在Activity中有一个方法runOnUiThread,就是使用这个原理实现的。例如以下:

//让Runnable在UI线程运行
public final void runOnUiThread(Runnable action) {
//假设当前线程不是UI线程。那么通过Handler转发到UI线程
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

View里面也有相似的方法:

public boolean post(Runnable action) {
Handler handler;
if (mAttachInfo != null) {
handler = mAttachInfo.mHandler;
} else {
// Assume that post will succeed later
ViewRoot.getRunQueue().post(action);
return true;
} return handler.post(action);
}

原理是上面是一样的。

依据上面的学习,能够得出结论:
  1、假设Handler是在UI线程创建的。那么Handler和UI线程共享消息队列,所以通过Hanlder发出的消息都是在UI线程中运行的
  2、假设要让Handler发出的消息在子线程中运行,非常easy,在创建Handler的时候传入子线程的Looper(通过Looper.prepare创建)
  到这里相信在前面我提及的三个问题都已经有答案了吧,假设有没有明确的,欢迎留言。。。。

Android Handler消息机制深入浅出的更多相关文章

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

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

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

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

  3. Android Handler 消息机制原理解析

    前言 做过 Android 开发的童鞋都知道,不能在非主线程修改 UI 控件,因为 Android 规定只能在主线程中访问 UI ,如果在子线程中访问 UI ,那么程序就会抛出异常 android.v ...

  4. Android消息传递之Handler消息机制

    前言: 无论是现在所做的项目还是以前的项目中,都会遇见线程之间通信.组件之间通信,目前统一采用EventBus来做处理,在总结学习EventBus之前,觉得还是需要学习总结一下最初的实现方式,也算是不 ...

  5. Handler消息机制的一些原理(直接用code讲解)——Android开发

    package com.example.handlertest; import android.os.Bundle; import android.os.Handler; import android ...

  6. 【Android】Handler消息机制

    Handler消息机制主要涉及Looper.Handler.MessageQueue.Message.其中,Looper主要负责获取消息,Handler负责发送消息及处理消息,MessageQueue ...

  7. Android全面解析之由浅及深Handler消息机制

    前言 很高兴遇见你~ 欢迎阅读我的文章. 关于Handler的博客可谓是俯拾皆是,而这也是一个老生常谈的话题,可见的他非常基础,也非常重要.但很多的博客,却很少有从入门开始介绍,这在我一开始学习的时候 ...

  8. Handler消息机制与Binder IPC机制完全解析

    1.Handler消息机制 序列 文章 0 Android消息机制-Handler(framework篇) 1 Android消息机制-Handler(native篇) 2 Android消息机制-H ...

  9. 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制

    第10章 Android的消息机制 10.1 Android消息机制概述 (1)Android的消息机制主要是指Handler的运行机制,其底层需要MessageQueue和Looper的支撑.Mes ...

随机推荐

  1. qW3xT.2挖矿病毒 解决过程及坑

    周一早上老大让我把项目更新一下,然后配置一下elasticsearch,我登上服务器之后部署的时候没有什么感觉,但是在配置elasticsearch的过程中感觉服务器哪个地方有点不对,下意识的top了 ...

  2. 利用 html+css 画同心圆(concentric circles)——绝对布局与相对布局

    一.css 绘制圆 #circle { width: 300px; height: 300px; background-color: #000000; border-radius: 300px; } ...

  3. 如何用js代码实现图片切换效果

    通过点击按钮,实现图片的隐藏与显现,切换. 实现代码:<style> .a{ width: 300px; height: 300px; border: 1px solid black; } ...

  4. js 记住我

    $(function(){ $("#btn_login").click(function() { var anv=$("#an").val(); //登录名 v ...

  5. C 语言复杂声明

    int board [8] [8] ; //声明一个内含 int 数组的数组 int ** ptr ; //声明一个指向指针的指针,被指向的指针指向 int int * risks [10] ; // ...

  6. rabbitmq-3.5.1-安裝

    系统版本:CentOS 6.5RabbitMQ-Server:3.5.1一.安装erlang1.安装准备,下载安装文件 wget https://packages.erlang-solutions.c ...

  7. PAT_A1145#Hashing - Average Search Time

    Source: PAT A1145 Hashing - Average Search Time (25 分) Description: The task of this problem is simp ...

  8. wafII笔记

    wafII笔记:    组件的使用方法:        组件属性:                 属性的设置和获取通过option方法来完成 waf("#id").wafProm ...

  9. kernel panic必备知识

    获得vmcore Kernel dump 是什么 Kdump – 捕捉kernel dump的工具 Kdump的工作原理 Kdump的配置 Dump分析的工具crash(1) 准备环境 根据vmcor ...

  10. nyoj22-素数求和问题

    素数求和问题 时间限制:3000 ms  |  内存限制:65535 KB 难度:2 描述 现在给你N个数(0<N<1000),现在要求你写出一个程序,找出这N个数中的所有素数,并求和. ...