从源码解析-Handler Looper Message Messagequeue关系 解决Handler引起的内存泄漏

本文深入解析Android中的Handler机制,涵盖Looper、MessageQueue的工作原理及内存泄漏问题。

在Android开发中Handler的使用肯定是少不了的,Handler使线程之间的通信变得那么简单,而且这个东西面试的时候也是少不了的一个问题,对它进行总结是有必要的。

Handler是什么,它是Android给我们提供的一套用来更新UI的机制,我们可以用它来发送消息,也可以用它来处理消息。

其实带来一个问题就是Android为什么设计只能通过Handler机制更新UI,最根本的原因是解决多线程并发问题,因为Android中的View是线程不安全的,一个Activity中有多个线程要更新UI,没有锁的话,就会导致页面错乱;加锁的话,又导致响应问题;所以Android就给我们提供了一套更新UI的机制,开发者不用去关心多线程问题,所有更新UI的操作都放在主线程的消息队列中去轮询处理。

说到Handler肯定要讲这三要素,Looper,MessageQueue,Message

Looper:中文意思是作线环的装置,在这里很明显就是循环的装置咯,其实就是一个永动机,不停的从MessageQueue消息队列中取出Message消息,然后传递给Handler,Handler再回调HandleMessage方法去处理,每个线程中只有一个Looper对象。

MessageQueue:从字面意思就可以理解这是一个消息队列。一个线程可以有多个Handler对象,然后多个Handler将消息发送出去,这些消息都保存在一个MessageQueue中,为什么只有一个MessageQueue呢,因为一个线程里只有一个Looper。

Message:消息主体,一个Message对象可以包含很多你想发送的东西。

结合上面的可以得出一个线程只有一个Looper对象,一个Looper对象只有一个MessageQuere对象,一个MessageQueue可以存放很多Message对象。工作流程就是Handler将Message发送到MessageQueu中,然后Looper不断轮询,将消息从队列中取出,又交给Handler处理

讲到这些理论性质的东西,那这些东西是怎么产生的呢,我们知道Android是基于JAVA开发的,Java程序入口是main函数,所以Android程序的入口应该也是Main函数,虽然我们平时看到的都是Activity或者Service的生命周期函数,其实这些都是Android封装的,从内存使用来讲,没有这些概念的,扯远了,当我们启动一个Activity的时候,Android都会创建一个唯一的主线程,也就是main线程,这是在android.app.ActivityThread类里,看看main方法,这里推荐下查看Android源码一个好用的工具Source Insight,不过先得把源码下载下来

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());
        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        Process.setArgV0("<pre-initialized>");

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

 

这个方法里代码不多,首先调用Looper.prepareMainLooper方法,进入android.os.Looper类的这个方法看看,

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

这个方法先调用了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));
 }

这里又碰到了一个ThreadLocal,这又是什么鬼,看看这个变量的定义

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

可以看到ThreadLocal是线程内部的一个数据存储类,通过它可以在指定线程中存储数据,只有在指定线程中可以获取到数据,对于其它线程来说无法获取到,在这里保存Looper对象。

那这个方法就很明显了,先通过get方法获取主线程的Looper对象,默认是空的,然后new一个Looper对象,set到ThreadLocal里保存,我们继续往下看在new Looper的时候做了什么

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

}

可以看到,在Looper的构造方法里实例化了一个消息队列MessageQueue,这也就是前面说的一个Looper对象拥有一个Messagequeue对象。

prepare方法走完后就通过myLooper()方法返回一个Looper对象赋值给sMainLooper

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();

 }

private static Looper sMainLooper;//sMainLooper在Looper类的定义,说明这个对象是常驻内存的

至此ActivityThread类的main方法的Looper.prepareMainLooper方法已经走完了,这一步就给主线程创建了一个且只会有一个Looper对象,并且拥有了一个且只会有一个消息队列MessageQueue。在方法结尾调用了Looper.loop()方法

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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 slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            final long end;
            try {
                msg.target.dispatchMessage(msg);
                end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (slowDispatchThresholdMs > 0) {
                final long time = end - start;
                if (time > slowDispatchThresholdMs) {
                    Slog.w(TAG, "Dispatch took " + time + "ms on "
                            + Thread.currentThread().getName() + ", h=" +
                            msg.target + " cb=" + msg.callback + " msg=" + msg.what);
                }
            }

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

    }

这个方法的运行机制是 

1.通过myLooper()方法获取Looper对象

2.通过Looper获取Messagequeue对象

3.进入一个无限循环,不断的通过MessageQueue的next()方法获取下一个消息Message,

4.通过msg.target.dispatchMessage(msg)将消息分发出去,这个msg是Message对象,target其实是Message对象里的Handler对象

好了,到这里主线程的消息机制初始化完成了,接下来就只用通过Handler去发送消息就行了。

那就要想了,那android.os.Handler是怎么跟Looper或者MessageQueue进行联系的呢,平时在主线程里我们是这么构造一个Handler的实例:

Handler mHandler = new Handler();

那我们先进入Handler的构造方法里去看看

public Handler() {
        this(null, false);

 }

这里调用了另一个构造方法

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;

    }

1.先通过Looper.myLooper()方法获取mLooper;

2.先判断是否为null,我们如果在主线程里这样构造肯定不会为空的,因为这个方法上面介绍了,是程序启动时主线程里初始化的;但是你如果在一个子线程里去这样做,肯定就报空了,需要你自己先调用Looper.prepare(),去为子线程实例化一个Looper对象。

3.然后通过mLooper.mQueue获取消息队列赋值给Handler内部的mQueue

4.这样Handler内部就拥有了MessageQueue和Looper

Handler既然实例化结束了,我们平时发送消息主要有以下几种方式

1. mHandler.sendMessage(msg)

public final boolean sendMessage(Message msg) {
        return sendMessageDelayed(msg, 0);

 }

public final boolean sendMessageDelayed(Message msg, long delayMillis){

      if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }


2. mHandler.sendEmptyMessage(what)

public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);
    }


3. mHandler.sendEmptyMessageDelayed(int what, long delayMillis)

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);

    }

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }


4. mHandler.sendMessageDelayed(Message msg, long delayMillis)

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }


5. mHandler.post(Runnable r)

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }


6. mHandler. postDelayed(Runnable r, long delayMillis)

public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

从这些调用可知,不管对外开放了多少方法,最终都会走到同一个方法去

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

    }

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

    }

方法逻辑就是

1. 将开发者发送过来的消息Message传递到enqueueMessage方法;

2. 然后指定Message的Target为this,其实就是Handler自己;接着把消息推到队列Message中

-------------------------------                       分割线                        ------------------------------------

好了,这里就讲完了我们的主线程如何初始化一个Looper对象和MessageQueue对象;在主线程里怎么使用Handler发送消息走到MessageQueue中;但是总感觉少了点啥,是不是,对了,就是Handler如何去处理Message没有总结。

上面讲到ActivityThread类的Main方法最后调用了Looper.Loop()方法,这个方法里通过msg.target.dispatchMessage(msg)去处理消息,刚发送消息的方法里说了将Message的Target指定为Handler自己,所以这里就将消息传递到Handler的dispatchMessage方法

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

    }

1.假如我们没有指定Message中的callback(这个callback是通过post方法传进来的runnable对象)或者Handler构造方法中没有传CallBack接口,那就调用handleMessage(msg)方法,

/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }

2.我们开发中就重写这个方法就可以去处理消息了。

 

到这里Handler可以告一段落了,用网络上的一张图来总结下流程

Handler之内存泄漏

你没有看错,跟我们天天接触的Handler居然会是个内存杀手,有内存泄漏的情况,那是怎么造成的呢?

先解释下内存泄漏的含义:

Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

原因:非静态内部类持有外部类的引用,导致外部Activity无法被回收

private Handler handler = new Handler()
 {
      public void handleMessage(android.os.Message msg)
     {
            if (msg.what == 1) 
        {
                .......
             }
        }
 };

要知道我们平时是直接在Activity内部创建一个Handler,如上,当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能在Handler的回调中操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。

解决办法:

第一:在Activity的onDestory方法中通过Handler的removeCallback方法移除消息

第二:在关闭Activity的时候停掉线程

第三:将Handler声明成静态类,在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。但是这怎么更新UI呢?这时候需要在静态内部类当中维护一个Activity的弱引用,如下

static class MyHandler extends Handler
    {
        WeakReference<Activity> mWeakReference;
        public MyHandler(Activity activity) 
        {
            mWeakReference=new WeakReference<Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg)
        {
            final Activity activity=mWeakReference.get();
            if(activity!=null)
            {
                if (msg.what == 1)
                {
                    .......
                }
            }
        }
    }

 

WeakReference弱引用:与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了

 

其实Android中的内存泄漏有一部分是由于在Activity中使用了非静态内部类导致的,使用的时候一定要注意,尽量使用静态内部类和弱引用的方法去避免这种问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值