zoukankan      html  css  js  c++  java
  • Android之Handler消息处理机制

    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持有引用,也就不会造成内存泄漏了。

  • 相关阅读:
    天梯赛练习 L3-011 直捣黄龙 (30分) dijkstra + dfs
    PAT甲级练习 1087 All Roads Lead to Rome (30分) 字符串hash + dijkstra
    天梯赛练习 L3-010 是否完全二叉搜索树 (30分) 数组建树模拟
    天梯赛练习 L3-008 喊山 (30分) bfs搜索
    天梯赛练习 L3-007 天梯地图 (30分) Dijkstra
    1018 Public Bike Management (30分) PAT甲级真题 dijkstra + dfs
    PAT天梯赛练习 L3-004 肿瘤诊断 (30分) 三维BFS
    课堂实验(计算1!+2!+...+100!)
    39页作业第7题
    39页作业(还款年限—月还款额表)
  • 原文地址:https://www.cnblogs.com/weimore/p/7860433.html
Copyright © 2011-2022 走看看