7

我想我找到了内存泄漏,并想确认我认为 Android 的 Binder 是如何实现的。在这种情况下,我有一个服务和一个活动,每个都在自己的进程中。我创建了一个 AIDL,它允许我通过 ipc 方法将回调对象从 Activity 传递给服务,然后在服务完成请求的任务时调​​用回调。

很长一段时间我都在想:如果我将一个新的回调对象传递给服务并且我没有在我的活动中保留指向回调对象的指针,为什么垃圾收集器不继续在我的活动中收集回调过程?由于这似乎没有发生,JVM 如何知道何时在我的 Activity 中对回调进行垃圾收集。

我想答案是,Binder系统在Activity进程中保存了指向我的Callback的指针,直到Service进程中对应的Callback对象调用了它的finalize()方法,然后再向Activity发送消息释放指针。它是否正确?如果不是,它是如何工作的?

我相信它会导致有趣的情况,如果 Activity 中的回调指向内存密集型的东西,直到收集到服务中的回调才会被收集。如果 Service 的内存不低,它可能很长时间都不会收集回调,并且回调可能只是在 Activity 中建立,直到 Activity 中出现 OutOfMemoryError。

4

2 回答 2

7

尤里说得很对。

我的服务启动一个保存回调的线程,当线程完成其工作时,它调用回调并且线程结束。当回调被调用时,它可能会在我的 Activity 中做一点工作,然后返回,此时我的 Activity 进程中没有指向回调的指针。

但是 Activity 中的回调对象将继续被 Android 的 binder 系统指向,直到 Service 中相应的回调对象被垃圾回收。

如果 Activity 进程中的回调对象支配了其他一些消耗大量内存的对象,那么我在我的 Activity 进程中无缘无故地浪费内存,甚至可能会出现 OutOfMemoryError。解决方案是在我的回调类中创建一个简单的方法,调用destory()它来清空所有回调的字段,并在我完成回调时调用该方法。

如果回调类是非静态内部类,您可能需要考虑将其更改为静态内部类并在构造函数中传入父类,这样您也可以在destory()方法中将其设为空。

这带来了一个有趣的想法,如果非静态内部回调类的父类是一个 Activity,并且在通过 binder 发送回调之后但在回调之前发生配置更改(例如屏幕旋转),那么回调将在执行时指向一个旧的 Activity 对象!

更新:我在 Binder.java 中发现了这段代码,当然它是被禁用的,但如果他们在 Javadocs 中提到这种东西会很好。

    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Binder> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
于 2012-05-19T19:30:23.440 回答
1

如果我正确理解 Binder 是如何工作的,那么在您的情况下,问题如下。对于每个传入的来电,您的服务创建一个单独的线程。当您将对象传递给该线程时,您的 Binder 系统会为该线程创建对象的本地副本。因此,在您的 Service 方法返回结果之前,带有对象副本的线程将继续工作。

要检查这一点,只需尝试查看您的服务进程的线程(在 DDMS 中)。

于 2012-01-26T21:16:01.180 回答