我们知道匿名内部类可能会导致内存泄漏。但是为什么它在异步网络呼叫时不起作用 例如:
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。为什么这个用于回调的匿名内部类实例不会导致活动泄漏。
答案 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)具有与活动不同的生命周期(不会被旋转或......破坏的生命周期)。