使用Handler中遇到的问题分析

时间:2024-10-06 10:53:38

    • 引言
    • Only one Looper may be created per thread
    • Cant create handler inside thread that has not called Looperprepare
    • 为何主线程中创建handler就没问题
    • handler内存泄漏

引言

相信很多童鞋在使用handler的时候肯定遇到了不少的麻烦吧,比如:
1、Only one Looper may be created per thread;
2、Can’t create handler inside thread that has not called ();
3、handler导致的内存泄漏等。
那么今天就详细分析一下其中的原因,以记录又一次深深的掉入坑里无法自拔的囧态。

Only one Looper may be created per thread

这个问题还是比较好查找原因的,我们在使用的时候如果调用()两次就会出现,通过查看源码来分析:

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));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看到prepare方法中代码非常少,首先是调用()方法去取Looper,如果是null说明没有创建并存储,然后会新建一个Looper对象,然后存入sThreadLocal对象中;如果不是null说明Looper对象已经创建过了,这时候就会抛出Only one Looper may be created per thread异常。可见prepare()方法不能调用两次,如果调用两次就会抛出异常。

Can’t create handler inside thread that has not called ()

意思是不能在没有调用()方法的线程中创建handler,那么继续通过源码分析:

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) {
                (TAG, "The following Handler class should be static or leaks might occur: " +
                    ());
            }
        }

        mLooper = ();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called ()");
        }
        mQueue = ;
        mCallback = callback;
        mAsynchronous = async;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

可见创建handler时,会去调用()方法,而()方法如下:

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
}
  • 1
  • 2
  • 3

上面我们分析过()方法会去创建Looper对象,然后存入sThreadLocal中,而()方法就是去sThreadLocal中去取Looper对象。所以在没有调用()方法的情况下,Looper肯定是没有创建的,而MessageQueue(消息队列)又是在Looper构造中去创建的;同时不断的去MessageQueue(消息队列)中取消息,调用handler的dispatchMessage()方法,都是Looper的loop()方法中去执行的。如果没有创建Looper对象,整个handler消息机制就没有办法正常工作,所以在没有调用()方法(没有创建Looper对象)会抛出上面的异常。

为何主线程中创建handler就没问题?

大家应该会发现,平时使用handler的时候,没有遇到上面所说的问题啊,那到底是为啥呢?

继续分析,看代码:

public static void main(String[] args) {
        (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.
        (false);

        ();

        // Set the reporter for event logging in libcore
        (new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = (());
        (configDir);

        Process.setArgV0("<pre-initialized>");

        ();

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

        if (sMainThreadHandler == null) {
            sMainThreadHandler = ();
        }

        if (false) {
            ().setMessageLogging(new
                    LogPrinter(, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        (Trace.TRACE_TAG_ACTIVITY_MANAGER);
        ();

        throw new RuntimeException("Main thread loop unexpectedly exited");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

从ActivityThread的main方法中,我们可以看到里面调用了Looper 中的()方法,而这个方法中调用了prepare方法去创建Looper对象并存储起来了,并且main方法中还调用了()方法。所以我们平时在主线程中使用不会遇到上述问题,是因为主线程中已经把我们需要做的事情做了。

handler内存泄漏

在平时开发中,应该有多数人使用handler会造成内存泄漏,可能你还没有察觉到。一般我们在activity中使用handler的写法如下:

private Handler handler = new Handler()
 {
      public void handleMessage( msg)
 {
            //doSomething()
        }
 };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

告诉我,你是不是就是这样写的呢?

当使用内部类(包括匿名类)来创建handler的时候,handler对象会隐式地持有这个activity的引用。而handler通常会伴随着一个耗时的后台线程(网络加载图片),这个任务执行完之后,通过消息机制通知handler,然后handler把图片更新到界面。但是,如果这时候任务没执行完用户关闭了activity,那么这个activity就有可能在GC检查时被回收掉,而该线程持有handler的引用,这个handler又持有activity的引用,导致该activity无法被回收掉(内存泄露)。

那么使用handler导致内存泄露的解决方法是什么呢?

  • 使用handler的removeCallbacks()和removeMessages()方法将回调和消息移除,或者使用removeCallbacksAndMessages(null)将所有回调和消息都移除。

  • 将handler声明为静态类。
    将handler声明为静态类就不会跟随外部类创建对象而加载,所以不会持有外部类对象的引用,所以activity可以被回收。由于handler不再持有activity的引用,不能在handler中操作activity中的对象了。所以需要在handler中增加一个对activity的弱引用(WeakReference)。

static class MyHandler extends Handler {
        WeakReference<Activity> mWeakReference;
        public MyHandler(Activity activity) {
            mWeakReference=new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity=();
            if(activity!=null) {
                 //doSomething()
            }
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

相关文章