android - утечки - что за приложение leaks




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

Как правило, анонимные классы имеют сильную ссылку на экземпляр окружающего класса. В вашем случае это будет SplashScreenActivity. Теперь, я думаю, ваша Activity закончена, прежде чем вы получите ответ со своего сервера через Volley. Поскольку слушатель имеет сильную ссылку на включение Activity, эта активность не может быть собрана мусором до тех пор, пока класс Аноним не будет завершен. Вам следует пометить все запросы, которые вы отправляете, с экземпляром Activity, и отменить все запросы в onDestroy() Activity.

stringRequest.setTag(activityInstance);

Чтобы отменить все ожидающие запроса:

requestQueue.cancellAll(activityInstance);

Кроме того, используйте контекст приложения внутри VolleySingleton для создания RequestQueue.

mRequestQueue = Volley.newRequestQueue(applicationContext);

Не используйте здесь свой контекст активности и не кэшируйте свой экземпляр Activity внутри VolleySingleton.

Я использую библиотеку волейбола для вызова веб-сервисов. Я сделал общий класс для того, чтобы заставить все веб-сервисы звонить и звонить по телефону оттуда, а анонимный прослушиватель - для успешной и ошибочной реакции.

Но когда я использую утечку канарейки, он показывает утечку памяти, связанную с контекстом. Ниже приведен фрагмент кода:

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

Любая идея, чтобы удалить эту утечку, поможет.


Я знаю, что я немного опаздываю, чтобы присоединиться к вечеринке, но несколько дней назад эта проблема испортила мои выходные. Чтобы выяснить, я продолжил исследование, которое, наконец, получило решение.

Проблема заключается в том, что последний объект запроса просочился в диспетчер сети и диспетчер кэшей.

@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());
            }
        }

Как вы видите, новый объект запроса создается до того, как он берет из очереди. Это преодолевает проблему утечки памяти.

PS: Не используйте Volley из репозитория Google, поскольку он устарел и имеет эту ошибку с тех пор. Чтобы использовать Volley, пойдите для этого:

https://github.com/mcxiaoke/android-volley

Вышеупомянутый репозиторий не содержит никаких утечек памяти. Чао.


У меня была аналогичная проблема, обнаруженная с помощью LeakCanary, где mListener Volley ссылался на мой прослушиватель ответов, и мой слушатель ссылался на 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();

Это устранило утечку.





leakcanary