• Android 中针对耗时的操作,放在主线程操作,轻者会造成 UI 卡顿,重则会直接无响应,造成 Force Close。同时在 Android 3.0 以后,禁止在主线程进行网络请求。
  • 针对耗时或者网络操作,那就不能在主线程进行直接操作了,需要放在子线程或者是工作线程中进行操作,操作完成以后,再更新主线程即 UI 线程。这里就涉及到一个问题了,在子线程执行完成以后,怎么能更新到主线程即 UI 线程呢,针对以上问题,就需要用到 Android 的消息机制了,即: Handler, Message, MessageQueue, Looper 全家桶

文章结构

  1. 用法
  2. 常见问题
    1. Can't create handler inside thread that has not called Looper.prepare()
    2. 内存泄漏
  3. 从源码角度解析为什么可以跨线程进行 UI 更新

用法

  1. 继承 Handler 类,复写 handleMessage() 方法, MyHandler 类的构造方法传入当前 Activity ,然后就可以对 ActivityUI 进行更新了。当前例子只是弹出一个 Toast
    static class MyHandler extends Handler {
// 弱引用,防止内存泄漏
WeakReference<MainActivity> weakReference; MyHandler(MainActivity activity) {
weakReference = new WeakReference<>(activity);
} @Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case WHAT:
MainActivity mainActivity = weakReference.get();
if (mainActivity != null) {
Toast.makeText(mainActivity, "Toast", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
}
  1. MainActicity onCreate,初始化 Handler
    MyHandler mHandler;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
}
  1. 点击 Button 按钮,模拟在子线程进行耗时操作,方法执行完成以后,通过 mhandler 发送一个 Message
    public void sendMessage(View view) {
//模拟耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
Message message = Message.obtain();
message.what = WHAT;
mHandler.sendMessage(message);
}
}
}).start(); }
  1. MyHandlerhanldeMessage() 方法中,接收到该 message ,并弹出 Toast 提醒
        @Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case WHAT:
MainActivity mainActivity = weakReference.get();
if (mainActivity != null) {
Toast.makeText(mainActivity, "Toast", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
  1. 以上代码,实现了在子线程执行耗时操作,执行完成以后更新 UI

    完整代码如下:
public class MainActivity extends AppCompatActivity {

    public static final int WHAT = 1000;

    MyHandler mHandler;

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
} @Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
} public void sendMessage(View view) {
//模拟耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
Message message = Message.obtain();
message.what = WHAT;
mHandler.sendMessage(message);
}
}
}).start(); } static class MyHandler extends Handler {
// 弱引用,防止内存泄漏
WeakReference<MainActivity> weakReference; MyHandler(MainActivity activity) {
weakReference = new WeakReference<>(activity);
} @Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case WHAT:
MainActivity mainActivity = weakReference.get();
if (mainActivity != null) {
Toast.makeText(mainActivity, "Toast", Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
}
}
}

常见问题

  1. Can't create handler inside thread that has not called Looper.prepare().
  • 在子线程 new 出来的 Handler 发送 message ,会出现该问题

    log如下:
06-04 14:55:38.365 16567-16818/cc.lijingbo.paypasswordview E/AndroidRuntime: FATAL EXCEPTION: Thread-1018
Process: cc.lijingbo.paypasswordview, PID: 16567
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at cc.lijingbo.paypasswordview.MainActivity$1.run(MainActivity.java:51)
at java.lang.Thread.run(Thread.java:818)

造成 crash 的代码如下:

    public void sendMessage(View view) {
//模拟耗时操作
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
Message message = Message.obtain();
message.what = WHAT;
new Handler().sendMessage(message);
}
}
}).start(); }
  • 如何避免: 假如是更新 UI 的话,只能使用 UI 线程的 Handler 来发送消息。
  1. 内存泄漏
  • 假如在子线程执行了耗时操作,这时用户操作进入了其他的 acitvity, 那么 MainActivity 就会被内存回收的,但是这个时候发现 Handler 还在引用着 MainActivity,内存无法及时回收,造成内存泄漏
  • Handler 防止内存泄漏常见方法:
    1. Handler 继承类,用 static 进行修饰,成为静态内部类
    2. Handler 中对 Activity 进行弱引用
    3. Activity 执行 onDestroy() 方法时,对 Handler 做执行清空操作。

从源码角度解析为什么可以跨线程进行 UI 更新

  • 消息机制主要包括: Handler, Looper , MessageQueue, Message

    • Handler: 两个作用,发送消息到 MessageQueue ,同时 Looper 不断从 MessageQueue 拿出消息分发给 Handler ,Handler 把消息通过 dispathMessage()handleMessage() 把消息分发具体的业务
    • Looper: 初始化 MessageQueue, 内部死循环不断从 MessageQueue 中获取数据进行分发给 Handler
    • MessageQueue: 消息队列,内部使用的是单链表的数据结构,
    • Message: 被传递的消息
  • 先来张时序图,让大脑有个大致的了解。

  • 为什么通过 Handler 可以把子线程的结果通知或者携带给 UI 线程

    • 这里的 Handler 指的是主线程的 Handler ,同时与 Handler 配套的 LooperMessageQueue 是在 UI 线程初始化的,所以在子线程中调用 Handler 发送消息可以更新 UI 线程。
    • LooperUI 线程源码, 在 ActivityThread 类:
    public static void main(String[] args) {
...
Looper.prepareMainLooper(); // 初始化在 UI 线程的 Looper ActivityThread thread = new ActivityThread();
thread.attach(false); if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
} if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
} // End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop(); // 开启从当前线程的 MessageQueue 中获取 Message throw new RuntimeException("Main thread loop unexpectedly exited");
}
- Looper
    public static void prepareMainLooper() {
prepare(false);// 设置不可退出
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
    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
}
    private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);// 在UI 线程,传入的是 fasle ,MessageQueue 不可退出。
mThread = Thread.currentThread();
}

源码解析

  • 先从 Handler 的 sendMessage() 这个入口开始。

    发现 sendMessage() 调用了内部方法 sendMessageDelayed() ,而 sendMessageDelayed() 调用了内部方法 sendMessageAtTime(), sendMessageAtTime() 调用了 enqueueMessage()enqueueMessage() 调用了 MessageQueueenqueueMessage() 方法。以上传来传去的这些在 Handler 中的方法,都是做各种状态的处理,然后把 Message 传到 MessageQueueenqueueMessage() 方法。

    源码如下:
    public final boolean sendMessage(Message msg)
{ // 立即发送的消息,设置延迟时间为 0
return sendMessageDelayed(msg, 0);
} ... public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
} public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// mQueue 从 Looper 中获取的。判断当前 Handler 持有的 MessageQueue 是否为 null ,为 null 的时候抛出 RuntimeException 。
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
// 传入 MessageQueue, Message, Time
return enqueueMessage(queue, msg, uptimeMillis);
} ... private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 设置 Message 的 target 为 当前的 Handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 把 Message 和 Time 传入到 MessageQueue 的 enqueueMessage() 方法执行。
return queue.enqueueMessage(msg, uptimeMillis);
}
...
  • MessageQueue 的 enqueueMessage() 源码
    boolean enqueueMessage(Message msg, long when) {
// Message 的target 即 Handler ,不能为 null ,为 null 的时候抛异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) { // msg 用过抛出异常
throw new IllegalStateException(msg + " This message is already in use.");
} synchronized (this) {
if (mQuitting) {// 退出标志位,要是退出的话, 执行 msg 的回收操作。
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle(); //Message 回收操作
return false;
} msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// MessageQueue 没有消息,或者是 msg 是第一条消息,唤醒阻塞的 MsssageQueue
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 将消息按时间顺序插入到 MessageQueue, 一般地,不需要唤醒消息队列,除非消息队头存在 barrier ,并且 Message 同时是队列最早的异步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
} // We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

MessageQueue 按照 Message 触发时间的先后顺序排列的,对头的消息是将要最早触发的消息,当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。

  • 取消息

    当发送了消息后,由 MessageQueue 维护消息队列,然后在 Looper 中通过 loop() 方法不断的取消息。
    public static void loop() {
// 通过 ThreadLocal 获取 存储当前线程的的 Looper。
final Looper me = myLooper();
// 做非空判断,为空的话,抛出异常。
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
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();
// 死循环,不断的从 MessageQueue 中获取 Message
for (;;) {
Message msg = queue.next(); // MessageQueue 没消息的时候,可能会阻塞
if (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 logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
} final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs; final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
// 消息分发,Message 的 target 就是 Handler, 这里调用 Handler 的dispatchMessage() 把消息分发出去
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " + time + "ms on "
+ Thread.currentThread().getName() + ", h=" +
msg.target + " cb=" + msg.callback + " msg=" + msg.what);
}
} 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();
}
}
    Message next() {
// 返回
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
// 当消息循环已经退出则返回
if (ptr == 0) {
return null;
} int pendingIdleHandlerCount = -1; // 循环迭代首次为-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞操作,当等待 nextPollTimeoutMillis时长 或者消息队列被唤醒,都会返回
nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 当 msg 的 Handler 为null时,查询 MessageQueue 的下一条异步消息,为Null则退出
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 当异步消息触发时间大于当前时间,则设置下一次循环的超市时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取一条消息,设置消息为已使用,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
// 设置消息的使用状态
msg.markInUse();
// 获取MessageQueue下一条要执行的消息
return msg;
}
} else {
// 没有消息
nextPollTimeoutMillis = -1;
}
// 所有待处理的消息已被处理,消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
...
}
}
}

nativePollOnce 是阻塞操作,其中 nextPollTimeoutMillis 代表下一个消息到来前,还需要等待的时长;当 nextPollTimeoutMillis = -1 时,表示消息队列中无消息,会一直等待下去。

可以看出 next() 方法根据消息的触发时间,获取下一条需要执行的消息,队列中消息为空时,则会进行阻塞操作。

  • 分发消息

    分发消息,由 Handler 的 dispatchMessage() 方法执行
    public void dispatchMessage(Message msg) {
// msg 的 callback 不为null,这消息分发给 handleCallback() 执行, 然后调用 message.callback.run() 方法
if (msg.callback != null) {
handleCallback(msg);
} else {
// Handler 的内部接口,不为null ,执行该接口的实现方法 handleMessage();
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// Message 的 Runable 和 Callback 接口都为 null 的情况下,执行 Handler 的handlrMessage() 方法。
handleMessage(msg);
}
}
    private static void handleCallback(Message message) {
message.callback.run();
}

消息分发流程:

Message 的 msg.callback 不为空的时候,执行 message.callback.run() 方法

Message 成员变量 Callback 不为null 的时候,执行该接口的 handleMessage() 方法

最后调用 Handler 自身的 handleMessage() 方法

  • 最后放一张图来整理理解

https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis/message-mechanism.html

Android 消息分发机制的更多相关文章

  1. Android事件分发机制源码分析

    Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...

  2. Android 事件分发机制具体解释

    很多其它内容请參照我的个人网站: http://stackvoid.com/ 网上非常多关于Android事件分发机制的解释,大多数描写叙述的都不够清晰,没有吧来龙去脉搞清晰,本文将带你从Touch事 ...

  3. Android事件分发机制二:viewGroup与view对事件的处理

    前言 很高兴遇见你~ 在上一篇文章 Android事件分发机制一:事件是如何到达activity的? 中,我们讨论了触摸信息从屏幕产生到发送给具体 的view处理的整体流程,这里先来简单回顾一下: 触 ...

  4. Android事件分发机制三:事件分发工作流程

    前言 很高兴遇见你~ 本文是事件分发系列的第三篇. 在前两篇文章中,Android事件分发机制一:事件是如何到达activity的? 分析了事件分发的真正起点:viewRootImpl,Activit ...

  5. Android事件分发机制五:面试官你坐啊

    前言 很高兴遇见你~ 事件分发系列文章已经到最后一篇了,先来回顾一下前面四篇,也当个目录: Android事件分发机制一:事件是如何到达activity的? : 从window机制出发分析了事件分发的 ...

  6. Android事件分发机制(下)

    这篇文章继续讨论Android事件分发机制,首先我们来探讨一下,什么是ViewGroup?它和普通的View有什么区别? 顾名思义,ViewGroup就是一组View的集合,它包含很多的子View和子 ...

  7. Android事件分发机制(上)

    Android事件分发机制这个问题不止一个人问过我,每次我的回答都显得模拟两可,是因为自己一直对这个没有很好的理解,趁现在比较闲对这个做一点总结 举个例子: 你当前有一个非常简单的项目,只有一个Act ...

  8. android事件分发机制

    android事件分发机制,给控件设置ontouch监听事件,当ontouch返回true时,他就不会走onTouchEvent方法,要想走onTouchEvent方法只需要返回ontouch返回fa ...

  9. [转]Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    Android事件分发机制 该篇文章出处:http://blog.csdn.net/guolin_blog/article/details/9097463 其实我一直准备写一篇关于Android事件分 ...

随机推荐

  1. python程序打包

    环境: CentOS6.5_x64Python版本 : 2.6 使用pyinstaller打包 pyinstaller可以将python程序打包成二进制文件,打包后的文件在没有python的环境中也可 ...

  2. Java泛型的PECS原则

    1.什么是PESC ? PESC  = producer-extens , consumer -super. 如果参数化类型表示一个 T 生产者,就使用 <? extends T>: 如果 ...

  3. Android开发之欢迎界面标准

    import java.util.ArrayList; import android.app.Activity; import android.content.SharedPreferences; i ...

  4. visual studio运行时库MT、MTd、MD、MDd的研究

    在开发window程序是经常会遇到编译好好的程序拿到另一台机器上面无法运行的情况,这一般是由于另一台机器上面没有安装响应的运行时库导致的,那么这个与编译选项MT.MTd.MD.MDd有什么关系呢?这是 ...

  5. django之创建第1个项目并查看网页效果

    1.c盘下创建djangoweb文件夹 Microsoft Windows [版本 6.1.7601]版权所有 (c) 2009 Microsoft Corporation.保留所有权利. 2.C:\ ...

  6. JVM常见面试题

    1. 内存模型以及分区,需要详细到每个区放什么. 栈区: 栈分为java虚拟机栈和本地方法栈 重点是Java虚拟机栈,它是线程私有的,生命周期与线程相同. 每个方法执行都会创建一个栈帧,用于存放局部变 ...

  7. 基于RESTful API 怎么设计用户权限控制?

    前言 有人说,每个人都是平等的:也有人说,人生来就是不平等的:在人类社会中,并没有绝对的公平,一件事,并不是所有人都能去做:一样物,并不是所有人都能够拥有.每个人都有自己的角色,每种角色都有对某种资源 ...

  8. QQ登录整合/oauth2.0认证-02-跳转到QQ互联页

    ---------------------------目录---------------------------------- QQ登录整合/oauth2.0认证-01-申请appkey和appid ...

  9. [转]NLP Tasks

    Natural Language Processing Tasks and Selected References I've been working on several natural langu ...

  10. Delphi消息推送

    移动端的消息推送大家都体验过,智能手机上一大堆广告等各种消息会不时从消息栏中弹出来骚扰你. PC程序中我们有时也会用到消息推送,比如通知之类.通常我们使用的方法可能更多地使用Socket之类来处理,有 ...