IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> [Android答答答]Handler是什么? -> 正文阅读

[移动开发][Android答答答]Handler是什么?

前言

1. 为什么要用Handler

我们的都知道,在Android中我们无法在子线程中去更新UI,为什么不能更新呢?我们可以在源码中可以看到

void checkThread() {
	...
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

我们如果同时多个线程都需要去更新UI,那么绘制UI就是一个不可靠的非安全操作。如果选择通过加锁的方式,UI绘制的效率就会变低,影响到界面的显示。所以谷歌就限制了只能主线程才能更新UI,通过handler去更新UI。

概念

1.什么是Handler

handler是通过消息队列,发送传递和处理消息的工具。
主要用于异步消息的处理:当发出一个消息之后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是发送消息和接收消息不是同步的处理。 这种机制通常用来处理相对耗时比较长的操作。

说白了,handler就是个运输消息的工具,将消息发送到需要处理的线程中。

2.什么是MessageQueque

MessageQueue字面意思是消息队列。
队列是一种数据结构,有着先进先出的特点。其特性很适用于像消息生产消费依次顺序的场景。

3.Message是什么

Message就是handler发送与接收消息的实体,承载了数据。

4.Looper又是啥

Looper字面意思是循环的意思,在Android中它就是一个不断循环的线程,消息在这个线程中存储和发送。

原理

1. UI线程是如何创建Looper的

Looper的创建一般是Looper.prepare(),但是像Activity中我们一直没有看到创建的方法就可以直接使用Handler发送消息了,原因是系统已经在ActivityThread的main方法为我们创建Looper了。

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
		...
		//创建Looper
        Looper.prepareMainLooper();
		
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        //开启循环
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

2. Handler是如何发送消息的

我们直接看sendMessage源码,从sendMessage开始,依次调用sendMessageDelayed,sendMessageAtTime,再到队列的enqueueMessage方法。

public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
 public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //handler与message绑定
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最后看队列Queue中的enqueue方法

 boolean enqueueMessage(Message msg, long when) {
      	...
        synchronized (this) {
            ...
            msg.markInUse();
            //标记message加入queue的时间
            msg.when = when;
            //当前待处理的消息
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //添加新的消息
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
            	 ...
            }
        }
        return true;
    }

这个过程就是入队,将消息根据时间顺序指定下一个消息。

出队的过程还是看源码,在Looper的loop循环中,最终找到dispatchMessage方法。

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
        //callback在message的构造方法中初始化或者使用handler.post(Runnable)时候不为空
            handleCallback(msg);
        } else {
        mCallback是一个Callback对象,通过无参的构造方法创建出来的handler
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //当mCallback为空时,直接执行handleMessage将msg发送出去
            handleMessage(msg);
        }
    }

3. Looper没有消息死循环的时候,为什么不会卡死应用

当主线程的MessageQueue没有消息的时候,代码会阻塞在Looper的mQueue的next
方法那里,这个时候主线程会释放cpu资源,进入睡眠模式,直到下个消息到达会再次唤醒主线程。这套机制采用的是linux的pipe/epoll机制。通过往pipe管道中写入数据来唤醒主线程工作。原理类似于I/O,读写是阻塞的,不占用CPU资源。

4. 一个线程有几个handler,又有几个looper

一个线程可以有N个handler,因为可以不断的new出来新的。
一个线程中只有一个Looper。

Looper类创建时在源码中对内存中的Looper进行非空判断,仅限制一个Looper存在。

public static void prepare() {
        prepare(true);
    }


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));
    }

ThreadLocal是用来存储Looper的,在Looper这个类创建的时候就new出来了。ThreadLocal 提供了针对于单独的线程的局部变量,并能够使用 set()、get() 方法对这些变量进行设置和获取,并且能够保证这些变量与其他线程相隔离。通过使用 threadLocal 存储对象,线程和线程之间的彼此的数据就会隔离起来,从而保证了彼此线程的数据安全和独立性。

5. handler线程是如何切换的

线程之间的资源是共享的,也就是任何变量在任何线程都是可以修改的。切换线程的过程就是整个handler的运行过程。
总结来说就是三个步骤
a. 创建一个Looper对象保存在ThreadLocal中,同时持有一个MessageQueue对象
b. 创建Handler对象,获取到Looper和MessageQueue对象。在sendMessage的时候,在不同的线程(子线程)将消息发送到MessageQueue。
c. 主线程(UI线程)通过Looper.loop()无限循环获取MessageQueue中的消息,如果存在就将消息通过dispatchMessage方法最终调用到我们写的handleMessage方法中。这样一整个线程切换的过程就完成了。

6. 为什么handler会有内存泄漏的风险,如何规避

先看张截图,我们一般在Acitivty中new Handler的时候,会出现一个黄色的背景提示。
大致翻译过来就是handler是内部类,会引起内存泄漏。如果是handler的MessagQueue和Looper是子线程就没问题。主线程的话需要定义成静态内部类和使用弱引用。
在这里插入图片描述
那么为什么要这样使用呢,原因就是如果handler在主线程创建,如果发送的消息还在队列中,若此时Activity突然finish了,但是handler中的消息依然需要处理。最关键的就是非静态内部类天然持有外部类的引用,导致activity无法回收,引起内存泄漏。

定义成静态内部类,就不会持有外部类的引用,所以也不会有问题。
除此之外,由于是静态类,在handler中就无法操作activity对象,所以需要在handler中增加一个activty的弱引用。

static class MyHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            text.setText(result);
        }
    }
}

7.如果队列中已存在消息,但需要一些消息紧急提前,该怎么做

这里需要引出一个概念叫同步屏障。线程的消息都是放到同一个MessageQueue中的,取消息是互斥取消息,而且只能从头部取消息,添加消息是按照消息的执行的先后顺序进行的排序,同一时间内的消息如果想要立马执行,就需要一个紧急通道,哪个通道就是同步屏障的概念。
同步屏障顾名思义就是阻碍同步消息,只让异步消息进入。
开启同步屏障的方法: MessageQueue#postSyncBarrier()

未完待续

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-01-17 11:36:04  更:2022-01-17 11:36:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:54:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码