为什么异步网络调用的回调方法在活动完成时不会导致内存泄漏?

2022-09-04 03:19:52

我们知道匿名内部类可能会导致内存泄漏。但是为什么它在异步网络调用时不起作用。
例如:

OkHttpClient client = new OkHttpClient();

 Request request = new Request.Builder()
                .get()
                .url(url)
                .build();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }
            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                // String str = response.body().string();
                // do sth to our View, but those views may be null when activity finished
                }

            }
        });

我们将在回调方法调用时更改视图的状态,但这些视图在活动完成时始终为 null。为什么此用于回调的匿名内部类实例不会导致活动泄漏。


答案 1

为什么这个用于回调的匿名内部类实例不会导致活动泄漏

我假设您在这里的意思是它不会导致内存泄漏,但它肯定会,因为您正在实例化匿名的范围是.CallbackActivity

如果您在 Android 中实例化一个内部类,然后将对此实例的引用传递给其他某个组件,则只要此组件是可访问的,那么内部类的实例也将是可访问的。例如,请考虑以下情况:Activity

class MemorySink {

    static private List<Callback> callbacks = new ArrayList<>();

    public static void doSomething(Callback callback){
        callbacks.add(callback);
    }
}

如果从某些活动创建了 的实例并将其传递给 ,当其中一个被销毁时,系统将不再使用该实例,则预计垃圾回收器将释放该实例。但是,如果这里引用了 a,并且引用了该引用,则即使在销毁后,该实例也会保留在内存中。砰,内存泄漏。CallbackdoSomething(callback)ActivityMemorySinkCallbackActivityActivity

所以你说你的样本没有导致内存泄漏,我首先建议你试试,创建一个简单的“MainActivity”,里面有2个按钮,可能还有一些图像来增加内存占用量。在按以下方式在第一个按钮上设置侦听器:MemorySinkActivityonCreate

    findViewById(R.id.firstButton).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startActivity(new Intent(MainActivity.this, MainActivity.class));
            MemorySink.doSomething(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {

                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {

                }
            });
            finish();
        }
    });

您刚刚使用 创建了内存泄漏。每次您单击该实例中的按钮时,都会被销毁,并创建一个新实例。但是,的旧实例将保留在内存中。我邀请您单击该按钮多次,然后转储内存(在Android Studio中使用Android Profiler),或使用LeakCanary。CallbackMainActivityMainActivityMainActivity

因此,我们创建了一个内存泄漏,与 OP 中的相同类相同。现在,让我们将此方法添加到:CallbackMemorySink

public static void releaseAll() {
    callbacks.clear();
}

并从 上的另一个按钮调用它。如果您多次按下第一个按钮(最好是当您有图像)时,即使您手动触发垃圾回收(Android Profile),您也会看到内存使用量上升。然后单击第二个按钮,释放所有引用,触发垃圾回收,内存下降。不再有内存泄漏。MainActivityMainActivityCallback

因此,问题不在于是否可以或不能创建内存泄漏,它肯定可以。问题是你在哪里通过它。在这种情况下,不会造成内存泄漏,所以你说,但根本不能保证这总是会发生。在这种情况下,您需要确定 的实现,才能说它不会产生内存泄漏。CallbackCallbackOkHttpClientOkHttpClient

我的建议是始终假设,如果您要传递对某个外部类的引用,则会发生内存泄漏。Activity


答案 2

是的,你是对的,这就是为什么你可以添加一个

 if (response.isSuccessful()) {
                // String str = response.body().string();
                if(view != null){
                //do sth to view
                }
 }

推荐