Android-Handler源码解析-Looper
admin
2024-01-25 23:14:25
0

成员变量

// Log的TAG
private static final String TAG = "Looper";// 线程本地变量,保证了每个线程仅有唯一的Looper对象。
@UnsupportedAppUsage
static final ThreadLocal sThreadLocal = new ThreadLocal();
// 主线程的Looper,由ActivityThread的main方法内调用Looper.prepareMainLooper()进行创建。
@UnsupportedAppUsage
private static Looper sMainLooper;
// 消息分发状态的观察者,有分发开始、分发异常、分发结束。
private static Observer sObserver;// 消息队列
@UnsupportedAppUsage
final MessageQueue mQueue;
// 创建Looper的线程
final Thread mThread;
// 是否在loop中
private boolean mInLoop;// 日志打印类
@UnsupportedAppUsage
private Printer mLogging;
// Trace追踪标记
private long mTraceTag;// 慢分发阈值时间,如果设置了这个参数,如果消息分发的时间超过这个值,则这个looper将显示一个警告日志。
private long mSlowDispatchThresholdMs;
// 慢交付阈值时间,如果设置了这个参数,如果消息执行(实际交付时刻dispatchStart - post时刻msg.when)的时间超过这个值,则这个looper将显示一个警告日志。
private long mSlowDeliveryThresholdMs;
// 是否慢交付检测,如果消息交付时间超过mSlowDeliveryThresholdMs,则为true。
private boolean mSlowDeliveryDetected;
复制代码

说明:

  1. Looper为什么需要持有MessageQueue,因为Looper轮询的时候需要知道轮询哪个MessageQueue

创建Looper

想要使用Looper,首先要创建Looper,所以我们接下来看下它是如何被创建的。

Looper.prepare()

public static void prepare() {prepare(true);
}private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {// 调用线程已经有Looper了,再进行prepare,则抛出异常(每个线程只能创建一个Looper)。throw new RuntimeException("Only one Looper may be created per thread");}// 创建Looper,并存入到sThreadLocal中。sThreadLocal.set(new Looper(quitAllowed));
}
复制代码

prepare()方法,为静态方法,为调用线程创建一个Looper存入sThreadLocal中,以便后续获取此线程LooperquitAllowed参数传入为ture,以允许退出Looper

说明:

  1. 一个线程,只能调用一次prepare()prepare(boolean)方法,否则抛出异常
  2. 一个线程,只能有一个Looper
  3. prepare()方法创建的Looper允许退出

接下来我们来看一下Looper构造方法

private Looper(boolean quitAllowed) {// 创建消息队列mQueue = new MessageQueue(quitAllowed);// 记录创建线程mThread = Thread.currentThread();
}
复制代码

Looper构造方法,为私有方法,不能通过new创建,参数quitAllowed是否允许退出,创建Looper会创建一个MessageQueue,并记录创建线程

说明:

  1. 一个Looper只有一个MessageQueue只有一个关联Thread

Looper.prepareMainLooper()

@Deprecated
public static void prepareMainLooper() {// 准备Looper,如没有则进行创建,不允许退出。prepare(false);// 同步,保证线程安全。synchronized (Looper.class) {if (sMainLooper != null) {// 已经创建MainLooper了,再准备,则抛出异常。throw new IllegalStateException("The main Looper has already been prepared.");}// 获取调用线程的Looper,为MainLooper。sMainLooper = myLooper();}
}
复制代码

prepareMainLooper()方法,为静态方法,为主线程准备Looper,并传入quitAllowedfalse使其不允许退出,并将其标记apploopermyLooper()相关介绍,看后面的-获取Looper

已被声明@Deprecated(弃用),因为app主线程looper是由Android环境创建的(ActivityThreadmain方法内调用Looper.prepareMainLooper()创建),因此永远不需要自己调用这个函数。

说明:

  1. 多个线程只能调用一次prepareMainLooper()方法,否则抛出异常。
  2. 主线程Looper,是由Android环境创建的(ActivityThreadmain方法),不需要我们关心。
  3. 主线程Looper不允许退出

小结

  1. 创建-非主线程Looper只能通过Looper.prepare()方法创建,它是允许退出的。
  2. 创建-主线程Looper,它是通过Android环境创建ActivityThreadmain方法内调用Looper.prepareMainLooper()创建),它是不允许退出的。
  3. 一个线程,一个Looper,一个MessageQueue

获取Looper

创建完Looper后,便可以获取Looper使用了,所以我们接下来看下它是如何被创建的。

Looper.myLooper()

public static @Nullable Looper myLooper() {return sThreadLocal.get();
}
复制代码

myLooper()方法,为静态方法,返回与当前线程关联的Looper对象。如果调用线程没有Looper关联,则返回null

Looper.getMainLooper()

public static Looper getMainLooper() {synchronized (Looper.class) {return sMainLooper;}
}
复制代码

getMainLooper()方法,为静态方法,返回主线程Looper对象。

小结

  1. 获取Looper,有两个方法:Looper.myLooper()Looper.getMainLooper()
  2. Looper.myLooper()方法,获取调用线程Looper,有可能为null
  3. Looper.getMainLooper()方法,获取主线程Looper,不会为null

轮询Looper

Looper创建好后,需要调用Looper.loop()方法,使其进入消息循环中,所以我们接下来看下它的Looper.loop()方法。

Looper.loop()

public static void loop() {// 获取当前线程Looper对象final Looper me = myLooper();if (me == null) {// Looper对象为空,抛出异常(没有Looper; 这个线程上没有调用Looper.prepare())。throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {// 已经在loop中,再调用loop(),则提示警告(再次loop将使队列中的消息在此消息完成之前被执行)。Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}// 标记在loop中me.mInLoop = true;// 确保这个线程的标识是本地进程的标识,并跟踪标识token实际上是什么。Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// 允许用系统道具(adb命令)覆盖一个阈值,例如:// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride =SystemProperties.getInt("log.looper."+ Process.myUid() + "."+ Thread.currentThread().getName()+ ".slow", 0);// 慢交付检测,恢复默认值。me.mSlowDeliveryDetected = false;// 死循环,遍历消息队列里的消息。for (;;) {// 调用loopOnce方法,进行处理。if (!loopOnce(me, ident, thresholdOverride)) {// loopOnce方法返回false,退出循环,结束loop。return;}}
}
复制代码

loop()方法,为静态方法,内部执行死循环进行轮询,调用loopOnce()方法轮询单个消息loopOnce()方法返回true,则继续轮询下一个消息返回false,则退出循环结束loop

说明:

  1. 当前线程调用Looper.loop()方法前,一定要为当前线程准备Looper(调用Looper.prepare()),否则抛出异常

接下来,我们来看一下loopOnce()方法。

private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {// 获取此Looper的消息队列里面的下个消息,有可能会阻塞线程。Message msg = me.mQueue.next();if (msg == null) {// 没有消息,表示消息队列正在退出(调用quit()方法),直接返回false以退出loop。return false;}// 获取日志打印类,这必须在一个局部变量中,以防UI事件设置logger。final Printer logging = me.mLogging;if (logging != null) {// 打印日志:Dispatching(分发中)logging.println(">>>>> Dispatching to " + msg.target + " "+ msg.callback + ": " + msg.what);}// 获取Looper的全局观察者,使用变量,确保观察者在处理事务时不会更改。final Observer observer = sObserver;// 获取追踪标记final long traceTag = me.mTraceTag;// 获取消息分发阈值long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;// 获取消息交付阈值long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {// 有覆盖的(adb命令传入),则用覆盖的值。slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}// 是否记录慢交付final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);// 是否记录慢分发final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);// 是否需要获取开始时刻final boolean needStartTime = logSlowDelivery || logSlowDispatch;// 是否需要获取结束时刻final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {// 开始追踪Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}// 分发开始时刻final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;// 分发结束时刻final long dispatchEnd;// token,用于Observer回调间传递。Object token = null;if (observer != null) {// 获取token,通知Observer消息分发开始。token = observer.messageDispatchStarting();}// 设置当前线程的Uidlong origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);try {// 通知Message的Handler进行分发消息(重要!!!)msg.target.dispatchMessage(msg);if (observer != null) {// 通知Observer消息分发结束observer.messageDispatched(token, msg);}// 记录分发结束时刻dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {// 通知Observer消息分发异常observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {// 恢复当前线程的UidThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {// 结束追踪Trace.traceEnd(traceTag);}}if (logSlowDelivery) {// 记录慢交付,如果(dispatchStart - msg.when)时间大于阈值,则进行Slog提示警告。if (me.mSlowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");me.mSlowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {me.mSlowDeliveryDetected = true;}}}if (logSlowDispatch) {// 记录慢分发,如果(dispatchEnd - dispatchStart)时间大于阈值,则进行Slog提示警告。showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);}if (logging != null) {// 打印日志:Finished(已完成)logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// 确保在分发过程中线程的标识没有损坏。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();return true;
}
复制代码

loopOnce()方法,为静态方法,为轮询单个消息。它会先获取消息队列下个Message,然后调用Message目标Handler进行分发

说明:

  1. LooperMessageQueue里面没有消息时,则线程进入阻塞,释放CPU,它不会因为没有数据而停止loop(),所以如果此Looper不使用了一定要调用quit()结束循环,它会使MessageQueuenext()方法返回null,从而结束循环
  2. 可通过调用looper.setMessageLogging()指定Looper设置日志打印者,以监听消息状态>>>>> Dispatching to分发开始<<<<< Finished to分发结束,可判断时间差来判断分发是否超时

小结

  1. 轮询Looper前,调用线程一定要有Looper(需调用Looper.prepare()),否则抛出异常
  2. 非主线程Looper,如果不使用了,一定要调用quit()结束循环
  3. MessageQueue里面没有消息时,则线程进入阻塞,释放CPU,它不会因为没有数据而停止loop(),所以如果此Looper不使用了一定要调用quit()结束循环

退出Looper

Looper不使用了,便需要退出,所以我们接下来看下它是如何退出的。

quit()

public void quit() {mQueue.quit(false);
}
复制代码

quit()方法,为不安全退出Looper

说明:

  1. 此方法,导致loop()方法,在不处理消息队列中的任何消息的情况下终止,因为它会删除消息队列所有消息
  2. looper被请求退出后,任何向队列发送消息的尝试都将失败。例如,Handler.sendMessage(Message)方法将返回false
  3. 此方法,可能不安全的,因为在looper终止之前可能无法传递一些消息。考虑使用quitSafely,以确保所有等待的工作都以有序的方式完成

quitSafely()

public void quitSafely() {mQueue.quit(true);
}
复制代码

quitSafely()方法,为安全退出Looper

说明:

  1. 此方法,导致loop()方法,在处理消息队列中的已经到期所有消息不处理消息队列中的未来(当前时刻以后)到期所有消息的情况下终止,因为它会删除消息队列所有未来的消息

小结

  1. 退出Looper,有两个方法:quit()不安全退出、quitSafely()安全退出。
  2. quit()方法,在不处理消息队列中的任何消息的情况下终止(此方法,可能不安全的,因为在looper终止之前可能无法传递一些消息)。
  3. quitSafely()方法,在处理消息队列中的已经到期所有消息不处理消息队列中的未来(当前时刻以后)到期所有消息的情况下终止
  4. looper被请求退出后,任何向队列发送消息的尝试都将失败。例如,Handler.sendMessage(Message)方法将返回false

属性set、get方法

LoopermLoggingmThreadmQueue属性对外提供了setget方法,我们接下来看下它们的实现。

setMessageLogging()

public void setMessageLogging(@Nullable Printer printer) {mLogging = printer;
}
复制代码

设置Looper处理消息控制消息日志记录。如果启用日志消息将在每次消息分发开始结束时写到printer标识目标Handler和消息内容。

getThread()

public @NonNull Thread getThread() {return mThread;
}
复制代码

获取与此looper关联线程

getQueue()

public @NonNull MessageQueue getQueue() {return mQueue;
}
复制代码

获取looper消息队列

其它

Looper.myQueue()

public static @NonNull MessageQueue myQueue() {return myLooper().mQueue;
}
复制代码

获取当前线程关联的MessageQueue对象。此线程必须Looper否则抛出NullPointerException

isCurrentThread()

public boolean isCurrentThread() {return Thread.currentThread() == mThread;
}
复制代码

判断调用线程是否是该looper线程,如果是,则返回true

dump()

public void dump(@NonNull Printer pw, @NonNull String prefix) {pw.println(prefix + toString());mQueue.dump(pw, prefix + "  ", null);
}
复制代码

转储looper状态,以进行调试,调用MessageQueuedump()方法。

相关内容

热门资讯

电视安卓系统哪个品牌好,哪家品... 你有没有想过,家里的电视是不是该升级换代了呢?现在市面上电视品牌琳琅满目,各种操作系统也是让人眼花缭...
安卓会员管理系统怎么用,提升服... 你有没有想过,手机里那些你爱不释手的APP,背后其实有个强大的会员管理系统在默默支持呢?没错,就是那...
安卓系统软件使用技巧,解锁软件... 你有没有发现,用安卓手机的时候,总有一些小技巧能让你玩得更溜?别小看了这些小细节,它们可是能让你的手...
安卓系统提示音替换 你知道吗?手机里那个时不时响起的提示音,有时候真的能让人心情大好,有时候又让人抓狂不已。今天,就让我...
安卓开机不了系统更新 手机突然开不了机,系统更新还卡在那里,这可真是让人头疼的问题啊!你是不是也遇到了这种情况?别急,今天...
安卓系统中微信视频,安卓系统下... 你有没有发现,现在用手机聊天,视频通话简直成了标配!尤其是咱们安卓系统的小伙伴们,微信视频功能更是用...
安卓系统是服务器,服务器端的智... 你知道吗?在科技的世界里,安卓系统可是个超级明星呢!它不仅仅是个手机操作系统,竟然还能成为服务器的得...
pc电脑安卓系统下载软件,轻松... 你有没有想过,你的PC电脑上安装了安卓系统,是不是瞬间觉得世界都大不一样了呢?没错,就是那种“一机在...
电影院购票系统安卓,便捷观影新... 你有没有想过,在繁忙的生活中,一部好电影就像是一剂强心针,能瞬间让你放松心情?而我今天要和你分享的,...
安卓系统可以写程序? 你有没有想过,安卓系统竟然也能写程序呢?没错,你没听错!这个我们日常使用的智能手机操作系统,竟然有着...
安卓系统架构书籍推荐,权威书籍... 你有没有想过,想要深入了解安卓系统架构,却不知道从何下手?别急,今天我就要给你推荐几本超级实用的书籍...
安卓系统看到的炸弹,技术解析与... 安卓系统看到的炸弹——揭秘手机中的隐形威胁在数字化时代,智能手机已经成为我们生活中不可或缺的一部分。...
鸿蒙系统有安卓文件,畅享多平台... 你知道吗?最近在科技圈里,有个大新闻可是闹得沸沸扬扬的,那就是鸿蒙系统竟然有了安卓文件!是不是觉得有...
宝马安卓车机系统切换,驾驭未来... 你有没有发现,现在的汽车越来越智能了?尤其是那些豪华品牌,比如宝马,它们的内饰里那个大屏幕,简直就像...
p30退回安卓系统 你有没有听说最近P30的用户们都在忙活一件大事?没错,就是他们的手机要退回安卓系统啦!这可不是一个简...
oppoa57安卓原生系统,原... 你有没有发现,最近OPPO A57这款手机在安卓原生系统上的表现真是让人眼前一亮呢?今天,就让我带你...
安卓系统输入法联想,安卓系统输... 你有没有发现,手机上的输入法真的是个神奇的小助手呢?尤其是安卓系统的输入法,简直就是智能生活的点睛之...
怎么进入安卓刷机系统,安卓刷机... 亲爱的手机控们,你是否曾对安卓手机的刷机系统充满好奇?想要解锁手机潜能,体验全新的系统魅力?别急,今...
安卓系统程序有病毒 你知道吗?在这个数字化时代,手机已经成了我们生活中不可或缺的好伙伴。但是,你知道吗?即使是安卓系统,...
奥迪中控安卓系统下载,畅享智能... 你有没有发现,现在汽车的中控系统越来越智能了?尤其是奥迪这种豪华品牌,他们的中控系统简直就是科技与艺...