Handler机制原理

333 阅读8分钟

为了避免ANR,我们会通常把 耗时操作放在子线程里面去执行,因为子线程不能更新UI,所以当子线程需要更新的UI的时候就需要借助到Android的消息机制,也就是Handler机制。

1.Android的消息机制概述

1、Handler发送消息仅仅是调用MessageQueue的enqueueMessage向插入一条信息到MessageQueue
2、Looper不断轮询调用MeaasgaQueue的next方法
3、如果发现message就调用handler的dispatchMessage,ldispatchMessage被成功调用,接着调用handlerMessage()

2.Android的消息机制分析

2.1 MessageQueue的工作原理

消息队列,存储一组消息,以队列的形式对外提供插入和删除的工作,采用单列表的数据结构存储消息。
插入消息:通过enqueueMessage(Message msg, long when)实现
读取消息:通过next()实现,通过for(;;)方法无限循环,如果队列中没有消息,next()一直阻塞在这里,当有新消息到来时,next方法会返回这条消息并将其从单链表中移除

2.2 Looper的工作原理

消息轮询,Looper以无限循环的形式去查找是否有新消息,如果有的话就处理,没有就一直等待。线程默认没有Looper,如果需要使用Handler就必须为线程创建Looper。每个线程只有一个Looper。
构造方法:创建一个MessageQueue即消息队列,然后将当前线程的对象保存起来

 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
}

注意: 创建出来一个Handler但是没有创建Looper的话就会报错。

"Can't create handler inside thread that has not called Looper.prepare()"); ,

解决办法就是new Handler的时候加上Looper.prepare();

主要方法

  • 创建
    Looper.prepare() : 为当前线程创建一个Looper
    prepareMainLooper() : 可以在任何地方获取到主线程(ActivityThread)的Looper
  • 开启: Looper.loop() : 开启消息轮询
    loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回null。Looper必须退出,否则loop方法将会无限循环下去。loop方法会调用MessageQueue的next方法来获取新消息,而next方法是一个阻塞操作,当没有消息时,next方法会一直阻塞在那里,这也导致了loop方法一直阻塞字啊那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:msg.garget.dispatchMessage(msg),这里的msg。target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给了dispatchMessage方法处理。但是这里不同的是,Handler的dispatchMessage方法是在创建Handler时所使用的Looper中执行的,遮掩给就成功的将代码逻辑切换到指定的线程中去执行了。
  • 退出
    quit() : 直接退出Looper
    quitSafely() : 设定一个标记,只有当目前已有消息处理完毕之后才会执行退出操作。
    注意: Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false。在子线程中,如果手动创建了Looper,那么在所有事情完成以后应该调用quit来终止消息循环,否则这个子线程就会一直处于等待状态。而如果推出Looper以后,这个额线程就会立刻终止,因此建议不需要的时候终止Looper

2.3Handler的工作原理

发送和处理消息,将一个任务切换到指定的线程去执行。Handler创建时会采用当前线程的Looper来构建内部的消息循环系统。
发送消息:通过send()和post()方法发送消息,其实post方法最终还是调用send()。Handler发送消息的过程仅仅是向消息队列中插入了一条消息,MessageQueue的next方法机会返回这条消息给Looper,Looper收到消息后开始处理了,最终消息由Looper交由Handler处理,即Handler的dispatchMessage方法会被调用,这时Handler进入了处理消息的阶段。
处理消息: 分三种情况

  • 1、如果是post发送来的message,那么就让这个message所持有的Runnable执行run方法,非常简单。 Message的Callback 是一个Runnable对象,Handler的post的重载的函数不管参数多少,肯定都是有Runnable的。
private static void handleCallback(Message message) {
    message.callback.run();
}
  • 2、如果是利用Handler(Callback callback) 构造函数实例化的Handler,也就是构造函数里面传入了一个CallBack的对象,那么就执行这个Callback的handlerMessage。 利用这个接口和Handler的一个构造函数,我们可以这么创建Handler handler=new Handler(callback)来创建Handler;备注写明了这个接口的作用:可以创建一个Handler的实例但是不需要派生Handler的子类。对比我们日常中最经常做的,就是派生一个Handler的子类,复写handleMessage方法,而通过上面的代码,我们有了一种新的创建Handler方式,那就是不派生子类,而是通过Callback来实现。 这种方式非常少用。看一下Handler里面的Callback这个接口的设计
public interface Callback {
    public boolean handleMessage(Message msg);
}
  • 3、如果是send方法发送的,那么就执行handleMessage,这个方法我们非常熟悉了,google的给的备注的也说了,子类必须实现方法以接受这些Message。这也就是我们最常见的最常用的方式了。
/**
  * Subclasses must implement this to receive messages.
  */
public void handleMessage(Message msg) {
}

特殊构造:

public Handler(Looper looper){
    this(looper,null,false);
}

2.4 Message的工作原理

消息对象的实体,Message对象的内部实现是链表,最大长度是50,用于缓存消息对象。Message对象本身存在于一个消息池中,达到重复利用消息对象的目的,以减少消息对象的创建,所以建议使用obtainMessage方法来获取消息对象。

Message message = myHandler.obtainMessage();

2.5 ThreadLoack的工作原理

可以再不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。目的是保证每一个线程只创建唯一一个Looper

3.主线程的消息循环

Android的主线程就是ActivityThread,主线程的入口方法为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。主线程的消息循环开启以后,ActivityThread还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityThread.H,他内部定义了一组消息类型,主要包括了四大组件的启动和停止过程。ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信的方式完成Activity的请求后会调用ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

4.Handler使用内存泄漏问题及解决方案

非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。 非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Handler是这样写的:

public class MainActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // 做相应逻辑
            }
        }
    };
}

也许有人会说,mHandler并未作为静态变量持有Activity引用,生命周期可能不会比Activity长,应该不一定会导致内存泄露呢,显然不是这样的!mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。 通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式。

public class MainActivity extends AppCompatActivity {

    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler = new MyHandler(this);
        start();
    }

    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private static class MyHandler extends Handler {

        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    // 做相应逻辑
                }
            }
        }
    }
}

mHandler通过弱引用的方式持有Activity,当GC执行垃圾回收时,遇到Activity就会回收并释放所占据的内存单元。这样就不会发生内存泄露了。上面的做法确实避免了Activity导致的内存泄露,发送的msg不再已经没有持有Activity的引用了,但是msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息给移除掉。

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}