Android之Handler消息处理机制

时间:2021-09-01 17:50:33

Handler的作用

      Handler消息机制在Android中的应用非常广泛,很多组件的底层实现都是靠Handler来完成的,所以掌握Handler消息机制的原理还是非常重要的。Handler的主要功能有两点:

      1.它可以在不同的线程之间传递消息  

      我们都知道Andorid中规定只有主线程里可以进行UI操作,在其他线程中绝不可以进行与UI相关的操作,否则会报错。还有就是在主线程中也不可以进行耗时操作,否则会阻塞主线程,报ANR。基于以上这两点,我们就可以使用Handler在子线程中处理耗时操作,然后把返回的结果传给主线程,再进行UI方面的更新处理等。

      2.它可以延时传递消息。

      如果我们想要处理一个延时操作,一般可以通过Thread.sleep(times)使线程休眠几秒,然后再处理数据。但是再Android中是绝不能允许在主线程红进行休眠操作阻塞线程的,所以我们可以通过Handler来发送延迟消息的方式实现延时操作。

 

消息处理机制 

      Android的消息处理机制表面上只是使用了Handler,但其实还用到了其他的东西,比如说ThreadLocal,Looper,MessageQuery等,这些组合在一起使用,才构成了整个Android消息机制。

      我们先回顾一下Handler的使用:我们以 “在子线程处理完耗时操作,然后通过Handler发送消息到主线程更新UI”为例。首先要先在主线程创建一个Handler对象,重写它的handlerMessage()方法,处理更新UI的操作。然后我们在子线程进行处理耗时操作,当执行完成后,我们创建一个Message对象,将需要传递的数据存入Message对象中,再调用Handler对象的sendMessage()方法将该Message对象发送出去。之后我们就能在Handler的handlerMessage()方法中接收到该Message对象了,然后取出其中的数据,进行更新UI的操作即可,这样整个过程就算完成了。

      上面说的例子是从子线程发送消息回主线程,然后再主线程中处理消息。如果是通过主线程发送消息到子线程,然后在子线程处理消息,那又应该如何做呢?我们尝试直接在子线程中创建Handler对象,重写handlerMessage()方法,然后在主线程中发送消息,但程序运行后会报如下错误:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

      意思是“不能够在一个没有执行过Looper.prepare()方法的线程中去创建Handler对象”,到这里我们终于能够看到Looper的影子了。那么这里就有一个问题,为什么在主线程中没有要求我们先调用Looper.prepare()方法呢,或者说与普通线程相比,主线程有什么特别的吗?

      其实这是因为在主线程中,系统已经调用过该方法了,所以不需要我们再去手动调用。代码如下,主线程在刚开始创建的时候它的Looper就已经调用了prepareMianLooper()方法,所以我们能直接创建Handler。

public final class ActivityThread { 
   public static void main(String[] args) {

        ...

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

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

 

      下面开始介绍消息机制中几个重要的类。

ThreadLocal

      ThreadLocal类通过泛型可以存储任何类型的对象,它内部主要的方法为set()和get()方法。它通过set()方法存储对象,通过get()方法拿到存储的对象。ThreadLocal类的特点是它会以线程为作用域来存储数据,比如我们创建一个ThreadLocal对象来存储Integer类型数据,首先我们在主线程中给这个ThreadLocal对象赋值为10,在子线程1中给这个对象赋值为20,在子线程2中不进行赋值操作,然后分别打印ThreadLocal中存储的数据的值。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //在主线程中赋值为10
        mThreadLocal.set(10);
        //打印结果
        Log.e("test","Thread*main mThreadLocal:"+mThreadLocal.get());

        new Thread("Thread*1"){
            @Override
            public void run() {
                super.run();
                //在线程1中赋值为20
                mThreadLocal.set(20);
                //打印结果
                Log.e("test","Thread*1 mThreadLocal:"+mThreadLocal.get());
            }
        }.start();
        new Thread("Thread*2"){
            @Override
            public void run() {
                super.run();
                //在线程2中不进行赋值操作,打印结果
                Log.e("test","Thread*2 mThreadLocal:"+mThreadLocal.get());
            }
        }.start();
    }

      最终得到的结果如下:

11-19 23:13:42.422 15945-15945/com.weimore.demo1 E/test: Thread*main mThreadLocal:10
11-19 23:13:42.425 15945-15965/com.weimore.demo1 E/test: Thread*1 mThreadLocal:20
11-19 23:13:42.425 15945-15966/com.weimore.demo1 E/test: Thread*2 mThreadLocal:null

      可见在不同的线程中,对同一个ThreadLoccal对象进行赋值,但最后的结果是不同的。ThreadLocal以线程为作用域来存储对象,保证了对象在不同线程中的数据独立性和唯一性。当然,要想真正了解ThreadLocal是如何做到分线程存储数据的,还是得看set()和get()方法的源码。

      在set()方法中,首先会获得当前线程对象,然后调用getMap()方法,在getMap()方法中其实就是获取当前线程中的成员变量threadlocals,它是一个ThreadLocalMap对象,其实就是以Map的形式存储数据。如果该对象存在,则将数据存入,否则就创建该对象,然后再存入数据。所以通过set()方法,我们知道了,数据其实最终是保存在当前线程的一个成员变量threadlocals中。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

      而get()方法其实就是从获取当前所在线程的threadlocals对象,然后从中取出存储的数据。如果之前没有通过ThreadLocal存储过数据,则返回null。

   public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

      因此,我们知道了,原来通过ThreadLocal存储数据,最终其实存储在线程的成员变量中的,所以我们在不同的线程中取得的数据不是同一个对象,所以赋值过后,得到的结果会不同。

      ThreadLocal类在消息机制中主要是负责存储Looper的,系统通过ThreadLocal来存储和获得Looper对象,从而保证每个线程的Looper是独立且唯一的。

 

MessageQuery

      MessageQuery虽然叫做消息队列,但其实内部是以单链表的方式存储消息的。MessageQuery是在Looper初始化时被创建的,它负责和Looper,Handler一起协作来维持整个消息机制的运作。MessageQuery中重要的方法有两个:enqueueMessage(),next()。

      enqueueMessage()方法的作用是插入一个消息到链表中,当Handler调用sendMessage()方法时会调用enqueueMessage()方法。而next()方法的作用就是循环地从链表中返回和移除消息并交由Handler去处理消息,该方法会Looper的loop()方法中被调用。

 

Handler

      Handler的使用我们已经非常熟悉了,Handler中比较常用的方法就是post()或postDelayed()方法,还有sendMessage()和sendMessageDelayed()方法等,其实这四个方法最后都是调用sendMessageDelayed()方法,而sendMessageDelayed()方法又是调用sendMessageAtTime(方法。在sendMessageAtTime()中我们发现了如下代码:

 public boolean sendMessageAtTime(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);
    }

      可以看到这里调用了enqueueMessage()方法,并将mQueue对象传入其中,可以看到这个mQueue是一个MessgaeQuery对象,那么它是从何而来?难道Handler的内部也有一个MessagerQuery队列?我们查看Handler初始化的方法,发现这个mQueue是从当前线程的Looper中得到的。

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

      那么Handler的enqueueMessage()方法到底做了什么呢?

  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target
= this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }

      我们看它的源码,发现在该方法中Handler将自己存储在了Message对象的成员变量target中,然后调用用了消息队列的enqueueMessage()方法将Message对象传入。前面已经说过,MessageQuery中的enqueueMessage()负责将消息存入消息队列中,而这里就是该方法具体被调用的地方。也就是说我们在使用Handler的sendMessage()方法时,其实是将Handler自身存入了消息中,然后再将消息存入了Looper中的消息队列中。

      Handler中还有一个比较重要的方法就是dispatchMessage()方法。该方法会在Looper的方法中被调用,它的主要作用是将从消息队列中取出的消息发送到Handler的handlerMessage()方法中。如下:

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

      

 

Looper

      Looper算是消息机制的核心类,它负责创建消息队列,以及开启循环。当消息队列中有消息存在时,就将消息从消息队列中取出,并交由Handler处理。因此一个线程中如果想要使用Handler,就必须先创建Looperd对象,否则无法开启消息循环,更别说发送接收消息。又因为一个线程中不能存在多个消息队列,所以每个线程中Looper只允许被创建一次。而主线程中因为已经在内部创建过Looper对象了,所以可以直接使用Handler,如果在主线程再创建Looper实例的化,就会报Looper重复创建的异常。

      Looper类中主要的方法有以下几个:prepare(),getMainLooper(),loop(),myLooper()。

      其中getMainLooper()方法可以获取到主线程的Looper对象,myLooper()方法可以获取到当前线程的Looper对象,我们可以通过对比这两个方法得到的Looper对象是否为同一个对象,从而判断当前线程是否是主线程。

      在Looper.prepare()方法中,主要负责创建Looper对象和创建消息队列。我们找到prepare()方法的源码:

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

      可以看到,首先要判断当前线程是否已拥有Looper对象。这里的sThreadLocal其实就是ThreadLocal类的对象。如果发现ThreadLocal对象在当前线程中已经存储过Looper了,说明Looper被重复创建了,则抛出“only one Looper may be created per thread”异常,如果ThreadLocal中还未存储Looper,则创建Looper,并存储到ThreadLocal中。

      我们再接着看Looper的构造方法:

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

      在Looper的构造方法中会创建一个MessageQuery(消息队列),并将当前线程赋值给mThread变量。这里我们可以知道消息队列确实是由Looper创建的,如果不创建Looper,消息队列就不存在。

      我们接着看Looper的最后一个也是最重要的方法loop()。loop()方法源码如下:

 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

      可以看到在loop()方法中存在一个无限for循环,它会不断的调用MessageQuery的next()方法,从消息队列中取出消息,而在下面还调用了msg.target的diapatchMessage()方法用来处理取出的消息。前面分析Handler时说到过,这个msg.target其实就是Handler自身,所以其实这里就是调用Handler的dispatchMessage()方法。上面也说了Handler的despatchMessage()方法最终会将消息发送到handlerMessage()中去处理。这里要注意的是,我们用Handler发送消息时可能是在别的线程中发送的,但最后消息是在Looper的loop()方法中被取出然后处理的,最后的handlerMessage()方法一定是在Looper所在的线程中被执行的。

 

由Handler引发的内存泄漏

      我们先来看一下平常我们在Activity中创建Handler可能会造成内存泄漏的写法:

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //发送延迟消息
        handler.sendEmptyMessageDelayed(0, 1000 * 6);
    }

}

      像上面这种情况就非常由可能会造成内存泄漏。我们可以看到,上面在Activity中创建Handler,其实是使用了匿名内部类的方法创建的Handler对象。在Java中,非静态内部类(包括匿名内部类)是会持有外部类的引用的,所以这时的Handler就持有了Activity的引用。

       根据前面我们了解的Android的消息机制,明白了在主线程中创建Handler后,Handler发送的消息会被发送到主线程的Looper所创建的消息队列中,这时主线程的MessageQuery就持有了Message的引用,另外由于Message中的target变量其实就是Handler自身,所以Message其实也持有了Handler的引用。这样就导致了也许Handler正在发送或处理延迟消息时,Activity被销毁了,但Activity的引用被Handler所持有,所以造成了内存泄漏。

ActivityThread -> Looper -> MessageQuery -> Message -> Handler ->Activity

      解决这种内存泄漏的方法有几种:

      首先最简单的,就是将 Handler 声明为静态内部类,这样Handler就不会持有Activity的引用了,当然如果你确实需要Activity的引用来处理某些东西,那就使用WeakReference,让Handler持有Activity的弱引用,这样也不会造成内存泄漏。还有一种方法就是在Activity被销毁的时候调用Handler.removeCallbacksAndMessages()方法,移除消息队列中的所有消息和回调,这样就不会使Handler被MessageQuery持有引用,也就不会造成内存泄漏了。