Android Handler 机制总结
1、Android消息机制是什么?
Android 消息机制 主要指 Handler 的运行机制以及 Handler 所附带的 MessageQueue 和 Looper 的工作流程。Handler 的主要作用是将任务切换到指定线程去执行,我们常用的就是通过 Handler 来异步更新 UI(线程间的信息传递)。
2、Handler 使用方法
2.1、创建一个 Handler 实例用来发送接收并处理消息
Handler myHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case TestHandler.GUIUPDATEIDENTIFIER:
myBounceView.invalidate();
break;
}
super.handleMessage(msg);
}
}; myHandler.post(run);
myHandler.sendEmptyMessage(TestHandler.GUIUPDATEIDENTIFIER);
myHandler.sendMessage(myHandler.obtainMessage());
上面最后三行都是发送消息,你也可以在其他线程发消息,然后在主线程接收,处理 UI 逻辑;
2.2、如果你需要在某个线程来处理消息,那也是可以的:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mLooper = Looper.myLooper();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "handleMessage: " + msg.what);
}
};
Looper.loop();
}
}).start();
这里 Looper 让该线程一直在运行,从而通过消息循环机制获取从其他线程发来的消息,传给 Handler 来处理。
如下,你在下面的线程中发送一个消息,上面的 Handler 就会收到:
new Thread(new Runnable() {
@Override
public void run() {
if (mLooper != null) {
mHandler.sendEmptyMessage(123456);
Log.d(TAG, "sendMessage: " + 123456);
}
}
}).start();
这样似乎也可以达到两个线程互相通信的效果。
3、Handler 不正确使用引发的内存泄露
容易造成内存泄漏的一种Handler使用方法:将 Handler 声明为 Activity 的内部类。在 Java 语言中,非静态内部类会持有外部类的一个隐试引用,这样就可能造成外部类无法被垃圾回收。而导致内存泄漏。
那么正确的使用就是:
- 将 Handler 声明为静态内部类。并持有外部类的若引用。
- 在子线程中使用 Handler,这是需要我们自己创建一个 Looper 对象。
具体可参考文章:Android 常见内存泄露 & 解决方案
其中该文的第二种方法贴下,我也是第一次看到:
将自定义 Handler 抽出去,也同样达到效果的小栗子:
首先创建一个类,通过泛型将实例传入
public class UIHandler<T> extends Handler {
protected WeakReference<T> ref;
public UIHandler(T cls){
ref = new WeakReference<T>(cls);
} public T getRef(){
return ref != null ? ref.get() : null;
}
}
看下 activity 中使用,直接用 myHandler 对象发送 message 即可。
private static class UIMyHandler extends UIHandler<HandlerActivity>{ public UIMyHandler(HandlerActivity cls) {
super(cls);
} @Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
HandlerActivity activity = ref.get();
if (activity != null){
if (activity.isFinishing())
return;
switch (msg.what){ }
}
}
} private UIMyHandler myHandler = new UIMyHandler(this);
其实两种写法大同小异,只是觉得第二种方式思想更好,这种思想值得每一个人学习。
4、Handler 原理简析
它在使用的过程中主要与 Messgae、MessageQueue、和 Looper 这三个对象关联密切,Handler 机制的实现原理依赖于这三者。下面就来简单讲讲这三者和 Handler 之间的关系。
4.1 Handler :消息发送者,处理者,
几个常见的构造方法,分别是:
Handler()
默认构造方法,与当前线程及其Looper实例绑定。如在主线程中执行new Handler()
,那么该 handler 实例所绑定的便是 UI 线程和 UI 线程绑定的 Looper 实例。
Handler(Handler.Callback callback)
与当前线程及其 Looper 实例绑定,同时调用一个 callback 接口(用于实现消息处理——即在 callback 中重写 handleMessage() 方法)
Handler(Looper looper)
将该新建的 handler 实例与指定的 looper 对象绑定。Handler(Looper looper, Handler.Callback callback)
指定该 handler 实例所绑定的 looper 实例并使用给定的回调接口进行消息处理。
4.2 Message 信息传播的载体
extends Object implements Parcelable
一个 message 对象包含一个自身的描述信息和一个可以发给 handler 的任意数据对象。这个对象包含了两个int 类型的extra 字段和一个 object 类型的 extra 字段。利用它们,在很多情况下我们都不需要自己做内存分配工作。
虽然 Message 的构造方法是 public 的,但实例化 Message 的最好方法是调用 Message.obtain()
或 Handler.obtainMessage()(实际上最终调用的仍然是
,因为这两个方法是从一个可回收利用的 message 对象回收池中获取Message实例。该回收池用于将每次交给 handler 处理的 message 对象进行回收。Message.obtain()
)
4.3 MessageQueue 信息传递的队列
MessageQueue 是用来存放 Message 的集合,并由 Looper 实例来分发里面的 Message 对象。同时,message 并不是直接加入到 MessageQueue 中的, 而是通过与 Looper 对象相关联的 MessageQueue.IdleHandler
对象来完成的。我们可以通过 Looper.myQueue()
方法来获得当前线程的MessageQueue。
PS: MessageQueue 是由 Looper 来管理的,当你创建。
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
4.4 Looper 消息处理的动力来源
Looper是线程用来运行消息循环(message loop)的类。默认情况下,线程并没有与之关联的Looper,可以通过在线程中调用 Looper.prepare()
方法来获取,并通过 Looper.loop()
无限循环地获取并分发 MessageQueue 中的消息,直到所有消息全部处理。
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
sThreadLocal 对象是一种特殊的全局性变量,它的全局性仅限于自己所在的线程,而外界所有线程一概不能访问到它,因此,每个线程的 Looper 是独立的,这也是如果我们想在一个线程中处理消息,必须先调用 Looper.prepare() ,是为了区别于线程来保存数据。
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
} 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.prepare() 会重新创建一个新的 Looper,这也说明了确实每个线程一个 Looper。
当我们想要在其他线程传递数据到主线程时,一般会先获取主线程分 Looper,原因也是上面说的,每个每个线程一个 Looper。而 Looper 又是MessageQueue 的持有者,只有将消息放入到对应线程的 MessageQueue 中,才能在对应线程中做相应的处理。
这也是 Handler 跨线程处理消息的本质所在。
4.5 ThreadLocal 线程数据存储者
上源码:
public class ThreadLocal<T> {
.....
}
这里可以看出 threadlocal 是一个范型类,这标志着 threadlocal 可以存储所有数据,作为存储数据来说,我们首先想到的是会对外提供set(), get(), remove(),等方法。
这里可以看下 set 方法,可以发现是先获取 Thread.currentThread;数据和线程是紧密联系在一起的。
/**
* Sets the value of this variable for the current thread. If set to
* {@code null}, the value will be set to null and the underlying entry will
* still be present.
*
* @param value the new value of the variable for the caller thread.
*/
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据。 可以理解为 hashmap,根据key值(所处的线程)来获取values. 其中 values 里面保存着各种各样的数据。它们对ThreadLocal所做的读写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据。
4.6 消息延时原理
最后会调用 enqueueMessage 将 message 添加到单链表队列中。
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
最终会调用 MessageQueue 的 enqueueMessage() 方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
接着我们再来看代码:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
} synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
} msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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;
}
根据延迟时间的长短,先将 message 添加到单链表队列中去。如果添加到第一个的,就会调用 nativeWake 来唤醒当前的阻塞。这里大家可能没看到哪里阻塞了,后面会提到。接下来我们看 loop() 方法:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
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(); for (;;) {
Message msg = queue.next(); // might block
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 traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
} 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();
}
}
从中可以看到会从 MessageQueue 队列中获取下一个消息,如果获取到了,就会调用 msg.target.dispatchMessage(msg) 来处理这个消息。那怎么去获取这个消息呢,接下来看看 next 的内容。
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 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
} nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
} // Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
} // If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
} if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} // Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
} if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
} // Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
可以看到,在是在一个无线循环里面调用 nativePollOnce(ptr, nextPollTimeoutMillis) 进行阻塞。nativePollOnce() 的作用类似与 object.wait(),只不过是使用了 Native 的方法对这个线程精确时间的唤醒。如果时间是 0 会立马返回,也就是不会阻塞。接着会继续往下执行。
如果是第一次运行,会优先处理 idlers,只有这个处理完了,才会开始处理队列里面的消息。然后会计算消息的延时,时间到了,就会return,回到 loop ()方法。
继续处理下一个消息。
总结:
postDelay() 一个10秒钟的 Runnable A、消息进队,MessageQueue 调用 nativePollOnce() 阻塞,Looper 阻塞;
紧接着 post() 一个 Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把 B 插入消息队列的头部(A的前面),然后调用nativeWake() 方法唤醒线程;
MessageQueue.next() 方法被唤醒后,重新开始读取消息链表,第一个消息 B 无延时,直接返回给 Looper;
Looper 处理完这个消息再次调用 next() 方法,MessageQueue 继续读取消息链表,第二个消息 A 还没到时间,计算一下剩余时间(假如还剩9秒)继续调用 nativePollOnce() 阻塞;直到阻塞时间到或者下一次有 Message 进队;
到此,关于Handler 的内容基本总结完毕。
5、最后附上一张图:
概括:子线程获取主线程的 Looper,然后发送 Message。 Message 被压入主线程 Looper 中的 MessageQueue,Looper 从队列中取出一个消息,交给主线程中的 Handler 来处理。需注意,发消息和处理消息的 Handler 同属于某个 Handler 的实例。
参考文章
1、android Handler机制之ThreadLocal详解
Android Handler 机制总结的更多相关文章
- Android Handler 机制 - Looper,Message,MessageQueue
Android Studio 2.3 API 25 从源码角度分析Handler机制.有利于使用Handler和分析Handler的相关问题. Handler 简介 一个Handler允许发送和处理M ...
- Android Handler机制剖析
android的handler机制是android的线程通信的核心机制 Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃. Android中的实现了 接收消息的& ...
- Android Handler机制 (一个Thead中可以建立多个Hander,通过msg.target保证MessageQueue中的每个msg交由发送message的handler进行处理 ,但是 每个线程中最多只有一个Looper,肯定也就一个MessageQuque)
转载自http://blog.csdn.net/stonecao/article/details/6417364 在android中提供了一种异步回调机制Handler,使用它,我们可以在完成一个很长 ...
- Android Handler机制彻底梳理
Android的消息机制其实也就是Handler相关的机制,对于它的使用应该熟之又熟了,而对于它的机制的描述在网上也一大堆[比如15年那会在网上抄了一篇https://www.cnblogs.com/ ...
- android Handler机制之ThreadLocal详解
概述 我们在谈Handler机制的时候,其实也就是谈Handler.Message.Looper.MessageQueue之间的关系,对于其工作原理我们不做详解(Handler机制详解). Messa ...
- android——handler机制原理
在android版本4.0及之后的版本中多线程有明确的分工,子线程可以写所有耗时的代码(数据库.蓝牙.网络服务),但是绝对不能碰UI,想碰UI跟着主线程走,那么我们如何才能让主线程知道我们要对 UI进 ...
- android handler机制和Timer采用
Timer主要用于创建一个任务来定期运行. 创建继承Task该任务等级.即任务每次跑. private class MyTask extends TimerTask { @Override publi ...
- Android Handler的使用示例:结合源码理解Android Handler机制(一)
什么是Handler? Android 的官方解释: 文档分节1:A Handler allows you to send and process Message and Runnable objec ...
- Android Handler机制(四)---Handler源码解析
Handler的主要用途有两个:(1).在将来的某个时刻执行消息或一个runnable,(2)把消息发送到消息队列. 主要依靠post(Runnable).postAtTime(Runnable, l ...
随机推荐
- golang使用 gzip压缩
golang使用 gzip压缩 这个例子中使用gzip压缩格式,标准库还支持zlib, bz2, flate, lzw 压缩处理_三步: 1.创建压缩文件2.gzip write包装3.写入数据 ou ...
- php5.6,Ajax报错,Warning: Cannot modify header information - headers already sent in Unknown on line 0
php5.6ajax报错 Deprecated: Automatically populating $HTTP_RAW_POST_DATA is deprecated and will be remo ...
- mixer中动态Alpha通道处理案例
本案例处理的是RGB+a,每个色彩的采样为10位位宽. 1.在Mixer IP中打开Alpha Blending Enable 和Alpha Input Stream Enable.这样在Blo ...
- pt-query-digest 使用说明
pt-query-digest --user=anemometer --password=123456 --socket=/tmp/mysql.sock --port=43306 --review h ...
- SAP开发系统中开发和配置客户端请求号变更
假如102为开发客户端,800为配置客户端 正常操作,创建开发请求,应该在102客户端里去创建,但由于操作疏忽开发请求建在了800客户端,如何调整请求到102? 调整步骤:登陆102,SE09找到80 ...
- Jquery 在子页面上设置父页面元素的值
使用情景:因为我父页面上有用art.dialog,而子页面上有项目中的框架弹出方法跟art.dialog冲突,不能使用art.dialog自带的方法传值, 所以只好用一种简单粗暴的方法来设置. var ...
- Chrome 的 PNaCl 还活着么?
WebAssembly Migration Guide Given the momentum of cross-browser WebAssembly support, we plan to focu ...
- CTR常见规则摘录
1.给用户推荐热门的10个商品 . 2.对数据进行预处理,删除未购买过品牌的用户记录(不删除最近一周才出现的新用户),删除未被购买过的品牌记录,删除疯狂点击但是从不购买的刷钻用户记录等,利用一些简单 ...
- 关于isNaN()函数的细节
根据<JavaScript高级程序设计>的解释,NaN,即非数值(Not a Number),用于表示一个本来要返回数值的操作数未返回数值的情况,例如5/0就会得到NaN. 而因为NaN的 ...
- Redis的使用及参考代码
Redis是一种完全开源免费,高性能的key-value数据库或数据结构服务器,因为value值可以是字符串,哈希(map),列表list,集合等. Jedis 是 Redis 官方首选的 Java ...