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

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

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

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

我假设你在这里的意思是它不会导致内存泄漏,但它肯定可以,因为你实例化匿名Callback是一个Activity

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

 class MemorySink { static private List callbacks = new ArrayList<>(); public static void doSomething(Callback callback){ callbacks.add(callback); } } 

如果您从某些活动创建了Callback实例并将它们传递给doSomething(callback) ,当其中一个Activity被销毁时,系统将不再使用该实例,预计垃圾收集器将释放该实例。 但是,如果MemorySink在这里引用了一个对该Activity有引用的Callback ,那么该Activity的实例即使在被销毁之后也会保留在内存中。 Bam,内存泄漏。

所以你说你的样本没有引起内存泄漏,我首先建议你试试MemorySink ,创建一个简单的Activity “MainActivity”,带有2个按钮,可能还有一些图像来增加内存占用。 在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实例都将被销毁,并且将创建一个新实例。 但是, MainActivity的旧实例将保留在内存中。 我邀请您多次单击该按钮,然后转储内存(在Android Studio中使用Android Profiler),或使用LeakCanary。

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

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

并从MainActivity上的另一个按钮调用它。 如果您多次按第一个按钮(如果您在MainActivity有图像,则更好),即使您手动触发垃圾收集(Android配置文件),您也会看到内存使用率上升。 然后单击第二个按钮,释放对Callback所有引用,触发垃圾回收,内存关闭。 没有更多的内存泄漏。

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

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

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

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

非静态内部类具有对外部类的实例的强引用。 另一方面,静态内部类没有对外部类的实例的强引用。 它使用WeakReference引用外部类。 你可以在这里了解更多

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

在这里,您将传递一个回调对象进行改造。 通过这种方式,您可以告诉改造在呼​​叫完成时回拨给您的过程中使用它。

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

如果你想修复这个内存泄漏,你需要取消调用并删除activity onStop中的回调,但这样你就会失去旋转调用。

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