[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()

未完待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值