Android的Handler机制原理
2016-07-22 14:58
344 查看
[b]1.Handler是什么?[/b]
在Android中表示一种消息处理机制或者叫消息处理方法,用来循环处理应用程序主线程各种消息,比如UI的更新,按键、触摸消息事件等。
[b]2.为什么Android要用Handler机制[/b]
Android应用程序启动时,系统会创建一个主线程,负责与UI组件(widget、view)进行交互,比如控制UI界面界面显示、更新等;分发事件给UI界面处理,比如按键事件、触摸事件、屏幕绘图事件等,因此,Android主线程也称为UI线程。
由此可知,UI线程只能处理一些简单的、短暂的操作,如果要执行繁重的任务或者耗时很长的操作,比如访问网络、数据库、下载等,这种单线程模型会导致线程运行性能大大降低,甚至阻塞UI线程,如果被阻塞超过5秒,系统会提示应用程序无相应对话框,缩写为ANR,导致退出整个应用程序或者短暂杀死应用程序。
除此之外,单线程模型的UI主线程也是不安全的,会造成不可确定的结果。线程不安全简单理解为:多线程访问资源时,有可能出现多个线程先后更改数据造成数据不一致。比如,A工作线程(也称为子线程)访问某个公共UI资源,B工作线程在某个时候也访问了该公共资源,当B线程正访问时,公共资源的属性已经被A改变了,这样B得到的结果不是所需要的的,造成了数据不一致的混乱情况。
线程安全简单理解为:当一个线程访问功能资源时,对该资源进程了保护,比如加了锁机制,当前线程在没有访问结束释放锁之前,其他线程只能等待直到释放锁才能访问,这样的线程就是安全的。
基于以上原因,Android的单线程模型必须遵守两个规则:
1. 不要阻塞UI线程;
2. 不要在UI线程之外访问UI组件,即不能在子线程访问UI组件,只能在UI线程访问。
因此,Android系统将大部分耗时、繁重任务交给子线程完成,不会在主线程中完成,解决了第一个难题;同时,Android只允许主线程更新UI界面,子线程处理后的结果无法和主线程交互,即无法直接访问主线程,这就要用到Handler机制来解决此问题。基于Handler机制,在子线程先获得Handler对象,该对象将数据发送到主线程消息队列,主线程通过Loop循环获取消息交给Handler处理。
[b]3.Hanlder机制的架构[/b]
msg.target是Handler的一个对象引用,handler对象发送消息暂存到消息队列,Looper取出消息分发给相应的handler处理。
4.Handler源码浅析及其原理
new Handler()时会调用Handler的构造函数
在构造方法中,通过调用
create handler inside thread that has not called Looper.prepare()",意思是:不能在未调用
在得到
这是
到此,Android 消息机制的三个重要角色全部出现了,分别是
一般在代码我们接触比较多的是
这个方法的代码很简单,就是从
在
同样很简单,就是给
现在的问题是,
回答:
为什么这样说呢,看一下
这个方法的代码有点长,不去追究细节,只看整体逻辑。可以看出,在这个方法内部有个死循环,里面通过
UI 线程),下面分析消息的发送及处理流程。
这个方法就是调用
消息插入消息队列后,
首先,如果消息的
如果发送消息时调用
此时在
可以看到是直接调用了
如果在创建
以上便是消息发送与处理的流程,发送时是在子线程,但处理时
HandlerThread是什么
HandlerThread继承了Thread,是一个包含有looper的线程类。正常情况下,除了主线程,工作线程是没有looper的,但是为了像主线程那样也能循环处理消息,Android也自定义一个包含looper的工作线程——HandlerThread类。
HandlerThread的run方法:
prepare方法创建了looper对象,创建looper对象的同时也创建了mesageQueue对象,然后像主线程一样,也包含了loop方法,循环取消息,并且分发给相应的handler对象处理。
Android 中更新 UI 的 4 种方式:
runOnUiThread
handler 的 post
handler 的 sendMessage
View 自身的 post
这里重点说一下 你说Handler的post方法创建的线程和UI线程有什么关系?
可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.
注:产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。
对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中(这句话稍后会进行详细解释),在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。
Post允许把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。详细解释如下:
boolean post(Runnable r):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,立即执行。
boolean postAtTime(Runnable r,long uptimeMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,在特定的时间执行。
boolean postDelayed(Runnable r,long delayMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,延迟delayMills秒执行
void removeCallbacks(Runnable r):从消息队列中移除一个Runnable对象。
Message:
Handler如果使用sendMessage的方式把消息入队到消息队列中,需要传递一个Message对象,而在Handler中,需要重写handleMessage()方法,用于获取工作线程传递过来的消息,此方法运行在UI线程上。
对于Message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用Message.obtain()这个静态的方法或者Handler.obtainMessage()获取。Message.obtain()会从消息池中获取一个Message对象,如果消息池中是空的,才会使用构造方法实例化一个新Message,这样有利于消息资源的利用。并不需要担心消息池中的消息过多,它是有上限的,上限为10个。Handler.obtainMessage()具有多个重载方法,如果查看源码,会发现其实Handler.obtainMessage()在内部也是调用的Message.obtain()。
Handler中,与Message发送消息相关的方法有:
Message obtainMessage():获取一个Message对象。
boolean sendMessage():发送一个Message对象到消息队列中,并在UI线程取到消息后,立即执行。
boolean sendMessageDelayed():发送一个Message对象到消息队列中,在UI线程取到消息后,延迟执行。
boolean sendEmptyMessage(int what):发送一个空的Message对象到队列中,并在UI线程取到消息后,立即执行。
boolean sendEmptyMessageDelayed(int what,long delayMillis):发送一个空Message对象到消息队列中,在UI线程取到消息后,延迟执行。
void removeMessage():从消息队列中移除一个未响应的消息。
关于Runnable的使用:
很多人会认为Handler post进去的就是一个线程,这里要纠正一下,Runnable只是个接口,并不是线程,是要配合Thread 的 start() 方法才能开启有一个心线程。
post进去队列的Runnable实例,当被Looper拿出来执行的时候,只是执行run()方法而已,这里并没有产生新线程,所以Handler是依附UI线程的时候,实现Runnable接口的run()方法的时候,不能进行耗时操作,不然会出现UI卡死。
还有一点队列是先进先出,如果要不断post多个Runnable进去队列,如果Ruannable post进去的时间间隔 delay区别太大,会出现一个总是被执行,而另一个被执行的次数大大减小。
小小地总结一下:
首先执行Looper的prepare()方法,这个方法有两个作用:一是生成Looper对象,而是把Looper对象和当前线程对象形成键值对(线程为键),存放在ThreadLocal当中,然后生成handler对象,调用Looper的myLooper()方法,得到与Handler所对应的Looper对象,这样的话,handler、looper 、消息队列就形成了一一对应的关系,然后执行上面的第三个步骤,即Looper在消息队列当中循环的取数据。
在Android中表示一种消息处理机制或者叫消息处理方法,用来循环处理应用程序主线程各种消息,比如UI的更新,按键、触摸消息事件等。
[b]2.为什么Android要用Handler机制[/b]
Android应用程序启动时,系统会创建一个主线程,负责与UI组件(widget、view)进行交互,比如控制UI界面界面显示、更新等;分发事件给UI界面处理,比如按键事件、触摸事件、屏幕绘图事件等,因此,Android主线程也称为UI线程。
由此可知,UI线程只能处理一些简单的、短暂的操作,如果要执行繁重的任务或者耗时很长的操作,比如访问网络、数据库、下载等,这种单线程模型会导致线程运行性能大大降低,甚至阻塞UI线程,如果被阻塞超过5秒,系统会提示应用程序无相应对话框,缩写为ANR,导致退出整个应用程序或者短暂杀死应用程序。
除此之外,单线程模型的UI主线程也是不安全的,会造成不可确定的结果。线程不安全简单理解为:多线程访问资源时,有可能出现多个线程先后更改数据造成数据不一致。比如,A工作线程(也称为子线程)访问某个公共UI资源,B工作线程在某个时候也访问了该公共资源,当B线程正访问时,公共资源的属性已经被A改变了,这样B得到的结果不是所需要的的,造成了数据不一致的混乱情况。
线程安全简单理解为:当一个线程访问功能资源时,对该资源进程了保护,比如加了锁机制,当前线程在没有访问结束释放锁之前,其他线程只能等待直到释放锁才能访问,这样的线程就是安全的。
基于以上原因,Android的单线程模型必须遵守两个规则:
1. 不要阻塞UI线程;
2. 不要在UI线程之外访问UI组件,即不能在子线程访问UI组件,只能在UI线程访问。
因此,Android系统将大部分耗时、繁重任务交给子线程完成,不会在主线程中完成,解决了第一个难题;同时,Android只允许主线程更新UI界面,子线程处理后的结果无法和主线程交互,即无法直接访问主线程,这就要用到Handler机制来解决此问题。基于Handler机制,在子线程先获得Handler对象,该对象将数据发送到主线程消息队列,主线程通过Loop循环获取消息交给Handler处理。
[b]3.Hanlder机制的架构[/b]
msg.target是Handler的一个对象引用,handler对象发送消息暂存到消息队列,Looper取出消息分发给相应的handler处理。
4.Handler源码浅析及其原理
new Handler()时会调用Handler的构造函数
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; }
在构造方法中,通过调用
Looper.myLooper()获得了
Looper对象。如果
mLooper为空,那么会抛出异常:"Can't
create handler inside thread that has not called Looper.prepare()",意思是:不能在未调用
Looper.prepare()的线程创建
handler。上面的例子并没有调用这个方法,但是却没有抛出异常。其实是因为主线程在启动的时候已经帮我们调用过了,所以可以直接创建
Handler。如果是在其它子线程,直接创建
Handler是会导致应用崩溃的。
在得到
Handler之后,又获取了它的内部变量
mQueue,
这是
MessageQueue对象,也就是消息队列,用于保存
Handler发送的消息。
到此,Android 消息机制的三个重要角色全部出现了,分别是
Handler、
Looper以及
MessageQueue。
一般在代码我们接触比较多的是
Handler,但
Looper与
MessageQueue却是
Handler运行时不可或缺的。
Looper
Handler的构造,其中调用了
Looper.myLooper()方法,下面是它的源码:
public static @NonNull MessageQueue myQueue() { return myLooper().mQueue; }
这个方法的代码很简单,就是从
sThreadLocal中获取
Looper对象。
sThreadLocal是
ThreadLocal对象,这说明
Looper是线程独立的。
在
Handler的构造中,从抛出的异常可知,每个线程想要获得
Looper需要调用
prepare()方法,继续看它的代码:
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)); }
同样很简单,就是给
sThreadLocal设置一个
Looper。不过需要注意的是如果
sThreadLocal已经设置过了,那么会抛出异常,也就是说一个线程只会有一个
Looper。创建
Looper的时候,内部会创建一个消息队列:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
现在的问题是,
Looper看上去很重要的样子,它到底是干嘛的?
回答:
Looper开启消息循环系统,不断从消息队列
MessageQueue取出消息交由
Handler处理。
为什么这样说呢,看一下
Looper的
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 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); 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的
next()方法获取下一条消息,没有获取到会阻塞。如果成功获取新消息,便调用
msg.target.dispatchMessage(msg),
msg.target是
Handler对象(下一节会看到),
dispatchMessage则是分发消息(此时已经运行在
UI 线程),下面分析消息的发送及处理流程。
消息发送与处理
在子线程发送消息时,是调用一系列的sendMessage、
sendMessageDelayed以及
sendMessageAtTime等方法,最终会辗转调用
sendMessageAtTime(Message msg, long uptimeMillis),代码如下:
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); } /** * Enqueue a message at the front of the message queue, to be processed on * the next iteration of the message loop. You will receive it in * {@link #handleMessage}, in the thread attached to this handler. * <b>This method is only for use in very special circumstances -- it * can easily starve the message queue, cause ordering problems, or have * other unexpected side-effects.</b> * * @return Returns true if the message was successfully placed in to the * message queue. Returns false on failure, usually because the * looper processing the message queue is exiting. */ public final boolean sendMessageAtFrontOfQueue(Message msg) { 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, 0); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
这个方法就是调用
enqueueMessage在消息队列中插入一条消息,在
enqueueMessage总中,会把
msg.target设置为当前的
Handler对象。
消息插入消息队列后,
Looper负责从队列中取出,然后调用
Handler的
dispatchMessage方法。接下来看看这个方法是怎么处理消息的:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
首先,如果消息的
callback不是空,便调用
handleCallback处理。否则判断
Handler的
mCallback是否为空,不为空则调用它的
handleMessage方法。如果仍然为空,才调用
Handler自身的
handleMessage,也就是我们创建
Handler时重写的方法。
如果发送消息时调用
Handler的
post(Runnable r)方法,会把
Runnable封装到消息对象的
callback,然后调用
sendMessageDelayed,相关代码如下:
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; }
此时在
dispatchMessage中便会调用
handleCallback进行处理:
private static void handleCallback(Message message) { message.callback.run(); }
可以看到是直接调用了
run方法处理消息。
如果在创建
Handler时,直接提供一个
Callback对象,消息就交给这个对象的
handleMessage方法处理。
Callback是
Handler内部的一个接口:
public interface Callback { public boolean handleMessage(Message msg); }
以上便是消息发送与处理的流程,发送时是在子线程,但处理时
dispatchMessage方法运行在主线程。
HandlerThread是什么
HandlerThread继承了Thread,是一个包含有looper的线程类。正常情况下,除了主线程,工作线程是没有looper的,但是为了像主线程那样也能循环处理消息,Android也自定义一个包含looper的工作线程——HandlerThread类。
HandlerThread的run方法:
public void run(){ mTid=Process.myTid(); Looper.prepare(); synchronized(this){ mLooper=Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid=-1; }
Android 中更新 UI 的 4 种方式:
runOnUiThread
handler 的 post
handler 的 sendMessage
View 自身的 post
这里重点说一下 你说Handler的post方法创建的线程和UI线程有什么关系?
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; }
可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.
注:产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。
对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中(这句话稍后会进行详细解释),在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。
Post允许把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。详细解释如下:
boolean post(Runnable r):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,立即执行。
boolean postAtTime(Runnable r,long uptimeMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,在特定的时间执行。
boolean postDelayed(Runnable r,long delayMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,延迟delayMills秒执行
void removeCallbacks(Runnable r):从消息队列中移除一个Runnable对象。
Message:
Handler如果使用sendMessage的方式把消息入队到消息队列中,需要传递一个Message对象,而在Handler中,需要重写handleMessage()方法,用于获取工作线程传递过来的消息,此方法运行在UI线程上。
对于Message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用Message.obtain()这个静态的方法或者Handler.obtainMessage()获取。Message.obtain()会从消息池中获取一个Message对象,如果消息池中是空的,才会使用构造方法实例化一个新Message,这样有利于消息资源的利用。并不需要担心消息池中的消息过多,它是有上限的,上限为10个。Handler.obtainMessage()具有多个重载方法,如果查看源码,会发现其实Handler.obtainMessage()在内部也是调用的Message.obtain()。
Handler中,与Message发送消息相关的方法有:
Message obtainMessage():获取一个Message对象。
boolean sendMessage():发送一个Message对象到消息队列中,并在UI线程取到消息后,立即执行。
boolean sendMessageDelayed():发送一个Message对象到消息队列中,在UI线程取到消息后,延迟执行。
boolean sendEmptyMessage(int what):发送一个空的Message对象到队列中,并在UI线程取到消息后,立即执行。
boolean sendEmptyMessageDelayed(int what,long delayMillis):发送一个空Message对象到消息队列中,在UI线程取到消息后,延迟执行。
void removeMessage():从消息队列中移除一个未响应的消息。
关于Runnable的使用:
很多人会认为Handler post进去的就是一个线程,这里要纠正一下,Runnable只是个接口,并不是线程,是要配合Thread 的 start() 方法才能开启有一个心线程。
post进去队列的Runnable实例,当被Looper拿出来执行的时候,只是执行run()方法而已,这里并没有产生新线程,所以Handler是依附UI线程的时候,实现Runnable接口的run()方法的时候,不能进行耗时操作,不然会出现UI卡死。
还有一点队列是先进先出,如果要不断post多个Runnable进去队列,如果Ruannable post进去的时间间隔 delay区别太大,会出现一个总是被执行,而另一个被执行的次数大大减小。
小小地总结一下:
首先执行Looper的prepare()方法,这个方法有两个作用:一是生成Looper对象,而是把Looper对象和当前线程对象形成键值对(线程为键),存放在ThreadLocal当中,然后生成handler对象,调用Looper的myLooper()方法,得到与Handler所对应的Looper对象,这样的话,handler、looper 、消息队列就形成了一一对应的关系,然后执行上面的第三个步骤,即Looper在消息队列当中循环的取数据。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories