Android消息机制--Handler实现原理

设计Handler 的初衷

Java中有很多种方法实现线程之间的通信,例如:通过synchronized关键字以“上锁”机制实现线程间的通信。多个线程持有同一个对象,他们可以访问同一个共享变量,利用synchronized“上锁”机制,哪个线程拿到了锁,它就可以对共享变量进行修改,从而实现了通信;使用Object类的wait/notify机制,执行代码obj.wait();后这个对象obj所在的线程进入阻塞状态,直到其他线程调用了obj.notify();方法后线程才会被唤醒。

这两个Java多线程通信的方法中都有一个共同的特点,那就是线程的阻塞。利用synchronized机制拿不到锁的线程需要等拿到锁了才会继续执行操作,obj.wait();需要等obj.notify();才会继续执行操作。

虽然Android系统是由Java封装的,但是由于Android系统的特殊性,Google的开发人员对Android线程的设计进行了改造。他们把启动APP时运行的主线程定义为UI线程
UI线程负责所有你能想到的所有的跟界面相关的操作,例如分发绘制事件,分发交互事件等。由于其特殊性Android系统强制要求以下两点:

  • 为保持用户界面流畅UI线程不能被阻塞,如果线程阻塞界面会卡死,若干秒后Android系统抛出ANR。
  • 除UI线程外其他线程不可执行UI操作。

可Java中线程间通信又都是阻塞式方法,所以传统的Java多线程通信方式在Android中并不适用。为此Google开发人员就不得不设计一套UI线程与Worker线程通信的方法。既能实现多线程之间的通信,又能完美解决UI线程不能被阻塞的问题。具体方法有以下几类:

  1. view.post(Runnable action)系列,通过View对象引用切换回UI线程。
  2. activity.runOnUiThread(Runnable action),通过Activity对象引用切换回UI线程。
  3. AsyncTask,内部封装了UI线程与Worker线程切换的操作。
  4. Handler,异步消息处理机制,多线程通信。

相关类

Android有大量的消息驱动方式来进行交互,比如Android的四剑客Activity, Service, Broadcast, ContentProvider的启动过程的交互,都离不开消息机制,Android某种意义上也可以说成是一个以消息驱动的系统。消息机制涉及MessageQueue/Message/Looper/Handler这4个类。

  • Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
  • MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
  • Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
  • Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。

相关方法

发送消息类方法

  1. sendEmptyMessage
    boolean sendEmptyMessage (int what)
    发送一个只有消息标识waht的空消息。该方法适用于不需要传递具体消息只是单独的发通知时。
  2. sendEmptyMessageAtTime
    boolean sendEmptyMessageAtTime (int what, long uptimeMillis)
    在具体指定的时间uptimeMillis发送一个只有消息标识waht的空消息。uptimeMillis为系统开机到当前的时间(毫秒)。
  3. sendEmptyMessageDelayed
    boolean sendEmptyMessageDelayed (int what, long delayMillis)
    在过了delayMillis毫秒之后发送一个只有消息标识waht的空消息。
  4. sendMessage
    boolean sendMessage (Message msg)
    发送一条消息。
  5. sendMessageAtTime
    boolean sendMessageAtTime (Message msg, long uptimeMillis)
    在具体指定的时间uptimeMillis发送一条消息。uptimeMillis为系统开机到当前的时间(毫秒)。
  6. sendMessageDelayed
    boolean sendMessageDelayed (Message msg, long sendMessageDelayed )
    在过了delayMillis毫秒之后发送一条消息。

处理消息类方法

handleMessage
void handleMessage (Message msg)
负责接受消息,所有发送的消息都会返回该方法,注意!必须Override这个方法才能接收消息。

切换线程类方法

  1. post
    boolean post (Runnable r)
    Runnable r 会运行在handler对象被创建的线程上。当我们在UI线程创建了Hnadler对象,在Worker线程调用handler.post()方法时,Runnable就会运行在UI线程中。
  2. postAtTime
    boolean postAtTime (Runnable r, long uptimeMillis)
    在具体指定的时间uptimeMillis让Runnable运行在Handler对象被创建的线程中。
  3. postDelayed
    boolean postDelayed(Runnable r, long delayMillis)
    在具体指定的时间delayMillis之后让Runnable运行在Handler对象被创建的线程中。

Handler实现原理

在这里插入图片描述
Handler接收消息端是线程独立的,不管handler的引用在哪个线程发送消息都会传回自己被实例化的那个线程中。但是Handler本身是多线程共享的引用,它的引用会在别的线程作为消息的发送端,不可能独立存在于某个线程内。所以,Handler需要一个独立存在于线程内部且私有使用的类帮助它接收消息!这个类就是Looper!

Looper

设计Looper的初衷是为了辅助Handler接收消息且仅独立于线程内部。那如何才能实现线程独立的呢?

在Java中提供了ThreadLocal这个工具类来帮助开发者实现线程独立。在谷歌文档中是这么介绍ThreadLocal的:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

For example, the class below generates unique identifiers local to each thread. A thread’s id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls.

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

 import java.util.concurrent.atomic.AtomicInteger;

 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
             @Override 
             protected Integer initialValue() {
                 return nextId.getAndIncrement();
         }
     };

     // Returns the current thread's unique ID, assigning it if necessary
     public static int get() {
         return threadId.get();
     }
 }
 

大概的意思就是:ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。但是ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

Handler 与 Looper 的关联

首先看下如下代码:

public class MainActivity extends Activity {

	private Handler handler1;
	private Handler handler2;
 
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		handler1 = new Handler();
		new Thread(new Runnable() {
			@Override
			public void run() {
				handler2 = new Handler();
			}
		}).start();
	}
 
}

运行程序你会发现,程序崩溃了。为什么?
在这里插入图片描述
报错信息提示是因为不能在没有调用Looper.prepare() 的线程中创建Handler,为什么不调用Looper.prepare()就不行呢?我们看下Handler的无参构造函数:

public Handler() {
    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 = null;
}

如果mLooper对象为空就会抛出我们刚刚碰到的运行时异常信息,那mLooper什么时候会为空呢?我们看下Looper.myLooper()的代码:

public static final Looper myLooper() {
    return (Looper)sThreadLocal.get();
}

这里的sThreadLocal就是前面提到的ThreadLocal类型的变量,底层代码是这样声明变量的:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>()

如果sThreadLocal中有Looper存在就返回Looper,如果没有Looper存在自然就返回空了。sThreadLocal的set方法是在什么时候调用的呢?我们看下Looper.prepare()的代码:

public static final void prepare() {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper());
}

首先判断sThreadLocal中是否已经存在Looper了,如果还没有则创建一个新的Looper设置进去。这就完全解释了为什么我们要先调用Looper.prepare()方法,才能创建Handler对象。同时也可以看出每个线程中最多只会有一个Looper对象。

在这里插入图片描述

为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?

通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者。ActivityThread.main() 代码如下:

//android.app.ActivityThread
public static void main(String[] args) {
  //...
  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
  }
  //...
  Looper.loop();

  throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLooper(); 代码如下:

/**
 * 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();
    }
}

ActivityThread 调用了 Looper.prepareMainLooper() 方法为主线程创建了 Looper ,并且调用了 loop() 方法,所以在主线程中可以直接使用 Handler。 Looper.prepare()在每个线程只允许执行一次,该方法会创建Looper对象,Looper的构造方法中会创建一个MessageQueue对象,再将Looper对象保存到当前线程ThreadLocal。

Looper类型的构造方法如下:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);  //创建MessageQueue对象. 
    mThread = Thread.currentThread();  //记录当前线程.
}

loop() 代码如下:

public static void loop() {
    final Looper me = myLooper();  //获取TLS存储的Looper对象 
    final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列

    Binder.clearCallingIdentity();
    //确保在权限检查时基于本地进程,而不是调用进程。
    final long ident = Binder.clearCallingIdentity();

    for (;;) { //进入loop的主循环方法
        Message msg = queue.next(); //可能会阻塞 
        if (msg == null) { //没有消息,则退出循环
            return;
        }

        //默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
        Printer logging = me.mLogging;  
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg); //用于分发Message 
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        //恢复调用者信息
        final long newIdent = Binder.clearCallingIdentity();
        msg.recycleUnchecked();  //将Message放入消息池 
    }
}

loop()进入循环模式,不断重复下面的操作,直到没有消息时退出循环

  • 读取MessageQueue的下一条Message;
  • 把Message分发给相应的target;
  • 再把分发后的Message回收到消息池,以便重复利用。

消息分发机制

Handler中提供了很多个发送消息的方法,其中除了sendMessageAtFrontOfQueue()方法之外,其它的发送消息方法最终都会辗转调用到sendMessageAtTime()方法中,这个方法的源码如下所示:

public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        msg.target = this;
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }
    else {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }
    return sent;
}

sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。MessageQueue是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在Looper的构造函数中创建的,所以一个Looper只有一个MessageQueue。

入队操作

enqueueMessage()代码如下:

final boolean enqueueMessage(Message msg, long when) {
    if (msg.when != 0) {
        throw new AndroidRuntimeException(msg + " This message is already in use.");
    }
    if (msg.target == null && !mQuitAllowed) {
        throw new RuntimeException("Main thread not allowed to quit");
    }
    synchronized (this) {
        if (mQuiting) {
            RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
            Log.w("MessageQueue", e.getMessage(), e);
            return false;
        } else if (msg.target == null) {
            mQuiting = true;
        }
        msg.when = when;
        Message p = mMessages;
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;
            mMessages = msg;
            this.notify();
        } else {
            Message prev = null;
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
            msg.next = prev.next;
            prev.next = msg;
            this.notify();
        }
    }
    return true;
}

MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的代码的16~31行我们就可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,时间就是uptimeMillis参数。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。

出队操作

Looper.loop()代码如下:

public static final void loop() {
    Looper me = myLooper();
    MessageQueue queue = me.mQueue;
    while (true) {
        Message msg = queue.next(); // might block
        if (msg != null) {
            if (msg.target == null) {
                return;
            }
            if (me.mLogging!= null) me.mLogging.println(
                    ">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what
                    );
            msg.target.dispatchMessage(msg);
            if (me.mLogging!= null) me.mLogging.println(
                    "<<<<< Finished to    " + msg.target + " "
                    + msg.callback);
            msg.recycle();
        }
    }
}

在Looper.loop()方法中调用MessageQueue 的next()将消息从消息队列中取出,内部逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。在loop()中每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,msg.target就是Handler。

dispatchMessage()代码如下:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,所以Handler的handleMessage()最终能够重新拿到当初发出的Message。

Android官方文档给出的标准的异步消息处理线程的写法:

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

为什么使用异步消息处理的方式就可以对UI进行操作了呢?这是由于Handler总是依附于创建时所在的线程,比如我们的Handler是在主线程中创建的,而在子线程中又无法直接对UI进行操作,于是我们就通过一系列的发送消息、入队、出队等环节,最后调用到了Handler的handleMessage()方法中,这时的handleMessage()方法已经是在主线程中运行的,因而我们当然可以在这里进行UI操作了。
整个异步消息处理流程的示意图如下:
在这里插入图片描述
Handler中的post()方法,代码如下所示:

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

post方法最终还是调用sendMessageDelayed()方法去发送一条消息,并且还使用了getPostMessage()方法将Runnable对象转换成了一条消息。

getPostMessage()代码如下:

private final Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

在这个方法中将消息的callback字段的值指定为传入的Runnable对象。在Handler的dispatchMessage()方法中有对callback作判断,如果Message的callback等于null才会去调用handleMessage()方法,否则就调用handleCallback()方法。

handleCallback()代码如下:

private final void handleCallback(Message message) {
    message.callback.run();
}

直接调用了一开始传入的Runnable对象的run()方法。

Activity中的runOnUiThread()方法代码如下:

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

如果当前的线程不等于UI线程(主线程),就去调用Handler的post()方法,否则就直接调用Runnable对象的run()方法。

不管是使用哪种方法在子线程中更新UI,其实背后的原理都是相同的,必须都要借助异步消息处理的机制来实现。

Handler 引起的内存泄露原因以及最佳解决方案

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值