Android中Handler的消息处理机制以及源码分析
在实际项目当中,一个很常见的需求场景就是在根据子线程当中的数据去更新ui。我们知道,android中ui是单线程模型的,就是只能在UI线程(也称为主线程)中更新ui。而一些耗时操作,比如数据库,网络请求,I/O等都是在其他线程中进行的,那么此时就需要在不同线程中进行通信了,而我们最常用的方式就是Handler。
常见的使用方式如下:
public class HandlerTestActivity extends AppCompatActivity {
public static final String TAG = "HandlerTestActivity";
private TextView displayTv; private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what == 42){
displayTv.setText("" + msg.arg1);
}
}
}; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_test);
displayTv = (TextView)findViewById(R.id.handlerDisplayTv); new Thread(new Runnable() {
@Override
public void run() {
int count = 0; while (true) { try {
//模拟耗时操作
TimeUnit.MILLISECONDS.sleep(1000);
}catch (InterruptedException e){
} Message msg = new Message();
msg.what = 42;
msg.arg1 = count++;
mHandler.sendMessage(msg); }
}
}).start();
}
}
实际的效果图:
其实也很简单,就是 每间隔1秒钟,屏幕上的textview显示的数字增加1。
总结一下,Handler,Looper,MessageQueue,Message这些组成了android当中的异步消息处理机制。主要用于不同的线程之间通讯。(虽然我们最常用到的场景就是 发送消息来更新ui)。
而在android的官方sdk文档中,给出的官方demo其实是这样的。
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();
}
}
它的基本原理如下。
1,在某个线程中。首先调用Looper.prepare()方法,来准备一个Looper实例,绑定到了该线程,并维护了一个消息队列(MessageQueue),MessageQueue它里面装的内容就是Message,然后调用 Looper.loop(),这是一个无限循环,它来取出消息队列当中的Message送到Handler进行处理。
2,我们new Handler()过程中,来获得到该线程的Looper。然后在其他线程当中,可以调用Handler实例来发送消息(sendMessage方法等),发送的message被放到了MessageQueue中,然后在Handler所在的线程中,Handler实例再通过handleMessage()等方法来处理 这个message,这样就实现了不同线程当中的通信。
所以问题的关键是 一个Handler实例所在的线程只有唯一的一个Looper来维持无限循环,并且它也维护了唯一的一个消息队列(MessageQueue)来传送Message(导入和取出)。
其实这个原理还是不复杂的,随便一本android入门书上也都会讲到这个问题,(并且肯定比我总结的要通俗易懂和严谨)。不过作为一个程序猿,不仅要知其然,更要知其所以然。深层次的理解了它的原理,不仅便于我们的记忆,也可以让我们使用起来更加得心应手。
所以下面,我就从源码的角度来分析这一过程,从子线程当中的sendMessage()开始,然后又怎么在主线程当中handleMessage()进行接收处理的。
首先来看 Handler.java
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
public Handler() {
this(null, false);
} public Handler(Callback callback, boolean async) {
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;
mAsynchronous = async;
}
当然,Handler的构造方法有好几个。不过我们先沿着主线去分析。
在这个构造方法中,最重要的是这两句。
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
这两句话,说明我们在new Handler时,该线程必须准备好了一个 Looper以及MessageQueue。
然后我们再来看一下Looper.java当中的代码。
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); final MessageQueue mQueue;
final Thread mThread; private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
} public static @Nullable Looper myLooper() {
return sThreadLocal.get();
} 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,那就只能通过prepare()方法了,而在prepare()方法中,注意那句“throw new RuntimeException("Only one Looper may be created per thread")”,这就保证了我们的Looper是唯一的,而通过Handler的构造方法的 “throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()")”。我们也能保证在使用 Handler之前,必须已经调用了 Looper.prepare()来初始化这个唯一的Looper。
而关于这个存储Looper的ThreadLocal对象,其实是一个线程内部的数据存储类,它最大的特点是作用域是限定在该线程内的,也就是说,当它存储数据后(set方法)。只能在该线程内才能获取到数据。 其他线程调用get方法得到的其实会是null(也就是无法得到对应数据)。当然,它内部有一套相应的机制和算法来保证它的作用域是线程内的。
那么从这些代码当中,我们已经明白了,当我们在new Handler()时,必须已经 初始化了唯一的一个Looper和MessageQueue了。
这里插一个问题。我们一直在口口声声的说,在new Handler之前,我们要调用 Looper.prepare()来执行必要的初始化,可是在我们的第一个HandlerTestActivity demo中,根本没调用 这个方法啊。这个原因是因为在UI线程启动时,在ActivityThread中已经帮我们调用过 Looper.prepareMainLooper()方法了(ActivityThread也调用了Looper.loop())。
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
所以我们在 HandlerTestActivity中,不需要调用Looper.prepare()方法了。而在官方的LooperThread demo中,因为不是在ui线程。那就只要你自己来调用 Looper.prepare()和Looper.loop()了.
既然Handler,Looper和MessageQueue我们已经都得到实例对象了,那么下面看看它的发送消息的过程。
/**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
*
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
} /**
* Enqueue a message into the message queue after all pending messages
* before (current time + delayMillis). You will receive it in
* {@link #handleMessage}, in the thread attached to this handler.
*/
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
} /**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
* */
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);
}
通过代码可以看出,几经周折,其实调用的是 enqueueMessage(queue, msg, uptimeMillis)方法,这个方法就是重中之重了。(Handler的sendMessage的方法有几个,但是归根到底,最后还是 调用enqueueMessage方法来进行入队操作)。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
通过代码可以看出来,Looper的enqueueMessage方法最后调用的还是 queue.enqueueMessage方法,也就是MessageQueue本身来进行入队操作。不过在这个方法中,我们还有一句要注意的代码,那就是
msg.target = this;通过这句代码,来把message的 target对象来变成 Handler本身了。这句代码在后面的处理流程中,是有重要作用的。
那么现在,我们就看看 MessageQueue的 enqueueMessage()方法。
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按照时间戳的顺序,插入到这个队列中,值得注意的是,MessageQueue队列的方式是通过链表来实现的。
至于为什么插入的顺序是按照时间来的,因为我们在使用Handler来发送message时,有几个方法本来就是有个延迟时间的。(比如sendMessageDelayed(Message, long)等,默认的发送时间就是当前时间),并且在Message对象中,也有when属性来保存这个时间的。
不过这里的这个时间戳取的是 手机自开机之后的时间,而不是我们经常说的linux那个源自1970年的那个时间。
我们的代码看到这里,其实就应该告一段落了,那就是 我们通过Handler来发送的Message,已经被送入到MessageQuequ了。消息已经在消息队列了。那么怎么取出来进行处理呢。
我们在最初分析原理的时候就说过,Looper.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;
} //省略一些其他代码
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
} if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
} //省略一些其他代码 msg.recycleUnchecked();
}
}
在这个代码当中,可以看到的确是维持了一个死循环,而这个死循环当中, 就是通过 queue.next() 从MessageQueue中取出message,并且如果没有数据的话,就会被阻塞。MessageQueue对象的next()方法这里就不贴出来了,就是一个出队操作(根据message的when属性,当时间到了,就返回对象,并从队列中删除),。
可是取出来的message数据,这里怎么做处理呢。关键是这一句 msg.target.dispatchMessage(msg),
别忘记了我们前面分析时,在message入队之前的一句代码 msg.target = this。所以 这里的 msg.target.dispatchMessage(msg)代码说白了还是在这个Handler的dispatchMessage()来分发处理的message。
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
} /**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
在dispatchMessage方法中,经过两个if判断,最终message的处理,落到了 handleMessage()方法,至于handleMessage()方法,是个空方法,看看方法前面的注释,这个时候就应该恍然大悟了。
我们在 最初的 demo中的处理方式,不就是重写的 handleMessage方法嘛。。。
所以在这里,这个过程算是比较清晰了,Handler对象(在其他线程中)发送的message,被放入了 MessageQueue中,然后通过Looper的无限循环,最后又被取出到Handler所处的线程中进行了处理。
另外,除了我们传统Handler的sendMessage()方法外,还有一种方法来使用Handler。
public class HandlerTest_2_Activity extends AppCompatActivity {
public static final String TAG = "HandlerTest_2_Activity";
private TextView displayTv; private Handler mHandler; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_test);
displayTv = (TextView) findViewById(R.id.handlerDisplayTv); mHandler = new Handler(); new MyThread("子线程").start(); } class MyThread extends Thread{ public MyThread(String name) {
super(name);
} @Override
public void run() {
super.run();
mHandler.post(new Runnable() {
@Override
public void run() {
//操作UI
displayTv.setText("operation from " + Thread.currentThread().getName()); }
});
}
}
}
我们在 MyThread子线程当中,可以直接使用 mHandler.post方法来传递任务到ui线程中进行相关操作。(Thread才是线程,Runnable只是任务,如果不明白这一点的,可以看一下我前面写的几篇多线程的文章)。
我们也分析一下相关的代码:
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*/
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
} private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
看到 sendMessageDelayed()方法,我们就很熟悉了,其实 还是老一套,将message发送到 队列中,不过 getPostMessage()方法是什么呢?
代码就几行,将Runnable包装成Message。并且m.callback = r; 而关于Message,我们可以new,也可以使用obtain方法。后者效率更好一些,因为系统会维护一个Message池进行复用。
不要忘记了我们的dispatchMessage()方法。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
} private static void handleCallback(Message message) {
message.callback.run();
}
此时再读读if判断的第一句,msg.callback是什么,不就是post中传递进去的 Runnable嘛,而这里回调的 handleCallback()方法,转了一圈,最后还是回调到了 Runnable的run()方法了。
所以使用Handler.post(Runnable)算是一种简写了。
不过在dispatchMessage()方法中,还有这样一句判断,mCallback != null ,其实是这样的。
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
public Handler(Callback callback) {
this(callback, false);
}
所以说,处理Message的方式不只一种。
写完这些,Handler这种消息处理机制已经从源码角度分析ok了。理解了原理,今后使用起来才更加的得心应手。原理其实不算复杂,但是真正的细节问题其实也有很多。这篇文章呢,也是我写的第一篇关于分析源码的文章,在这里呢,也给自己立个flag,希望今后能写更多的分析源码的文章。
---
github: https://github.com/yaowen369
Android中Handler的消息处理机制以及源码分析的更多相关文章
- android的消息处理机制(图文+源码分析)—Looper/Handler/Message[转]
from:http://www.jb51.net/article/33514.htm 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.andro ...
- Guava cacha 机制及源码分析
1.ehcahce 什么时候用比较好:2.问题:当有个消息的key不在guava里面的话,如果大量的消息过来,会同时请求数据库吗?还是只有一个请求数据库,其他的等待第一个把数据从DB加载到Guava中 ...
- java中的==、equals()、hashCode()源码分析(转载)
在java编程或者面试中经常会遇到 == .equals()的比较.自己看了看源码,结合实际的编程总结一下. 1. == java中的==是比较两个对象在JVM中的地址.比较好理解.看下面的代码: ...
- Vue3中的响应式对象Reactive源码分析
Vue3中的响应式对象Reactive源码分析 ReactiveEffect.js 中的 trackEffects函数 及 ReactiveEffect类 在Ref随笔中已经介绍,在本文中不做赘述 本 ...
- 2、JDK8中的HashMap实现原理及源码分析
本篇提纲.png 本篇所述源码基于JDK1.8.0_121 在写上一篇线性表的文章的时候,笔者看的是Android源码中support24中的Java代码,当时发现这个ArrayList和Linked ...
- 详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
目录 前言 现象 源码分析 HandlerMethodArgumentResolver与HandlerMethodReturnValueHandler接口介绍 HandlerMethodArgumen ...
- 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...
- Java并发包中Semaphore的工作原理、源码分析及使用示例
1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...
- 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)
每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和 ...
随机推荐
- YII2连表分页
控制器(controller)页面 use \yii\data\Pagination; //引入这个类 public function actionList(){ $data = Clock::fin ...
- hibernate log4j
log4j.rootLogger=warn, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender. ...
- gis电子地图开发公司面临的挑战和机遇
从上个世纪90年代开始电子地图应用就已经收到人们的关注,但是由于时代的局限性和市场经济发展的不成熟.地理信息系统系统的应用并没有得到很好的利用.只有少数的国家机构和军事系统才能够使用这些应用.随着技术 ...
- iScroll的简单使用
今天是2017-1-18,每天进步一点点 今天主要来总结一下我在项目中遇到的关于iScroll的使用问题. 第一个是iscroll的初始化问题. --在页面资源(包括图片)加载完毕后100ms之后初始 ...
- 命令行保存指定目录文件的名字(可包含文件夹文字)到txt文本文件
Microsoft Visual Studio中配置OpenCV解决方案属性的时候, 需要将OpenCV的lib扩展名的库文件添加到属性的依赖列表里面,网上的有些人博客里面直接给出的会有问题(但大多数 ...
- UGUI表情系统&超链接解决方案
最近帮一个同事解决图文混排的问题,发现了一种犀利的UGUI表情系统的解决方案 https://blog.uwa4d.com/archives/Sparkle_UGUI.html 使用重新生成UGUI文 ...
- Python 点滴 IV
[继承示意图] 类是实例的工厂, OOP就是在树中搜索属性,类事实上就是变量名与函数打成的包 . 每一个class语句会生成一个新的类对象 . 每次类调用时,就会生成一个新的实例对象 . 实例自己主动 ...
- Jenkins+tomcat+jdk setup
Jenkins download: http://jenkins-ci.org/ jdk version:jdk-7u45-linux-x64.tar.gz tomcat version:apache ...
- SSH Secure Shell显示serverTomcat后台内容
作为linux小白,仅仅有学一点记一点了: 部署server的时候.常常须要向本地一样查看控制台输出,在linux上能够通过查看日志输出替代,当然也能够通过命令让日志实时显示在命令窗体,这对用惯了wi ...
- 【Notification】屏蔽特定应用的通知提示
须要默认屏蔽特定app的通知提示 设置app是否接收通知的界面 点击每一个条目进去的界面 AppNotificationSettings extends SettingsPreferenceFragm ...