Android Handler 从使用到进阶,html5 手机端开发
构造方法中可以看到 mLooper = Looper.myLooper(); 获取的 mLooper 为 null,就报上面的那个异常了。
public Handler(@Nullable Callback callback, boolean async) {..mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()
" that has not called Looper.prepare()");}..}
3.4 子线程正确的创建
先执行,Looper.prepare(); 初始化 Looper,然后调用 loop()方法,再创建 Handler。
每个线程**Looper.prepare();**只能调用一次,否则会报错。
private Handler handler2;
/**
子线程*/private void thread() {new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();Looper looper = Looper.myLooper();looper.loop();
handler2 = new Handler(looper, new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {return false;}});}}).start();}
4.Message
Message 就是一个存放消息的类,是一个链表结构。
4.1 基本参数
public final class Message implements Parcelable {//用于 handler 标记处理的 public int what;//可以传递的 int 参数 1public int arg1;//可以传递的 int 参数 2public int arg2;//可以传递的 obj 参数 public Object obj;
//执行时间 public long when;//传递的 bundleBundle data;//Message 绑定的 HandlerHandler target;//Handler.post()时传的 callbackRunnable callback;//链表结构 Message next;
4.2 享元模式 obtain()
obtain() 可以重用 Message,减少开销提高性能。
[菜鸟教程-享元模式](
)
/**
Return a new Message instance from the global pool. Allows us to
avoid allocating new objects in many cases.*/public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}
4.3 回收 recycle()
如果发送的延迟消息,或者消息在执行,就会报错,一般我们不用调用 recycle 方法。
/**
Return a Message instance to the global pool.
<p>
You MUST NOT touch the Message after calling this function because it has
effectively been freed. It is an error to recycle a message that is currently
enqueued or that is in the process of being delivered to a Handler.
</p>*/public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "
"is still in use.");}return;}recycleUnchecked();}
Message 用完并不是内存回收,只是把里面的内容清空,等下次复用。
这个链表也不是无限的,最多就 50 个节点 。
private static final int MAX_POOL_SIZE = 50;
@UnsupportedAppUsagevoid recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = UID_NONE;workSourceUid = UID_NONE;when = 0;target = null;callback = null;data = null;
synchronized (sPoolSync) {//最多 50 个 if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}}
5.MessageQueue
这是一个阻塞队列。
队列是在不停的 for 循环的,是一个死循环,那不就一直占用着 cpu?所以就有了 native 的方法,处理休眠唤醒。
5.1 MessageQueue 每个线程只有一个
消息队列每个线程只有一个,跟 Looper 绑定在一起,在分析 Looper 时会一起分析。
5.2 消息入队
前面我们知道 Handler 所有消息入队最后都是调用 enqueueMessage(Message msg, long when) 。
判断插入消息的位置,还判断是否唤醒操作。
boolean enqueueMessage(Message msg, long when) {//handler 为空就报异常 if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}//加锁 synchronized (this) {//消息正在使用会报错 if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}//判断线程是否存活 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) {//如果队列为空,或者消息延迟时间为 0,或者延迟时间小于 mMessage 的,就插入在头部// 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.nextprev.next = msg;}
//是否唤醒// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}
5.3 消息出队
消息出队其实应该放在 Looper.loop() 里面分析更合适,这里先写,后面结合 loop() 一起看会更好。
前面 return null; 看注释的意思是,如果 looper 已经退出和释放,就返回 null。
这里无限循环,就是一定要取到消息,有消息,阻塞时间为消息的执行时间减去当前时间,如果没消息就阻塞, nativePollOnce(ptr, nextPollTimeoutMillis)。
这 next() 方法只有一个地方返回 msg,关注这里就行了。
@UnsupportedAppUsageMessage next() {// Return here if the message loop has already quit and been disposed.// This can happen if the application tries to restart a looper after quit// which is not supported.final long ptr = mPtr;if (ptr == 0) {return null;}
int pendingIdleHandlerCount = -1; // -1 only during first iterationint nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}//没消息取就阻塞 nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {// Try to retrieve the next message. Return if found.final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.target == null) {// Stalled by a barrier. Find the next asynchronous message in the queue.do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());}if (msg != null) {if (now < msg.when) {// Next message is not ready. Set a timeout to wake up when it is ready.//阻塞时间为消息的执行时间减去当前时间 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else {// Got a message.mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);msg.markInUse();//只有这里返回 msgreturn msg;}} else {//没有更多消息,设置为-1,阻塞// No more messages.nextPollTimeoutMillis = -1;}
..}}
5.4 退出
主线程是不能退出。
传入的 safe 处理,分别是移除消息未执行的消息,和移除全部消息。
void quit(boolean safe) {//主线程是不能退出的 if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}
synchronized (this) {if (mQuitting) {return;}mQuitting = true;
//移除消息 if (safe) {removeAllFutureMessagesLocked();} else {removeAllMessagesLocked();}
// We can assume mPtr != 0 because mQuitting was previously false.nativeWake(mPtr);}}
/**
移除全部消息*/private void removeAllMessagesLocked() {Message p = mMessages;while (p != null) {Message n = p.next;p.recycleUnchecked();p = n;}mMessages = null;}
/**
移除未执行的消息,正在运行的等待完成再回收*/private void removeAllFutureMessagesLocked() {final long now = SystemClock.uptimeMillis();Message p = mMessages;if (p != null) {
if (p.when > now) {//如果执行时间还未到,即未执行的消息,移除回收 removeAllMessagesLocked();} else {//等待在执行的消息执行完再回收移除 Message n;for (;;) {n = p.next;if (n == null) {return;}if (n.when > now) {break;}p = n;}p.next = null;do {p = n;n = p.next;p.recycleUnchecked();} while (n != null);}}}
6.Looper
Handler 要负责发送消息,MessageQueue 消息队列存放消息,Looper 就是负责消息循环。
商场的扶手电梯大家应该都知道吧,可以把扶手电梯的电机看成 Looper,一梯一梯看成消息队列 MessageQueue,坐电梯的人看成 Message,就这样不停的循环,把消息送去处理。
6.1 ThreadLocal
**[Android 开发也要掌握的 Java 知识 -ThreadLocal](
)** 可以看下 ThreadLocal 原理。
Looper 就是用到了 ThreadLocal,Looper 内部直接就有一个,还定义成 static,final 了,也就意味着 Android 里面获取的 ThreadLocal 只有这一个。
@UnsupportedAppUsagestatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
6.2 初始化 prepare(),为何只能调用一次
在 prepare()方法可以看到,如果初始化过,就调用就会报错了,所以每个线程最多只有一个 Looper,但 Handler 可以有很多个。
/** 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()}.*/public static void prepare() {prepare(true);}
private static void prepare(boolean quitAllowed) {//每个线程只能有一个 looperif (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}//初始化后设置给 sThreadLocalsThreadLocal.set(new Looper(quitAllowed));}
6.3 绑定当前线程,创建消息对列
创建消息对列,Looper 绑定到当前 Thread。
quitAllowed 消息队列是否可销毁,主线程的是不可销毁的,子线程默认是可销毁。
private Looper(boolean quitAllowed) {//创建消息队列 mQueue = new MessageQueue(quitAllowed);//绑定当前线程 mThread = Thread.currentThread();}
6.4 拿到当前线程的 looper
/**
Return the Looper object associated with the current thread. Returns
null if the calling thread is not associated with a Looper.*/public static @Nullable Looper myLooper() {return sThreadLocal.get();}
6.5 loop()
首先拿到当前线程的 looper,如果没有 prepare()那就是空,报异常。
然后就是不停的循环,取出消息,再处理消息。
[Android 中为什么主线程不会因为 Looper.loop()里的死循环卡死?](
)
/**
Run the message queue in this thread. Be sure to call
{@link #quit()} to end the loop.*/public static void loop() {//拿到当前线程的 looper,如果没有 prepare()那就是空,报异常 final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"
" before this one completed.");}...
for (;;) {//不停的取消息 Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}
..try {//处理消息,msg.target 就是绑定的 handlermsg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}..//回收消息 msg.recycleUnchecked();}}
6.6 退出
退出 Looper 其实就是调用 MessageQueue 的退出,传的 safe 不同而已,那这两段注释说啥。
传 false ,就是说后面发送消息都不会再处理了,发送消息全部都失败,而且该方法不安全,建议使用 quitSafely()。
传 true,跟上面效果差不多,就是 MessageQueue 的逻辑,移除未执行的消息,正在运行的等待完成。
/**
Quits the looper.
<p>
Causes the {@link #loop} method to terminate without processing any
more messages in the message queue.
</p><p>
Any attempt to post messages to the queue after the looper is asked to quit will fail.
For example, the {@link Handler#sendMessage(Message)} method will return false.
</p><p class="note">
Using this method may be unsafe because some messages may not be delivered
before
the looper terminates. Consider using {@link #quitSafely} instead to ensure
that all pending work is completed in an orderly manner.
</p>
@see #quitSafely*/public void quit() {mQueue.quit(false);}
/**
Quits the looper safely.
<p>
Causes the {@link #loop} method to terminate as soon as all remaining messages
in the message queue that are already due to be delivered have been handled.
However pending delayed messages with due times in the future will not be
delivered before the loop terminates.
</p><p>
Any attempt to post messages to the queue after the looper is asked to quit will fail.
For example, the {@link Handler#sendMessage(Message)} method will return false.
评论