【Android】Handler消息机制

概念

android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑,MessageQueue和Looper的工作过程,这三个实际上是一个整体
Handler的主要作用是将一个任务切换到某个指定的线程中去执行。因为Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,程序会抛出异常。
MessageQueue为消息队列,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作,内部采用单链表的数据结构存储消息
Looper为消息循环,以循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待
每个Handler都会关联一个消息队列,消息队列被封装在Looper中,而每个Looper又会关联一个线程(Looper通过ThreadLocal封装),最终就相当于每个消息队列会关联一个线程

延伸:系统为什么不允许在子线程中访问UI?
这是因为Android的UI控件不是线程安全的,在多线程中并发访问可能会导致UI控件处于不可预期的状态。
那为什么系统不对UI控件的访问加上锁机制呢?
首先加上锁会让UI访问的逻辑变得复杂,其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以采用单线程模型来处理UI操作

Handler工作原理概述

  • Handler创建时会采用当前线程的Looper来构建内部消息循环系统,如果当前线程没有Looper,就会报错
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
如果当前线程没有Looper,则会报错
解决的方法是,为当前线程创建Looper,或者在一个有Looper的线程中创建Handler

创建looper
			//为当前线程创建looper,并且会绑定到ThreadLocal中
            Looper.prepare()
            MyThread1().start()
            //启动消息循环
            Looper.loop()

  • Handler的post方法,最终也是调用send方法,发送消息到Looper中处理,当调用send方法时,会调用MessageQueue的enqueueMessage方法将消息放入消息队列中,然后Looper发现有新消息到来时,就会处理这个消息
  • 由于Looper是运行在创建Handler所在的线程中的,所以Handler中的逻辑会切换到创建Handler所在的线程中执行
  • 每个线程只能创建一个Looper
	//Looper
    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对象存储在ThreadLocal中
它是一个线程内部的数据存储类,通过它可以在指定线程中存储数据,数据存储后,只有在指定线程中可以获取到存储数据,对于其他线程来说则无法获取到数据
Android中的Looper,ActivityThread以及AMS
不同线程中访问同一个ThreadLocal对象,可以正确的存取值,set方法设置值,get方法获取值
底层是维护了一个数组,进行存取

MessageQueue消息队列的工作原理

主要包含两个操作:插入和读取,读取本身会伴随着删除操作。插入对应的是enqueueMessage方法,读取对应的是next方法
内部使用单链表的数据结构维护消息列表
next方法是个死循环方法,当列表中没有消息,则会阻塞,当有新消息到来,next方法会返回这条消息,并将此消息从链表中移除

Looper的工作原理

消息循环,不停的从MessageQueue中查看是否有新消息,如果有,就立刻处理,否则就一直阻塞
looper中封装了MessageQueue,保存了当前的线程对象,使用ThreadLocal进行存储

    private Looper(boolean quitAllowed) {
    	//创建消息队列
        mQueue = new MessageQueue(quitAllowed);
        //保存当前线程
        mThread = Thread.currentThread();
    }

Handler的工作需要Looper,没有Looper会报错,

  • 通过在一个有Looper的线程中创建Handler
		//在一个有Looper的线程中创建
        private val handler: Handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                if (msg.what == 1) {
                    println("MyThread handler")
                }
            }
        }
  • 使用Looper.prepare方法和Looper.loop方法手动创建Looper
			//手动创建Looper
		    Looper.prepare()
            val handler: Handler = Handler()
            Looper.loop()

手动创建Looper时,在不需要使用时,需要调用Looper的quit方法退出Looper,否则Looper持有的子线程就会一直处于等待的状态,如果Looper退出,则这个线程就会立刻终止

Looper中退出的方法是quit和quitSafely,注意主线程的Looper是不能退出的,会抛出异常。quit方法是直接退出,quitSafely方法是先把所有消息处理完后,再退出

Looper中重要的方法loop方法,只有调用了loop方法后,消息循环系统才会真正的起作用

  • loop是一个死循环,当MessageQueue的next方法返回null,才会跳出循环
  • 当调用了Looper.quit方法时,会通知MessageQueue退出,next返回null,loop跳出循环
  • loop会调用MessageQueue中的next方法获取新消息,next也是一个死循环方法,当没有消息时,会阻塞,导致loop方法也阻塞
  • 当有消息时,loop中会调用dispatchMessage,对消息进行处理
public static void loop() {
	...
	...
		//调用了handler的dispatchMessage
		msg.target.dispatchMessage(msg);
        if (observer != null) {
            observer.messageDispatched(token, msg);
        }
        dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}

//handler中的方法,切换到Looper对应的线程中了
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

Handler的工作原理

主要包含消息的发送和接收过程,消息发送可以通过post和send实现,post底层也是调用send

    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) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //调用了MessageQueue的插入消息的方法
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • Handler的消息处理方法dispatchMessage
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

在这里插入图片描述

Handler内存泄漏

子线程处理数据后,通过Handler发送消息到UI线程进行更新,如果这时Activity销毁了,Activity会被移出栈,按道理应该销毁,释放内存。但是如果这时子线程还在工作,仍然持有Handler的引用,Handler又是Activity的内部类,持有外部类的引用,导致Activity对象空间无法释放

  • 在Activity销毁时,移除所有消息
    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
    }
  • 使用静态内部类和弱引用解决
	//定义MyHandler继承Handler,传递要操作的Activity对象
    private class MyHandler(private val reference: WeakReference<Activity>) : Handler() {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            val a = reference.get()
            if (a is MainActivity) {
                a.textView.text = "你好啊"
            }
        }
    }

class MainActivity : AppCompatActivity() {
	private lateinit var myHandler: MyHandler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        myHandler= MyHandler(WeakReference(this))
        button.setOnClickListener { myHandler.sendEmptyMessageDelayed(0, 10000) }
    }
}

静态内部类不会持有外部类的引用
Activity销毁时,会把弱引用也回收了

总结

消息机制的流程

Handler发送消息时,向消息队列MessageQueue队列中插入一条消息,这时MessageQueue中的next方法获取到这条消息,返回给Looper,Looper收到消息后,就开始处理了,在loop方法中调用了Handler中的dispatchMessage方法,切换到了Looper的线程中

  • 创建Handler时,如果当前没有Looper,则会抛出异常
public Handler(@Nullable Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
}

创建Looper时,一个线程只能包含一个Looper
public final class Looper {
    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));
    }
}

  • 调用handler中的send方法,最终调用了enqueueMessage,把消息交给了Looper中的MessageQueue
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //queue是Looper中的queue
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • ActivityThread中的main方法,执行了Looper.loop方法

 public static void loop() {
		for (;;) {
			//死循环中调用了MessageQueue中的next方法
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

			...
			...
			...
			try {
				//调用了Handler中的dispatchMessage方法
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            }

        }
}

执行了MessageQueue中的next方法

    Message next() {
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);
     }

  • 调用了Handler中的dispatchMessage
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

主线程消息循环

Android的主线程是ActivityThread类,程序的入口也是在ActivityThread中,ActivityThread中定义了main方法
main方法中系统通过Looper.prepareMainLooper创建Looper和MessageQueue,并调用Looper.loop开启主线程的消息循环,直到退出应用时,调用了quit方法,线程结束

为什么主线程一直处于死循环状态,但是不会导致主线程ANR异常?

这是因为pipe管道机制和epoll
pipe机制:在没有消息时阻塞线程并进入休眠释放cpu资源,有消息时唤醒线程
Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce() 方法里

  • 当Looper中的loop被调用时,调用了MessageQueue.next的方法,next方法中没有消息时会阻塞,不会产生 ANR 异常, 那是因为此时 messageQueue 中并没有消息,无需处理界面界面更新等操作。 因此主线程处于休眠状态,无需占用 cpu 资源, 而当 messageQueue 中有消息时,,系统会唤醒主线程,来处理这条消息
  Message next() {
  nativePollOnce(ptr, nextPollTimeoutMillis);
  }

在主线程中执行耗时任务为什么会ANR异常呢?

  • 那是因为我们在主线程中进行耗时的操作是属于在这个死循环的执行过程中, 如果我们进行耗时操作, 可能会导致这条消息还未处理完成,后面有接受到了很多条消息的堆积,从而导致了 ANR 异常.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值