自我理解: 链接
1. 首先有一个Looper对象已经存在,并处于轮询的状态;
相对应的代码,Looper.prepare()-->Looper.loop()
2. Handler发送Message,加入到MQ中
各种发送消息的方法-->equeueMessage()-->MessageQueue.equeueMessage()
3. 轮询取出MQ的队头Message,通过Handler进行回调
MessageQueue.next()-->(handler)msg.target.dispatchMessage()-->handleMessage()[自定义TODO]
Message 【继承Parcelable】
封装了任务携带的参数和处理该任务的Handler
/**
* User-defined message code so that the recipient can identify
* what this message is about. Each {@link Handler} has its own name-space
* for message codes, so you do not need to worry about yours conflicting
* with other handlers.
*/
public int what; //标识位,用来区别消息
/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg1; //用来存储一些整数值,替代setData()存储
/**
* arg1 and arg2 are lower-cost alternatives to using
* {@link #setData(Bundle) setData()} if you only need to store a
* few integer values.
*/
public int arg2; //用来存储一些整数值,替代setData()存储
/**
* An arbitrary object to send to the recipient. When using
* {@link Messenger} to send the message across processes this can only
* be non-null if it contains a Parcelable of a framework class (not one
* implemented by the application). For other data transfer use
* {@link #setData}.
*
* Note that Parcelable objects here are not supported prior to
* the {@link android.os.Build.VERSION_CODES#FROYO} release.
*/
//发送给收件人的任意对象。当使用{@link Messenger}跨进程发送消息时,
//如果它包含框架类的Parcelable(不是应用程序实现的框架类),则它只能是非null。
//对于其他数据传输,请使用{@link #setData}
//2.2版本之前不支持实现Parceable的实体类
public Object obj;
-
Message的创建(2种方法)
- 直接实例化一个Message,然后设置其参数
Message msg = new Message; msg.what = 0; msg.arg0 = 1; ...
- Message.obtain() --
【推荐】
在许多情况下可以避免分配新的对象;避免重复创建Message
- 直接实例化一个Message,然后设置其参数
-
Message注意点:
- 尽量通过Message.obtain()的方式构建Message对象,防止Message的多次创建;
- 仅有int型参数时 最好使用arg1和arg2,减少内存的使用;
Looper:消息通道(使普通线程变成Looper线程)
在Activity中,系统会自动启动Looper对象;而在自定义类中,需要自己手动调用;
下面是Looper线程实现的典型示例(源码中的注释中有写到):
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();
}
}
关于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()}.
*/
"//将当前线程初始化为looper;确保在调用prepare方法之后,调用loop方法;然后结束时调用quit方法;"
public static void prepare() {
prepare(true);
}
'//一个Thread只能有一个Looper对象'
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));
}
//初始化时会创建一个MessageQueue对象和线程
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
关于Looper.loop()系统源码:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
//在此线程中运行消息队列,确保在结束loop后调用quit
public static void loop() {
//获取当前looper线程
final Looper me = myLooper();
//判断是否调用了prepare
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(;;)和while(true) for (;;)指令少,不占用寄存器,而且没有判断跳转'
for (;;) {
// 获取消息队列中的message
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
... log日志[省略]...
try {
'//交给Message对应的Handler处理消息'
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
... [省略]...
msg.recycleUnchecked();
}
}
总结:
1)一个Thread只能有一个Looper对象;
2)Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行;
3)Looper使一个线程变成Looper线程。
MessageQueue:消息队列
根据源码中的注释翻译如下:
保存由{@link Looper}分派的消息列表的低级类。消息不会直接添加到MessageQueue,而是通过与Looper关联的{@link Handler}对象添加。
您可以使用{@link Looper#myQueue()Looper.myQueue()}检索当前线程的MessageQueue。
/**
Low-level class holding the list of messages to be dispatched by a
{@link Looper}. Messages are not added directly to a MessageQueue,
but rather through {@link Handler} objects associated with the Looper.
You can retrieve the MessageQueue for the current thread with
{@link Looper#myQueue() Looper.myQueue()}.
*/
public final class MessageQueue {
...
//获取下一个message方法
Message next(){
...
}
//message添加到消息队列的方法
boolean enqueueMessage(Message msg, long when) {
...
}
}
Handler:消息操作类【重点】
官方注释(抽象的翻译):
Handler允许您发送和处理与线程{@link MessageQueue}关联的{@link Message}和Runnable对象。
每个Handler实例都与一个线程和该线程的消息队列相关联。
当您创建一个新的Handler时,它被绑定到正在创建它的线程的线程/消息队列 - 从那时起,它将消息和runnables传递给该消息队列并在消息出来时执行它们队列。
Handler有两个主要用途:
(1)将消息和runnable安排在将来的某个点上执行;
(2)将要在不同于自己的线程上执行的动作排入队列。
源码分析:
/**
*默认的构造方法会 关联一个looper
*/
public Handler(Callback callback, boolean async) {
...
//关联looper,不能为null
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//直接获取looper的消息队列,
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
handler发送消息:
可传递参数包括Message和Runnable,但即使传递Runnable对象,最终也被处理为Message对象,然后执行sendMessageAtTime()方法
/**
* 在所有待处理消息之后将消息排入消息队列。
* 如果消息成功的加入到MQ中,就返回true
*/
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);
}
handler消息的处理:
dispatchMessage(msg);此方法在Looper.loop()中调用msg.target.dispatchMessage();这里的msg.traget就是指向当前的handler;
然后Handler中调用handleMessage(msg)方法,来触发我们需要实现的具体逻辑。
延伸:
1. Handler内部如何获取到当前线程的Looper?
答:是通过 ThreadLocal
2. 系统为什么不允许子线程中访问UI?
答:Android中的UI控件不是线程安全的,如果在多线程中并发的访问,UI显示状态不可预计。
3. 为何系统不对UI的访问加上锁机制?
答:上锁会让UI访问的逻辑变得复杂,会降低UI访问的效率,也会阻塞某些线程的执行,体现在界面上会显得卡顿运行不畅。