我正在使用排球库进行网络服务调用。我创建了一个通用类,用于调用所有Web服务并从那里进行服务调用,并使匿名监听器成功并响应错误。
但是当我使用泄漏金丝雀时,它显示与上下文相关的内存泄漏。以下是我的代码片段:
public void sendRequest(final int url, final Context context, final ResponseListener responseListener, final Map<String, String> params) {
StringRequest stringRequest;
if (isNetworkAvailable(context)) {
stringRequest = new StringRequest(methodType, actualURL + appendUrl, new Listener<String>() {
@Override
public void onResponse(String response) {
dismissProgressDialog(context);
try {
(responseListener).onResponse(url, response);
} catch (JsonSyntaxException e) {
// Util.showToast(context, context.getResources().getString(R.string.error));
Crashlytics.logException(e);
}
}
}, new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Util.showToast(context,context.getString(R.string.error));
dismissProgressDialog(context);
if (error instanceof NetworkError) {
Util.showToast(context, context.getResources().getString(R.string.network_error));
} else if (error instanceof NoConnectionError) {
Util.showToast(context, context.getResources().getString(R.string.server_error));
} else if (error instanceof TimeoutError) {
Util.showToast(context, context.getResources().getString(R.string.timeout_error));
} else {
Util.showToast(context, context.getResources().getString(R.string.default_error));
}
}
}) {
@Override
protected Map<String, String> getParams() throws AuthFailureError {
return params;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
return request.getHeaders(context, actualURL, false);
}
};
stringRequest.setRetryPolicy(new DefaultRetryPolicy(30000, DefaultRetryPolicy.DEFAULT_MAX_RETRIES, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
VolleySingleton.getInstance(context).addRequest(stringRequest);
} else {
Util.showToast(context, context.getString(R.string.internet_error_message));
}
}
我创建了一个名为response listener的接口,用于重定向对activity或fragment的响应。我提出如下要求。
Request.getRequest().sendRequest(Request.SOME URL, SplashScreenActivity.this, SplashScreenActivity.this, new HashMap<String, String>());
但我面临内存泄漏:
In 2.1.1:31.
* activity.SplashScreenActivity has leaked:
* GC ROOT com.android.volley.NetworkDispatcher.<Java Local>
* references network.Request$5.mListener (anonymous subclass of com.android.volley.toolbox.StringRequest)
* references network.Request$3.val$responseListener (anonymous implementation of com.android.volley.Response$Listener)
* leaks activity.SplashScreenActivity instance
* Retaining: 1.2MB.
* Reference Key: b8e318ea-448c-454d-9698-6f2d1afede1e
* Device: samsung samsung SM-G355H kanas3gxx
* Android Version: 4.4.2 API: 19 LeakCanary: 1.4 6b04880
* Durations: watch=5052ms, gc=449ms, heap dump=2617ms, analysis=143058ms
任何想要删除此泄漏的任何帮助都表示赞赏。
答案 0 :(得分:5)
通常,匿名类具有对封闭类实例的强引用。在您的情况下,这将是SplashScreenActivity。现在我想,在您通过Volley从服务器获得响应之前,您的Activity
已经完成。由于侦听器具有对封闭Activity的强引用,因此在Anonymous类完成之前,不能对该Activity进行垃圾回收。您应该做的是标记您使用Activity实例发送的所有请求,并在onDestroy()
回调Activity时取消所有请求。
stringRequest.setTag(activityInstance);
取消所有待处理的请求:
requestQueue.cancellAll(activityInstance);
此外,在VolleySingleton中使用Application上下文来创建RequestQueue。
mRequestQueue = Volley.newRequestQueue(applicationContext);
不要在那里使用您的活动上下文,也不要在VolleySingleton中缓存您的Activity实例。
答案 1 :(得分:1)
基本上,匿名方法在Android
或任何ClientSideSystem
中都很糟糕,因为你没有大量记忆。发生的事情是,您已将Context
作为参数传递给方法,anonymous
拥有该参考。真正的混乱现在出现在场景中,因为network call
内部的线程无法完成它的工作,在此之前由于某种原因,调用活动要么在这种情况下破坏或回收GC
,无法收集活动,因为wokerThread
可能仍然在引用它。有关详细说明,请浏览this。
解决方案可以是静态内部类或独立类,在这两种情况下都使用WeakReference
来保存资源并在使用之前进行空检查。
WeakReference
的优点是,如果没有其他人,如果对其进行引用,它将允许GC
收集该对象。
答案 2 :(得分:1)
我在LeakCanary中检测到类似的问题,其中Volley的mListener引用了我的响应监听器,我的监听器引用了ImageView,因此可以使用下载的图像更新它。
我让我的响应监听器成为活动中的内部类..
private class MyVolleyResponseListener <T> implements com.android.volley.Response.Listener <Bitmap> {
@Override
public void onResponse(Bitmap bitmap) {
thumbNailView.setImageBitmap(bitmap);
}
}
..并停止并在活动中的onDestroy()内启动了排球请求队列。
requestQueue.stop();
requestQueue.start();
这解决了泄漏问题。
答案 3 :(得分:0)
我知道加入派对有点晚了,但是几天前这个问题确实破坏了我的周末。为了弄清楚,我继续研究了一下,最终得到了解决方案。
问题在于最后一个请求对象在Network Dispatcher&amp;缓存调度程序。
@Override
public void run() {
if (DEBUG) VolleyLog.v("start new dispatcher");
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Make a blocking call to initialize the cache.
mCache.initialize();
Request<?> request;
while (true) {
// release previous request object to avoid leaking request object when mQueue is drained.
request = null;
try {
// Take a request from the queue.
request = mCacheQueue.take();
} catch (InterruptedException e) {
// We may have been interrupted because it was time to quit.
if (mQuit) {
return;
}
continue;
}
try {
request.addMarker("cache-queue-take");
// If the request has been canceled, don't bother dispatching it.
if (request.isCanceled()) {
request.finish("cache-discard-canceled");
continue;
}
// Attempt to retrieve this item from cache.
Cache.Entry entry = mCache.get(request.getCacheKey());
if (entry == null) {
request.addMarker("cache-miss");
// Cache miss; send off to the network dispatcher.
mNetworkQueue.put(request);
continue;
}
// If it is completely expired, just send it to the network.
if (entry.isExpired()) {
request.addMarker("cache-hit-expired");
request.setCacheEntry(entry);
mNetworkQueue.put(request);
continue;
}
// We have a cache hit; parse its data for delivery back to the request.
request.addMarker("cache-hit");
Response<?> response = request.parseNetworkResponse(
new NetworkResponse(entry.data, entry.responseHeaders));
request.addMarker("cache-hit-parsed");
if (!entry.refreshNeeded()) {
// Completely unexpired cache hit. Just deliver the response.
mDelivery.postResponse(request, response);
} else {
// Soft-expired cache hit. We can deliver the cached response,
// but we need to also send the request to the network for
// refreshing.
request.addMarker("cache-hit-refresh-needed");
request.setCacheEntry(entry);
// Mark the response as intermediate.
response.intermediate = true;
// Post the intermediate response back to the user and have
// the delivery then forward the request along to the network.
final Request<?> finalRequest = request;
mDelivery.postResponse(request, response, new Runnable() {
@Override
public void run() {
try {
mNetworkQueue.put(finalRequest);
} catch (InterruptedException e) {
// Not much we can do about this.
}
}
});
}
} catch (Exception e) {
VolleyLog.e(e, "Unhandled exception %s", e.toString());
}
}
如您所见,在从队列中获取之前会创建一个新的请求对象。这克服了内存泄漏的问题。
P.S:不要使用来自Google存储库的Volley,因为它已被弃用,从那时起就有这个bug。为了使用Volley,请选择:
https://github.com/mcxiaoke/android-volley
以上存储库没有任何内存泄漏。再见。