Анонимный прослушиватель запроса волейбола, вызывающего утечку памяти
Я использую библиотеку volley для вызова веб-сервисов. Я сделал общий класс для того, чтобы заставить все веб-сервисы звонить и звонить по телефону оттуда, и сделал анонимный прослушиватель для успешного ответа на ошибку.
Но когда я использую утечку канарейки, он показывает утечку памяти, связанную с контекстом. Ниже приведен фрагмент кода:
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 для перенаправления ответов на активность или фрагмент. Я сделал запрос следующим образом.
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
Любая идея удалить эту утечку поможет.
Ответы
Ответ 1
Как правило, анонимные классы имеют сильную ссылку на экземпляр окружающего класса. В вашем случае это будет SplashScreenActivity. Теперь, я думаю, ваш Activity
закончен, прежде чем вы получите ответ со своего сервера через Volley. Поскольку слушатель имеет сильную ссылку на включение Activity, эта активность не может быть собрана мусором до тех пор, пока класс Аноним не будет завершен. Вам следует пометить все запросы, отправляемые с экземпляром Activity, и отменить все запросы в onDestroy()
обратном вызове Activity.
stringRequest.setTag(activityInstance);
Отменить все ожидающие запроса:
requestQueue.cancellAll(activityInstance);
Кроме того, используйте контекст приложения внутри VolleySingleton для создания RequestQueue.
mRequestQueue = Volley.newRequestQueue(applicationContext);
Не используйте здесь свой контекст активности и не кэшируйте свой экземпляр Activity внутри VolleySingleton.
Ответ 2
В принципе анонимный подход ужасен в Android
или в любом ClientSideSystem
, где у вас нет массивной памяти. Что происходит, вы прошли Context
как параметр в методе, а anonymous
содержит ссылку на него. Настоящий беспорядок приходит теперь на сцену, когда поток внутри которого делает network call
не может завершить работу, и до этого вызывающая активность по какой-либо причине либо уничтожает, либо перерабатывает в этом случае GC
не может собирать активность как wokerThread
может все еще содержать ссылку на него. Прочтите этот для подробного описания.
Решение может быть либо статическими внутренними классами, либо независимыми классами, в обоих случаях используйте WeakReference
для хранения ресурсов и выполнения нулевой проверки перед их использованием.
Преимущество WeakReference
заключается в том, что он сможет GC
собирать объект, если нет другого, если он содержит ссылку на него.
Ответ 3
У меня была аналогичная проблема, обнаруженная с помощью 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();
Это исправило утечку.
Ответ 4
Я знаю, что немного опоздал, чтобы присоединиться к вечеринке, но несколько дней назад эта проблема портила мои выходные. Чтобы выяснить, я продолжил исследование, которое, наконец, получило решение.
Проблема заключается в том, что последний объект запроса просочился в Network Dispatcher и Cache Dispatcher.
@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: Не используйте Volley из репозитория Google, поскольку он устарел и имеет эту ошибку с тех пор. Чтобы использовать Volley, сделайте следующее:
https://github.com/mcxiaoke/android-volley
В вышеупомянутом репозитории отсутствуют какие-либо утечки памяти. Чао.