Android进阶;Handler消息机制详解

我们知道,Android里面的线程做了这样的分工:主线程不做耗时操作,子线程不更新UI。


这是因为Android系统是个单线程模型,系统给App分配的进程里,只有一个主线程,主线程是App进程的核心,所有的组件都在主线程里实现、调度和管理,组件的绘制、互动操作和生命周期回调都是主线程处理的,这个线程如果出现阻塞、或多线程死锁、或CPU饥饿,导致事件处理时间过长,甚至没有机会得到处理,系统就会处理为ANR,强制关闭App。


所以主线程不能做耗时操作,耗时操作需要我们在子线程里处理。同时,Android不允许子线程刷新UI,因为Android无法保证多个线程同步操作,这样子线程和主线程之间就需要做协调,比如子线程从网络服务器拿来数据要展示到UI,就需要告知主线程去向UI里刷新数据,这就需要Handler消息机制了。

主线程、Looper和消息队列

Handler消息机制是围绕一个消息队列展开的,系统启动App时,App进程会启动ActivityThread主线程,主线程启动时,会创建一个Looper来循环处理消息,ActivityThread的main函数主要就是让这个Looper做死循环,如果消息循环停止了,App也就停止了:

//ActivityThread源码
Looper.loop();
//主线程会一直loop,如果出了looper,就直接抛异常,关闭app
throw new RuntimeException("Main thread loop unexpectedly exited");

Looper自己会去创建和管理一个消息队列,然后就开始不断地处理队列里的消息,子线程向消息队列里发消息,就可以和主线程通信了。
消息队列为空时,主线程会休眠,释放CPU资源。(主线程大部分时间都在休眠)

但是,Android为了确保主线程安全,Looper的消息队列是ThreadLocal的,禁止其他线程访问,所以子线程不能直接向消息队列里发消息。


这样,就需要在主线程里放一个Handler,Handler在创建时,会引用所在线程的Looper,进而获得Looper中的消息队列,这样,我们就可以用Handler来帮忙处理消息了。


这样一来,处理消息的主力就是Handler了。

Message

为了配合Handler,Message里面有这样几个主要属性:

  1. what:int值,存放消息ID(Send方式)
  2. callback:Runnable对象(Post方式,所以如果消息的callback非空,就直接处理runnable)
  3. target:Handler对象,指向Handler(消息还是丢给Handler处理)
  4. data: Bundle对象,存放业务数据
  5. next:Message对象,存放队列中的下一个Message(链式)

这几个主要的属性在Handler消息机制中非常重要,Handler要使用这几个属性才能正常运转。

Handler

Handler有两个作用:

  1. 发消息,子线程通过Handler,向消息队列发消息
    发消息有两种方式,send一个消息ID(msg.what),或者post一个Runnable(msg.callback),这两种方式最终都是包装为一个Message,通过一个sendMessageAtTime函数,把消息推进队列里。
  2. 处理消息,Looper通过Handler,逐个处理队列中的消息
    Looper在逐个处理消息时,拿到每个msg后,会去执行msg.target.dispatchMessage(msg),这里面的target其实就是Handler,这里会做一个判断,如果msg.callback不为空,说明消息里有Runnable(post进来的),就调用msg.callback.run,整个过程其实就是在Handler里调用Runnable的run函数;
 mHandler.post(new Runnable() {
       @Override
       public void run() {
       }
 });

如果msg.callback为空,说明没有Runnable(send进来的),就调用handlemessage,也就是执行Handler的handlemessage函数;

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

这样,Handler消息机制就能正常运转了。
Handler消息机制多见于主线程UI刷新,不过这并不是它的主业,它其实是用来实现Android线程间通信的,所以只要是跨线程的通信,都需要用到Handler,我们可以自己在子线程里写Looper。
如果我们要在子线程里使用Looper,可以使用HandlerThread,它会帮我们维护一个Looper;如果我们直接用Thread,就需要自己在run函数里做Looper的prepare和loop。

HanderThread

HanderThread就是handler+thread+looper,它有这么几个特点:
1.本质上是一个Thread
2.已经准备好了Looper,可以做Looper循环
3.本身是工作线程,其looper不在UI主线程,可以在handleMessage方法中执行异步任务
4.虽然不能并发处理多任务(因为是一个线程,不是线程池),但是性能损耗小
5.因为已经有Looper,所以可以在线程里创建和使用Handler

其他机制

Android的其他异步处理机制,其实都与Handler机制有关:

  • View.post(Runnable)/View.postDelayed(Runnable, long),这个机制一般也是走主线程的Handler处理,不过,如果View所在的视图树没有attach到window,就会自己维护一个RunQueue,在视图树performTraversals时,把RunQueue里的消息全部取出来,让视图树集中处理掉。
  • Activity.runOnUiThread(Runnable),这个函数其实是也是走的Handler机制,如果当前线程就是UI线程,则立即执行;如果当前线程不是UI线程,就把Runnable发给UI线程的消息队列,排队执行。runOnUiThread的源码大概是这样的:
if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
  • AsyncTask,AsyncTask实际上是个封装了线程池和handler的轻量级异步框架,它虽然用一个线程池做异步处理,但是处理结果总要提交回主线程,实际上就是使用一个Handler(AsyncTask有一个InternalHandler对象)来向主线程发消息(MESSAGE_POST_RESULT),并在主线程处理消息(handleMessage),这就要求把Handler放在主线程里,以便绑定主线程的Looper和消息队列,所以AsyncTask需要在主线程里创建和调用。
    有时候,AsyncTask持有的Activity对象可能会被销毁重建(如屏幕旋转),这就会导致AsyncTask指向的Activity对象无效,新的Activity无法接收AsyncTask的onPostExecute(),出现结果丢失的现象。

Handler的内存泄露

我们使用Handler时,如果用内部类或匿名内部类的方式去使用,就会遇到内部类持有外部对象导致的内存泄露(非静态内部类,在编译后的构造函数中会引用所在的外部类),解决方式有两种:

  1. 使用静态内部Handler类(+弱引用)。
  2. 在组件的onDestroy中,调用handler.removecallback()方法。

同样的,由于AsyncTask封装了handler,也会有同样的内存泄露问题,解决方法也是使用静态内部类+弱引用,或在onDestroy时及时调用asyncTask.cancel()方法,否则就会内存泄露,甚至导致崩溃(AsyncTask找不到要处理的View对象)。

更详细深入的handler通信机制手写视频资料;

1.Handler框架总览
2.4个关键类的一致性问题
3.Android 跨线程通信的深层原理
4.生产者-消费者模式应用
5.ThreadLocal源码解析
6.Handler框架手写实现
7.如何在子线程更新UI?

可以加下面的Android技术进阶群免费获取。

附录;

附录一;Android高级技术大纲

附录二;Android进阶实战技术视频

获取方式;

加Android进阶群;701740775。即可前往免费领取。麻烦备注一下csdn领取资料

猜你喜欢

转载自blog.csdn.net/feiyu1947/article/details/86539460