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

news2024/11/24 8:57:01

概述

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>() {
        @Override
        protected 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 DisplayEventReceiver
            implements Runnable {
        private boolean mHavePendingVsync;
        private long mTimestampNanos;
        private int mFrame;

        public FrameDisplayEventReceiver(Looper looper, int vsyncSource) {
            super(looper, vsyncSource);
        }

        @Override
        public 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);
        }

        @Override
        public 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.coloradmin.cn/o/1091953.html

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

相关文章

Step 1 搭建一个简单的渲染框架

Step 1 搭建一个简单的渲染框架 万事开头难。从萌生到自己到处看源码手抄一个mini engine出来的想法&#xff0c;到真正敲键盘去抄&#xff0c;转眼过去了很久的时间。这次大概的确是抱着认真的想法&#xff0c;打开VS从零开始抄代码。不知道能坚持多久呢。。。 本次的主题是搭…

一文让你玩转Linux多进程开发

Linux多进程开发 主要介绍多进程开发时的要点 进程状态转换 进程反应了进程执行的变化。 进程的状态分为三种 ,运行态,阻塞态,就绪态 在五态模型中分为以下几种,新建态&#xff0c;就绪态&#xff0c;运行态&#xff0c;阻塞态,终止态。 运行态&#xff1a;进程占用处理器正在运…

CSS结构选择器的使用

结构选择器 style>ul li:first-child {//选出第一个孩子进行变色background-color: blue;}</style><ul><li>我是第1个孩子</li><li>我是第2个孩子</li><li>我是第3个孩子</li><li>我是第4个孩子</li><li>…

基于linux的基础线程知识大总结

文章目录 1.线程的基础概念认知1.1什么是线程1.2线程的优缺点1.3一些页表知识的额外补充1.4进程和线程的对比 2.线程的基本控制2.1POSIX线程库2.2创建一个新的线程2.3有关线程id的解释和线程栈区的地址空间布局2.4线程终止2.5线程等待2.6线程分离 3.线程间互斥3.1基本概念3.2互…

多城镇信息发布付费置顶公众号开源版开发

多城镇信息发布付费置顶公众号开源版开发 以下是多城镇信息发布付费置顶公众号的功能列表&#xff1a; 信息发布&#xff1a;用户可以在公众号上发布各类信息&#xff0c;如房屋租售、二手物品交易、招聘信息等。 信息置顶&#xff1a;用户可以选择付费将自己的信息置顶在公众…

《PyTorch深度学习实践》第三讲 梯度下降算法

《PyTorch深度学习实践》第三讲 梯度下降算法 问题描述梯度下降问题分析编程实现代码实现效果 随机梯度下降问题分析编程实现代码实现效果 参考资料 问题描述 梯度下降 问题分析 编程实现 代码 import matplotlib.pyplot as plt# 训练集数据 x_data [1.0, 2.0, 3.0] y_data …

C++算法 —— 贪心(1)介绍

文章目录 1、什么是贪心算法2、特点3、学习方向 1、什么是贪心算法 贪心应当是一个策略&#xff0c;通过局部找到最优&#xff0c;来找到全局最优。它把解决问题的过程分为若干步&#xff0c;解决每一步的时候&#xff0c;都选择当前看起来最优的解法&#xff0c;通过这样做希…

Python数据分析实战-实现F检验(附源码和实现效果)

实现功能 F 检验&#xff08;F-test&#xff09;是一种常用的统计方法&#xff0c;用于比较两个或多个样本方差是否存在显著差异。它可以应用于多种场景&#xff0c;其中一些常见的应用场景包括&#xff1a; 方差分析&#xff08;ANOVA&#xff09;&#xff1a;F 检验在方差分…

【软考-中级】系统集成项目管理工程师-进度管理历年案例

持续更新。。。。。。。。。。。。。。。 进度管理历年案例和解析 2023 上 试题二(20分)2023 上 试题二(20分) 问题1(5分) 结合案例: (1)请写出项目关键路径,并计算项目工期。 答案:项目关键路径 ACEFGJKN,项目工期 80 解析(2)如果活动L工期拖延10天,对整个工期是否有影响…

C语言-程序环境和预处理(1)编译、连接介绍以及预处理函数,预处理符号详解及使用说明。

前言 本篇文章讲述了程序的翻译环境和执行环境&#xff0c;编译、连接&#xff0c;预定义符号&#xff0c;#define&#xff0c;#符号和##符号的相关知识。 文章目录 前言1.程序的翻译环境和执行环境2.编译链接2.1 翻译环境2.2 运行环境 3.预处理详解&#xff08;各预处理符号使…

Java之SPI

Java的SPI&#xff08;Service Provider Interface&#xff09;是一种面向接口编程的机制&#xff0c;用于实现组件之间的解耦和扩展。通过SPI机制&#xff0c;我们可以定义接口&#xff0c;并允许第三方提供不同的实现&#xff0c;从而实现可插拔、可扩展的架构。 SPI讲解 它…

Studio One6.5最新中文版安装步骤

在唱歌效果调试当中&#xff0c;我们经常给客户安装的几款音频工作站。第一&#xff0c;Studio One 6是PreSonus公司开发的一款功能强大的音频工作平台&#xff0c;具有丰富的音频处理功能和灵活的工作流程。以下是Studio One6的一些主要特点&#xff1a; 1.多轨录音和编辑&…

ezEIP信息泄露

漏洞描述 ezEIP存在信息泄露漏洞&#xff0c;通过遍历Cookie中的参数值获取敏感信息 漏洞复现 漏洞Url为 /label/member/getinfo.aspx访问时添加Cookie&#xff08;通过遍历获取用户的登录名电话邮箱等信息&#xff09; WHIR_USERINFORwhir_mem_member_pid1;漏洞证明&…

同比增长29.89%,长城汽车9月销售新车超12万辆

10月8日&#xff0c;长城汽车股份有限公司&#xff08;股票代码601633.SH、02333.HK、82333.HK&#xff1b;以下简称“长城汽车”&#xff09;发布2023年9月产销数据。今年9月&#xff0c;长城汽车销售新车121,632辆&#xff0c;同比增长29.89%&#xff0c;1-9月累计销售864,04…

安捷伦E9321A射频传感器

安捷伦E9321A射频传感器 E9321A 是 Agilent 使用的 6 GHz 0.1 瓦射频传感器。电子测试设备传感器测量波形的功率&#xff0c;例如多音和调制射频 (RF) 波形。传感器使用二极管检测器收集高度精确的调制测量值。50 MHz 至 6 GHz 300 kHz 视频带宽 功率范围&#xff1a;-65 至 20…

Android JNI调用流程

文章目录 前言一、JNI是什么二、JNI的优劣三、JNI的开发流程Java调用C函数1、创建声明native方法的Java工程&#xff0c;加载native函数的动态库&#xff0c;生成.h文件2、创建实现C函数的C工程&#xff0c;将本地代码编译成动态库C函数和Java本地方法的隐式映射&#xff08;相…

压缩炸弹,Java怎么防止

一、什么是压缩炸弹&#xff0c;会有什么危害 1.1 什么是压缩炸弹 压缩炸弹(ZIP)&#xff1a;一个压缩包只有几十KB&#xff0c;但是解压缩后有几十GB&#xff0c;甚至可以去到几百TB&#xff0c;直接撑爆硬盘&#xff0c;或者是在解压过程中CPU飙到100%造成服务器宕机。虽然…

11-网络篇-DNS步骤

1.URL URL就是我们常说的网址 https://www.baidu.com/?from1086k https是协议 m.baidu.com是服务器域名 ?from1086k是路径 2.域名 比如https://www.baidu.com 顶级域名.com 二级域名baidu 三级域名www 3.域名解析DNS DNS就是将域名转换成IP的过程 根域名服务器&#xff1a…

python2 paramiko 各种报错解决方案

一、介绍 paramiko是一个基于SSHv2协议的python库&#xff0c;支持以加密和认证的方式进行远程服务器的连接&#xff0c;用于实现远程文件的上传、下载或通过ssh远程执行命令。 paramiko支持Python&#xff08;2.7&#xff0c;3.4&#xff09;版本 paramiko库可直接使用pip …

谈谈C++中模板分离式编译出现的一些问题

什么是分离式编译 通俗的来讲就是将声明和定义分离在不同文件中 一个程序由若干个源文件共同实现&#xff0c;而每个源文件单独编译生成目标文件&#xff0c;最后将所有 目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。 正常函数与模板分离式编译 看代码&…