Android Handler/Looper视角看UI线程的原理

概述

Handler/Looper机制是android系统非重要且基础的机制,即使在rtos或者linux操作系统上开发应用框架时,也经常借鉴这个机制。通过该机制机制可以让一个线程循环处理事件,事件处理逻辑即在Handler的handleMessge种。本文建议android8.1源码分析这套机制的实现原理。

Handler/Looper

Handler:顾名思义,处理消息message的类,Handler也可以发送消息。

Looper:顾名思义,一个循环体,这个循环体本质上就是不断取出Queue种的Message,分发给特定的目标,具体分发给哪个目标要具体情况具体分析。并且Looper内部含有一个Thread对象,很好理解,就是Looper这个循环体是在该线程中执行的。

MessageQueue:消息队列,通过Handler发送的Message加入到该队列中,并且被Looper运行在Thread线程中不断取出,又被Handler执行。

执行流程图:

应用程序使用Looper

应用程序使用Looper分为两种情况,主线程和普通线程:

普通线程
  * <p>This is a typical example of the implementation of a Looper thread,* using the separation of {@link #prepare} and {@link #loop} to create an* initial Handler to communicate with the 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();*      }*  }

这段代码在Android源码中非常典型,使用步骤有三:

  1. Looper.prepare创建线程私有的Looper对象。
  2. 创建处理消息的Handler。
  3. Looper.loop开始运作:线程一直循环取消息,处理消息,如果没有消息处理将休眠。

上面代码看似简单,越是简单的代码有时候越是难以理解,我们要理解上面三个步骤是怎么把Thread,MessageQueue,Handler和Looper联系起来的。

Looper.prepare源码
    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));}private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}

可以看到prepare就给调用prepare的当前线程创建一个Looper对象,Looper对象创建的时候,内部新建了MessageQueue和Thread对象,特别注意,mThread是调用prepare的线程

Handler对象创建
  **          mHandler = new Handler() {*              public void handleMessage(Message msg) {*                  // process incoming messages here*              }*          };

可以想象,这里Handler必须要跟前面prepare创建的Looper产生联系才对,这个就要看Handler的构造函数:

public Handler();
public Handler(Callback callback);
public Handler(Looper looper);
public Handler(Looper looper, Callback callback);

我们要看下上面Handler()构造函数:

    public Handler() {this(null, false);}public Handler(Callback callback, boolean async) {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 = callback;mAsynchronous = async;}

 可以看到默认构造函数中非常重要的一句,Looper.myLooper,会取得当前线程的Looper对象,也就是Looper.prepare创建的线程私有的对象,同时Handler's MessageQueue来自瑜当前线程Looper's MessageQueue。

注意:Thread和Handler是1:N关系。比如UI线程就可以有多个Handler对象。

Handler发送和处理消息

按照上面分析,调用mHandler可以发送消息,然后处理消息在该Handler的handleMessage函数,我们看看上述流程是如何实现的。

Looper从MessageQueue中取得一个Message后,首先会调用Handler.dispatchMessage进行消息分发,后者再根据具体的策略将Message分发给相应的责任人源码如下:

 Handler.java:

    /*** Subclasses must implement this to receive messages.*/public void handleMessage(Message msg) {}/*** Handle system messages here.*/public void dispatchMessage(Message msg) {                                                                                                             if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}private static void handleCallback(Message message) {                                                                                                  message.callback.run();}

分发策略:

1. Meesage.callback对象不为空优先分发给message.callback。这种情况对应mHandler.post接口发送的runable对象:这种情况下Message.callback就是post的runnable对象

public final boolean post(Runnable r);
public final boolean postAtTime(Runnable r, long uptimeMillis);public final boolean post(Runnable r)                                                                                                                  
{return  sendMessageDelayed(getPostMessage(r), 0);
}private static Message getPostMessage(Runnable r) {                                                                                                    Message m = Message.obtain();m.callback = r;return m;
}

2.  Handler.mCallback不为空,分发给mCallback.handleMessage

3.  前两个都不存在,则调用handleMessage,比如我们当前普通线程场景,override了这个方法,就会调用我们自己Handler.handleMessage。这种情况主要对应是Handler send系列接口使用:

public boolean sendMessageAtTime(Message msg, long uptimeMillis);
public final boolean sendMessageDelayed(Message msg, long delayMillis);比如如下使用:
Message msg = mHandler.obtainMessage();
mHandler.sendMessageAtTime(msg,xxxx);public final Message obtainMessage()                                                                                                                   
{return Message.obtain(this);
}public static Message obtain(Handler h) {Message m = obtain();m.target = h;return m;
}

 可以看到这种情况下Message.target = mHandler,而Looper在loop中分发代码:

     */public static void loop() {final Looper me = myLooper();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 (;;) {Message msg = queue.next(); // might block...final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();final long end;try {msg.target.dispatchMessage(msg);end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();} finally {if (traceTag != 0) {Trace.traceEnd(traceTag);}}...msg.recycleUnchecked();}}

 loop循环从Looper's MessageQueue取得消息,然后调用Message.target.dispatchMessage,正如上面的分析Message.target = mHandler,所以调用到了Handler.dispatchMessage方法。

总结:

1. Handler.post(runnable),在Handler.dispatchMessage方法由于优先分发给runnable对象,所以这个runnable对象被执行,而不是Handler.handleMessage。

2. 如果使用sendMessage接口,那么Handler.dispatchMessage分发给Handler override的handleMessage接口,也就是普通线程使用代码中Handler's handleMessage函数。

UI线程

 我们知道ActivityThread是主线程的入口点,我们看看android UI线程中Looper的使用源码:

    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类似,也分3个步骤:

1.准备Looper: Looper.prepareMainLooper

2.创建主线程私有的Handler:sMainThreadHandler = thread.getHandler

3.开始工作:Looper.loop

与普通线程的不同点:

1. 普通线程使用Looper.prepare,而主线程需要使用Looper.prepareMainLooper

2.Handler不同:普通线程生成一个与Looper绑定的Handler即可,而主线程是从当前线程总获取

我们分别分析下上面两个不同点

prepareMainLooper
    public static void prepareMainLooper() {                                                                                                               prepare(false);synchronized (Looper.class) {sMainLooper = myLooper();}}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));}

可以看到,prepareMainLooper也使用了prepare,不过参数false代表ui线程不能退出,并且创建的Looper要赋值给sMainLooper,这样其他线程可以通过调用getMainLooper获取该对象。

MainThreadHandler

ActivityThread对象创建时候,会在内部同时生成一个Handler对象:

final H mH = new H();

thread.getHandler获取的就是该mH,也就是说ActivityThread中主线程处理消息的Handler之一就是该Handler mH。

loop

分析loop函数之前,我们提一下一个图形窗口典型的main函数如下:

main() {initialize()//初始化CreateWindow();//创建窗口ShowWindow();while(GetMessage()) {//不断分发处理DispatchMessage();}
}

那么根据前面loop函数的分析,其实UI线程的loop函数也是在循环中不断取消息,处理消息,符合上述模型。

ViewRootImpl

我们知道ViewRootImpl也属于UI线程的范围,因为ViewRootImpl创建就是在UI线程中创建,那么根据Handler创建的默认构造函数我们知道,这个Handler()这个默认的构造函数,适合当前线程的Looper绑定的,那么ViewRootImpl中Handler()形式构建的Handler也是处理UI线程的相关消息。

final ViewRootHandler mHandler = new ViewRootHandler();final class ViewRootHandler extends Handler {public void handleMessage(Message msg) {switch (msg.what) {case MSG_INVALIDATE:((View) msg.obj).invalidate();break;case MSG_INVALIDATE_RECT:final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;info.target.invalidate(info.left, info.top, info.right, info.bottom);info.recycle();break;case MSG_PROCESS_INPUT_EVENTS:mProcessInputEventsScheduled = false;doProcessInputEvents();break;case MSG_DISPATCH_APP_VISIBILITY:handleAppVisibility(msg.arg1 != 0);break;case MSG_DISPATCH_GET_NEW_SURFACE:handleGetNewSurface();break;....
Choreographer

Choreographer也是工作在UI主线程的一个重要的类,跟Vsync有关,我们通过源码看看这个类为什么工作在UI主线程。

看是否是主线程,我们要看下Choreographer中的Handler/Looper是基于哪个Thread来创建,看下Choreographer的构造函数如下:

private Choreographer(Looper looper, int vsyncSource) {mLooper = looper;mHandler = new FrameHandler(looper);                                                                                                               mDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource): null;mLastFrameTimeNanos = Long.MIN_VALUE;mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];for (int i = 0; i <= CALLBACK_LAST; i++) {mCallbackQueues[i] = new CallbackQueue();}}

所以重点就是构建函数中Looper是哪里构建的,Choreographer是个单例类,所以看下其初始化的代码:

    // Thread local storage for the choreographer.private static final ThreadLocal<Choreographer> sThreadInstance =                                                                                      new ThreadLocal<Choreographer>() {@Overrideprotected Choreographer initialValue() {Looper looper = Looper.myLooper();if (looper == null) {throw new IllegalStateException("The current thread must have a looper!");}return new Choreographer(looper, VSYNC_SOURCE_APP);}};

也就是说最终Looper.myLooper获取的是当前线程私有的Looper对象,而Choreographer的getInstance是在public ViewRootImpl(Context context, Display display) 构造函数中调用的,根据ViewRootImpl小节我们知道,ViewRootImpl在主线程构建,所以Choreographer是主线程的

 Vsync信号处理

我们都知道vsync信号处理的实际逻辑是在ui主线程当中,我们看下具体实现,首先要看下vsync信号接受和处理的地方,这个在Choreographer当中:

    private final class FrameDisplayEventReceiver extends DisplayEventReceiverimplements Runnable {private boolean mHavePendingVsync;private long mTimestampNanos;private int mFrame;public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {super(looper, vsyncSource);}@Overridepublic void onVsync(long timestampNanos, int builtInDisplayId, int frame) {...mTimestampNanos = timestampNanos;mFrame = frame;Message msg = Message.obtain(mHandler, this);msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);}@Overridepublic void run() {mHavePendingVsync = false;doFrame(mTimestampNanos, mFrame);}

可以看到接收到vsync信号之后,使用mHandler向该Handler所在的线程发送消息,根据Choreographer小节我们知道,这里mHandler和其Looper是UI线程的,所以上面代码中发送的消息在主线程中处理,而FrameDisplayEventReceiver是个runnable对象,所以最终主线程收到这消息的处理函数是:doFrame函数:

            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);mFrameInfo.markInputHandlingStart();doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);mFrameInfo.markAnimationsStart();doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);mFrameInfo.markPerformTraversalsStart();doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);

vsync信号过来在主线程处理, 最终处理函数是doFrame->doCallbacks,而doCallbacks就是要处理应用已经请求,且记录到queue里面的各种事件,比如应用请求渲染的时候调用invalidate调用栈:

invalidate() : ViewRootImpl.java--->scheduleTraversals() : ViewRootImpl.java--->mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);

最终postCallback的实现:


private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis) {synchronized (mLock) {final long now = SystemClock.uptimeMillis();final long dueTime = now + delayMillis;//将要执行的回调封装成CallbackRecord对象,保存到mCallbackQueues数组中mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);//函数执行时间到if (dueTime <= now) {scheduleFrameLocked(now);} else {//通过异步消息方式实现函数延时执行Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);msg.arg1 = callbackType;msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, dueTime);}}

1.将请求的事件保存在mCallbackQueue,等到vsync信号来到,doFrame中取出执行。

2. 发送MSG_DO_SCHEDULE_CALLBACK消息,最终调用native层的mDisplayEventReceiver.scheduleVsync();请求接收下一次vsync信号

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/107121.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【22】c++设计模式——>外观模式

外观模式定义 为复杂系统提供一个简化接口&#xff0c;它通过创建一个高层接口(外观)&#xff0c;将多个子系统的复杂操作封装起来&#xff0c;以便客户端更容易使用。 简单实现 #include<iostream>// 子系统类 class SubsystemA { public:void operationA() {std::co…

保姆式教程:MAC安装Android studio(包括安装JDK,Android SDK),解决gradle下载慢的问题

文章目录 参考文章安装JDK并配置环境变量安装JDK配置JDK相关的环境变量 Android studio 安装下载Android studiogradle下载慢解决方法 安装Android SDK选择jdk版本安装SDK并配置环境变量 参考文章 原文链接 原文链接 安装JDK并配置环境变量 安装JDK 下载地址 下载后双击安装…

Docker开启远程访问+idea配置docker+dockerfile发布java项目

一、docker开启远程访问 1.编辑docker服务文件 vim /usr/lib/systemd/system/docker.servicedocker.service原文件如下&#xff1a; [Unit] DescriptionDocker Application Container Engine Documentationhttps://docs.docker.com Afternetwork-online.target docker.socke…

【设计模式】十、组合模式

文章目录 案例组合模式基本介绍类图代码 组合模式在 JDK 集合的源码分析组合模式的注意事项和细节 案例 编写程序展示一个学校院系结构&#xff1a;需求是这样&#xff0c;要在一个页面中展示出学校的院系组成&#xff0c;一个学校有多个学院&#xff0c;一个学院有多个系。如…

Kotlin笔记(四):高阶函数

1. 高阶函数 1.1 定义高阶函数 高阶函数和Lambda的关系是密不可分的。一些与集合相关的函数式API的用法&#xff0c;如map、filter函数等,Kotlin的标准函数&#xff0c;如run、apply函数等。这几个函数有一个共同的特点&#xff1a;它们都会要求我们传入一个Lambda表达式作为参…

4.查询用户的累计消费金额及VIP等级

思路分析&#xff1a; &#xff08;1&#xff09;按照user_id及create_date 分组求消费金额total_amount &#xff08;2&#xff09;开窗计算同user_id下的累计销售金额sum(total_amount) over(partition by user_id order by create_date ROWS BETWEEN UNBOUNDED PRECEDING AN…

07测试Maven中依赖的范围,依赖的传递原则,依赖排除的配置

依赖的特性 scope标签在dependencies/dependency标签内,可选值有compile(默认值),test,provided,system,runtime,import compile&#xff1a;在项目实际运行时真正要用到的jar包都是以compile的范围进行依赖 ,比如第三方框架SSM所需的jar包test&#xff1a;测试过程中使用的j…

CustomShapes/自定义形状, CustomCurves/自定义曲线, AnimateableData/数据变化动画 的使用

1. CustomShapes 自定义形状视图 1.1 资源图文件 therock.png 1.2 创建自定义形状视图 CustomShapesBootcamp.swift import SwiftUI/// 三角形 struct Triangle: Shape{func path(in rect: CGRect) -> Path {Path { path inpath.move(to: CGPoint(x: rect.midX, y: rect.mi…

云原生网关可观测性综合实践

作者&#xff1a;钰诚 可观测性 可观测性&#xff08;Observability&#xff09;是指系统、应用程序或服务的运行状态、性能和行为能够被有效地监测、理解和调试的能力。 随着系统架构从单体架构到集群架构再到微服务架构的演进&#xff0c;业务越来越庞大&#xff0c;也越来…

数字孪生在制造运行管理(MOM)的七大应用场景

数字经济时代&#xff0c;数字孪生作为实现各行各业智能化、数字化的重要手段之一&#xff0c;受到了各方的广泛重视。随着各项关键使能技术的不断发展&#xff0c;数字孪生的应用价值有望得到进一步释放。这些关键使能技术包括建模、渲染、仿真、物联网、虚拟调试、可视化等&a…

02_单片机及开发板介绍

单片机简介 单片机&#xff0c;又称为微控制器&#xff08;Microcontroller&#xff09;&#xff0c;是一种集成了微处理器核心、存储器、输入/输出接口及各种功能模块的集成电路芯片。它通常由中央处理器&#xff08;CPU&#xff09;、存储器、输入/输出接口以及各种外设组成&…

文献阅读快速法-ChatPDF

如题&#xff0c;直接提供给大家一款能够快速阅读文档的好工具——iTextMaster。 iTextMaster是一款免费的pdf阅读浏览器&#xff0c;上传pdf文档后等待几秒钟&#xff0c;AI就会自动反馈给用户关于文档的摘要总结。十分的方便且实用。 ChatPDF为您提供简洁的文档摘要。对于那…

【算法|动态规划No.21】leetcode494. 目标和

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

8月19日PMP成绩,预计10月16日公布!附查询入口、流程

PMP的考试成绩一般在考后6-8周即可查询&#xff0c;8月PMP的成绩预计会在北京时间10月16日晚上公布&#xff0c;具体时间以官方公告为准。 如何查询8月考试成绩&#xff1f; 渠道一&#xff1a;收到PMI邮件提醒 当你注册PMI所使用的邮箱收到一封PMI发来的&#xff0c;标题为…

『PyQt5-Qt Designer篇』| 13 Qt Designer中如何给工具添加菜单和工具栏?

13 Qt Designer中如何给工具添加菜单和工具栏? 1 创建默认窗口2 添加菜单栏3 查看和调用1 创建默认窗口 当新创建一个窗口的时候,默认会显示有:菜单栏和状态栏,如下: 可以在菜单栏上右键-移除菜单栏: 可以在菜单栏上右键-移除状态栏: 2 添加菜单栏 在窗口上,右键-创建…

软件测试定位bug方法+定位案例(详解)

1、问题bug定位技巧 首先&#xff0c;作为开发也好&#xff0c;测试也好&#xff0c;定位问题有一个总的思路&#xff0c;而这个思路是和数据的走向一致的。 大致是这样&#xff1a; 用户层面问题 -> Web页面/软件界面 -> 中间件 -> 后端服务 -> 代码 -> 数据…

如何正确维护实验室超声波清洗器?

实验室一直被视为一个严谨而严肃的场所&#xff0c;实验应遵循一定的步骤&#xff0c;使用的设备也经历了详细的选择&#xff0c;如实验室超声波清洗机&#xff0c;其特点远强于一般类型的清洗机。专门负责采购的实验室人员一般对优质服务的实验室超声波清洗机印象深刻&#xf…

故障维修无忧服务:OLED透明拼接屏的专业技术支持与保修服务

OLED透明拼接屏作为未来显示技术的领军者&#xff0c;以其卓越的画质和全方位的优势在市场上备受推崇。 本文将深入探讨OLED透明拼接屏的画质特点和独有的优势&#xff0c;并为您提供选购指南、价格表以及故障维修服务&#xff0c;助您了解并选择最适合的OLED透明拼接屏。 一、…

使用CFimagehost源码搭建无需数据库支持的PHP免费图片托管私人图床

文章目录 1.前言2. CFImagehost网站搭建2.1 CFImagehost下载和安装2.2 CFImagehost网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…

NPM 常用命令(十二)

目录 1、npm unpublish 1.1 使用语法 1.2 描述 2、npm unstar 2.1 使用语法 3、npm update 3.1 使用语法 3.2 描述 3.3 示例 插入符号依赖 波浪号依赖 低于 1.0.0 的插入符号依赖 子依赖 更新全局安装的包 4、npm version 4.1 使用语法 5、npm view 5.1 使用语…