【问题标题】:possible alternative to static inner classes to prevent memory leaks in android/java?可能替代静态内部类以防止 android/java 中的内存泄漏?
【发布时间】:2017-05-04 09:36:17
【问题描述】:

最近我一直在研究 java/android 中的内存泄漏,几乎所有地方都说我应该使用带有弱引用的静态内部类,而不是匿名类。
所以,在我的 android 应用程序中,我开始这样做,但很快就厌倦了,因为它有很多样板代码......我认为有一个我更喜欢使用的替代解决方案,但我不确定它是在防止内存泄漏方面,静态内部类的有效替代方案。正如我之前所说,我还没有在其他任何地方看到这个解决方案(都说使用静态内部类),所以这就是为什么我不确定我的替代方案是否可行。

我将使用我的应用中的一个简单示例:
我有一个名为 WebClient 的类,它处理异步 Web 请求,它接受一个名为 iCallback 的接口,该接口将服务器的响应返回给调用者,在我的活动中,一旦我得到这个回调,我需要关闭一个对话框,并且可能执行一些活动相关的事情(如触发 onBackPressed() 和 setResult())。
所以这是我创建的静态内部类:

private static class CallBack implements WebClient.ICallback
{
    private WeakReference<ProgressDialog> mProgDiag;
    private WeakReference<BaseActivity> mActivity;

    public CallBack(BaseActivity activity, ProgressDialog progDiag)
    {
        this.mProgDiag = new WeakReference<>(progDiag);
        this.mActivity = new WeakReference<>(activity);
    }

    @Override
    public void onCallback(String data)
    {
        String responseAsString = Utils.extractStringFromResponse(...);

        final BaseActivity parentActivity = mActivity.get();
        ProgressDialog dialog = mProgDiag.get();

        if(dialog != null)
        {
            dialog.dismiss();
        }

        if (responseAsString == null)
        {
            if(parentActivity != null)
            {
                Utils.makeServerErrorDialog(parentActivity,
                                            new iDialogButtonClickedListener()
                                            {
                                                @Override
                                                public void onDialogButtonClicked()
                                                {
                                                    parentActivity.onBackPressed();
                                                }
                                            });
            }

            return;
        }

        //everything is ok
        if (responseAsString.equals("1"))
        {
            if(parentActivity != null)
            {
                Intent result = new Intent();
                result.putExtra(...);

                parentActivity.setResult(Activity.RESULT_OK, result);
            }
        }

        else
        {
            Utils.reportErrorToServer(...);

            if(parentActivity != null)
            {
                parentActivity.setResult(Activity.RESULT_CANCELED);
            }
        }

        if(parentActivity != null)
        {
            parentActivity.onBackPressed();
        }
    }
}

所以对于我在这个静态内部类中需要的每个变量,我必须创建一个新的弱引用,然后检索对象本身,然后每次我想访问它时我都需要检查它是否为空......看起来对我来说就像很多代码。

这是我建议的替代方案:

public abstract class BaseActivity extends AppCompatActivity
        implements WebClient.ICallback
{
    private static final String TAG = "BaseActivity";

    WebClient.ICallback mCallBack;
    ProgressDialog mProgDiag;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        setContentView(...);

        mCallBack = this;

        //some code to invoke a server request on button click
        //and passing mCallBack to the request
    }

    @Override
    public void onCallback(String data)
    {
        String responseAsString = Utils.extractStringFromResponse(...);

        mProgDiag.dismiss();

        if (responseAsString == null)
        {
            Utils.makeServerErrorDialog(this,
                                        new iDialogButtonClickedListener()
                                        {
                                            @Override
                                            public void onDialogButtonClicked()
                                            {
                                                onBackPressed();
                                            }
                                        });

            return;
        }

        //everything is ok
        if (responseAsString.equals("1"))
        {
            Intent result = new Intent();
            result.putExtra(...);

            setResult(Activity.RESULT_OK, result);
        }

        else
        {
            Utils.reportErrorToServer(...);

            setResult(Activity.RESULT_CANCELED);
        }

        onBackPressed();
    }

    @Override
    protected void onPause()
    {
        mCallBack = null;

        super.onPause();
    }

    @Override
    protected void onResume()
    {
        super.onResume();

        mCallBack = this;
    }
}

对我来说,这似乎更清晰:无需为我需要访问的每个变量创建和检索弱引用实例,我可以直接调用活动方法(例如 onBackPressed()),并且无需在任何地方检查 null。
我现在唯一需要检查 null 的地方是在调用 callBack 方法之前的 WebClient 类中。

所以我的问题是,这种方法在防止内存泄漏方面是否达到了相同的结果?它是静态内部类的“有价值”替代品吗?

【问题讨论】:

  • 好问题,我也想知道我是否真的需要每个变量的 WeakReference 并每次都检查 null
  • 好吧,您的解决方案中仍然需要一些null 检查。但我认为您的主要问题是您在需要时检查这些值。只需检查一次(在您的示例中为 parentActivity)。
  • Java 中没有“静态内部类”这样的东西,因为“内部类”的 Java 定义是一个非静态的嵌套类。
  • 匿名类绝对没有错。它们非常有用。使用弱引用使每个嵌套类都成为静态是一种奇怪的迷信。这种一揽子规则没有工程基础。正如您所发现的那样,它会导致过度设计的废话并远离问题域。放弃别人给你的坏建议(“几乎无处不在”?我认为不是!)并回到明智的编程实践。
  • 在这种情况下sensible programming practices 是什么?

标签: java android memory-leaks inner-classes


【解决方案1】:

很遗憾,您的方法不起作用。通过在您的活动中实现 WebClient.ICallback 而不是内部类,您不会摆脱泄漏。泄漏的发生不是因为对活动和对话框的引用隐含在匿名类、lambda 或非静态内部类实例中;当 Activity 消失时 WebClient 保留此引用时会发生这种情况(它不会被销毁,因为对它有强引用)。

您在 Activity 暂停时设置为 null 的特殊 mCallBack 没有任何收益。同样,您可以简单地将您的活动实例传递给 WebClient。现在有一个对您的活动的强引用,它由不受您控制的某人(WebClient 的异步处理程序)管理。如果你不走运,异步处理程序会卡在某个地方,永远不会释放这个引用。

请详细阅读explanation

请注意,如果不采取特殊措施,WebView 本身可以cause a memory leak

【讨论】:

  • 为什么 mCallback 没有任何收获?这是我的逻辑,请解释为什么我错了:如果我将 REFERENCE mCallback 传递给 WebClient,那么它的引用与活动所持有的引用相同。因此,当活动暂停时,该引用在活动和 WebClient 中都为空(因为它们都指向内存中的同一个对象) - 因此,当活动暂停时,WebClient 不再持有对活动的引用(它包含对 null 的引用),并且可以对活动进行垃圾收集。
  • 不幸的是,Java 引用不能以这种方式工作。当您将对 mCallBack 的引用传递给 WebClient 时,它不会保留指向 myActivityInstance.mCallBack 的“指针”。相反,它保留了一个重复 mCallBack 的单独引用。因此,当您将 mCallBack 设置为 null 时,WebClient 不受影响。如果 Java 使用引用计数,我们可以说设置 mCallBack = this 设置 refcount=2,并启动 WebClient 将其增加到 3。当 onPause() 设置 mCallBack = null 时,引用计数再次为 2。 Java 不使用 refcount,但在这种情况下结果是一样的。
猜你喜欢
  • 1970-01-01
  • 2016-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-13
  • 1970-01-01
  • 2015-04-04
相关资源
最近更新 更多