Анонимный прослушиватель запроса волейбола, вызывающего утечку памяти

Я использую библиотеку 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

В вышеупомянутом репозитории отсутствуют какие-либо утечки памяти. Чао.