写点什么

Android Handler 从使用到进阶,html5 手机端开发

用户头像
Android架构
关注
发布于: 2021 年 11 月 05 日
  • 构造方法中可以看到 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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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.

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android Handler 从使用到进阶,html5手机端开发