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

时间:2018-03-02 07:35:26

标签: java android jvm okhttp

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

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。为什么这个用于回调的匿名内部类实例不会导致活动泄漏。

4 个答案:

答案 0 :(得分:8)

  

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

我假设你在这里的意思是它不会导致内存泄漏,但考虑到你实例化匿名{{1}的范围,它肯定可以。是Callback

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

Activity

如果您从某些活动创建了class MemorySink { static private List<Callback> callbacks = new ArrayList<>(); public static void doSomething(Callback callback){ callbacks.add(callback); } } 的实例并将它们传递给Callback,当其中一个doSomething(callback)被销毁时,系统将不再使用该实例,这是预期的垃圾收集器将释放该实例。但是,如果此处Activity引用了MemorySink引用该Callback的{​​{1}},则Activity的实例即使在被销毁后也会保留在内存中。 Bam,内存泄漏。

所以你说你的样本没有导致内存泄漏,我会建议你试试Activity,创建一个简单的MemorySink&#34; MainActivity&#34;有2个按钮,可能还有一些图像,以增加内存占用。在Activity中以这种方式在第一个按钮上设置一个监听器:

onCreate

您刚刚使用 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(); } }); 创建了内存泄漏。每次单击Callback中的按钮时,MainActivity的实例都将被销毁,并且将创建一个新实例。但是,MainActivity的旧实例将保留在内存中。我邀请您多次单击该按钮,然后转储内存(在Android Studio中使用Android Profiler),或使用LeakCanary。

因此,我们使用MainActivity创建了内存泄漏,与OP相同。现在,我们将此方法添加到Callback

MemorySink

然后从public static void releaseAll() { callbacks.clear(); } 上的其他按钮调用它。如果多次按第一个按钮(如果MainActivity中有图像,则更好),即使手动触发垃圾收集(Android配置文件),您也会看到内存使用率上升。然后单击第二个按钮,释放对MainActivity的所有引用,触发垃圾回收,内存中断。没有更多的内存泄漏。

所以问题不是如果Callback可以或不能创建内存泄漏,那肯定是可以的。问题是你在哪里传递Callback。在这种情况下,Callback并没有创建内存泄漏,所以你说,但总是不能保证会发生这种情况。在这种情况下,您需要确保OkHttpClient的实现,以确保它不会产生内存泄漏。

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

答案 1 :(得分:1)

是的,你是对的,这就是你可以添加

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

答案 2 :(得分:1)

非静态内部类具有对外部类实例的强引用。另一方面,静态内部类没有对外部类的实例的强引用。它使用WeakReference引用外部类。 you can learn more here

答案 3 :(得分:1)

client.newCall(request).enqueue(new Callback() {...})

在这里,您要传递回调对象以进行改造。通过这种方式,您可以告诉改装在其通话过程中使用它来在呼叫结束时回拨给您。

该进程不在当前活动中,并且它位于单独的线程和上下文中(概念上,不是android的上下文)。在您的活动被某些东西(如旋转)破坏之后,改装服务可能仍然存在,并且持有对此对象的引用,并且您的活动导致内存泄漏(无法从内存中清除,从而污染内存),因为改造需要活动的回调对象。

如果您想修复此内存泄漏,则需要取消呼叫并删除活动onStop中的回调,但这样您就会在轮换时失去呼叫。

强烈建议做的更好的事情是,您不要在活动本身内部执行异步操作,而是在单独的对象中执行(如MVP模式中的Presenter或MVVM中的ViewModel)具有与活动不同的生命周期(不会被旋转或......破坏的生命周期)。