您的位置:首页 > 移动开发 > Android开发

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的构造函数

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;
}


  
prepare方法创建了looper对象,创建looper对象的同时也创建了mesageQueue对象,然后像主线程一样,也包含了loop方法,循环取消息,并且分发给相应的handler对象处理。

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在消息队列当中循环的取数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android