Утечка активности при использовании волейболистов

Я использую библиотеку volley для отправки запросов, и у меня есть утечка памяти. Я проследил его с утечкой канарейки и, похоже, от моих просьб mListeners. после некоторого поиска я отменяю все свои запросы в моей текущей деятельности, но все же у меня есть утечка, я мог бы использовать некоторую помощь, спасибо, вот мой код загрузки: (примечание: я использую одноэлементный шаблон для получения очереди запросов зрителей)

private void startImageDownloadService(final NEWS selectedNews) {
        if (selectedNews.getIMG_URL() != null && !selectedNews.getIMG_URL().equals("null")) {
            ImageRequest imageRequest = new ImageRequest(selectedNews.getIMG_URL(),
                    new Response.Listener<Bitmap>() {
                @Override
                public void onResponse(Bitmap bitmap) {
                    newsImage_imageView.setVisibility(View.VISIBLE);
                    newsImage_imageView.setImageBitmap(bitmap);
                    saveImageToFileStarter(bitmap, selectedNews);
                }
            }, 200, 200, null, null, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {
                    Toast.makeText(getApplicationContext(), "Failed to download image", Toast.LENGTH_SHORT).show();
                }
            });
            imageRequest.setShouldCache(false);
            MyVolleySingleton.getInstance(getApplicationContext()).addToRequestQueue(imageRequest, "newsActivityImage");
//
        }
    }


private void requestLike(final ImageButton likeButton, final long id) {
        try {
            StringRequest jsonRequest = new StringRequest(Request.Method.POST, G.likeURL + id + "/like",
                    new Response.Listener<String>() {
                @Override
                public void onResponse(String s) {
                    try {
                        Log.e("requestLikeJson", s);
                        JSONObject likeObject = new JSONObject(s);
                        int likeNumbers = likeObject.getInt("likes");
//                        Toast.makeText(NewsActivity.this, likeNumbers + "", Toast.LENGTH_LONG)
//                                .show();
                        if (likeButton.getTag().equals("notliked")) {
                            setLikeChangeInDatabase(false, id, likeNumbers);
                        }
                        else{
                            setLikeChangeInDatabase(true, id, likeNumbers);
                        }
//                        viewsCount_textView.setText("" + likeNumbers);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {
                    Toast.makeText(NewsActivity.this, "No Internet connection", Toast.LENGTH_LONG).show();
                    try {
                        volleyError.printStackTrace();
                        Log.e("network error", new String(volleyError.networkResponse.data));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (likeButton.getTag().equals("notliked")) {
                        likeButton.setTag("liked");
                        likeButtonImageSetter();
                    } else {
                        likeButton.setTag("notliked");
                        likeButtonImageSetter();
                    }
                    Animation shake = AnimationUtils.loadAnimation(NewsActivity.this, R.anim.actionfeedback);
                    likeButton.startAnimation(shake);
                }
            }) {

                @Override
                public Priority getPriority() {
                    return Priority.HIGH;
                }

                @Override
                public Map<String, String> getHeaders() throws AuthFailureError {
                    Map<String, String> params = new HashMap<>();
                    Log.e("sent token", "Token " + G.token);
                    params.put("Authorization", "Token " + G.token);
                    params.put("Accept-Language", "en-US,en;q=0.8,fa;q=0.6,pt;q=0.4,ar;q=0.2,gl;q=0.2");

                    return params;
                }

                @Override
                protected Response<String> parseNetworkResponse(NetworkResponse response) {
                    String utf8String = null;
                    try {
                        utf8String = new String(response.data, "UTF-8");
                        return Response.success(utf8String, HttpHeaderParser.parseCacheHeaders(response));

                    } catch (UnsupportedEncodingException e) {
                        return Response.error(new ParseError(e));
                    }

                }
            };
            jsonRequest.setRetryPolicy(new DefaultRetryPolicy(
                    G.socketTimeout,
                    0,
                    DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

            MyVolleySingleton.getInstance(getApplicationContext()).addToRequestQueue(jsonRequest, "like");
        } catch (Exception e) {

        }
    }

    private void requestView(long id) {

        try {
            StringRequest jsonRequest = new StringRequest(Request.Method.GET, G.viewURL + id + "/view", new Response.Listener<String>() {
                @Override
                public void onResponse(String s) {
                    try {
                        JSONObject viewObject = new JSONObject(s);
                        Log.e("requestViewJson", s);

                    } catch (JSONException e) {
                        e.printStackTrace();

                    }
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError volleyError) {
                    try {
                        volleyError.printStackTrace();
                        Log.e("network error", new String(volleyError.networkResponse.data));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }) {

                @Override
                public Map<String, String> getHeaders() throws AuthFailureError {
                    Map<String, String> params = new HashMap<>();
                    Log.e("sent token", "Token " + G.token);
                    params.put("Authorization", "Token " + G.token);
                    params.put("Accept-Language", "en-US,en;q=0.8,fa;q=0.6,pt;q=0.4,ar;q=0.2,gl;q=0.2");

                    return params;
                }

                @Override
                public Priority getPriority() {
                    return Priority.HIGH;
                }

                @Override
                protected Response<String> parseNetworkResponse(NetworkResponse response) {
                    String utf8String = null;
                    try {
                        utf8String = new String(response.data, "UTF-8");
                        return Response.success(utf8String, HttpHeaderParser.parseCacheHeaders(response));

                    } catch (UnsupportedEncodingException e) {
                        return Response.error(new ParseError(e));
                    }

                }
            };
            jsonRequest.setRetryPolicy(new DefaultRetryPolicy(
                    G.socketTimeout,
                    0,
                    DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));

            MyVolleySingleton.getInstance(getApplicationContext()).addToRequestQueue(jsonRequest,"view");
        } catch (Exception e) {

        }
    }

и в моем onStop() у меня есть:

@Override
    protected void onStop() {
        try {//new change
            MyVolleySingleton.getInstance(getApplicationContext()).cancelPendingRequests("newsActivityImage");
            MyVolleySingleton.getInstance(getApplicationContext()).cancelPendingRequests("view");
            MyVolleySingleton.getInstance(getApplicationContext()).cancelPendingRequests("like");
            loadImageFromFile.cancel(true);
            loadImageFromFile=null;
            }catch (Exception e){}
        super.onStop();

    }

Ответ 1

После некоторых исследований я обнаружил, что слушатели волейбола вызывают утечку активности, потому что они создаются как анонимные классы и поэтому содержат неявную ссылку на их внешний класс (в этом примере новости), поэтому я сделал отдельный класс для VolleyRequest (просто чтобы сделать их настраиваемыми, не связанных с утечкой) и создал интерфейс прослушивателя, который я реализую как внутренний класс, и использую слабую ссылку для доступа к активности

код запроса:

public class MyVolleyStringRequest extends com.android.volley.toolbox.StringRequest {

    StringResponseListener mListener;

    public MyVolleyStringRequest(int method, String url, final StringResponseListener mListener) {
        super(method, url, new Response.Listener<String>() {
            @Override
            public void onResponse(String s) {
                    mListener.onSuccess(s);
            }
        }, new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError volleyError) {
                    mListener.onFailure(volleyError);
            }
        });
        this.mListener = mListener;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        Map<String, String> params = new HashMap<>();
        Log.e("sent token", "Token " + G.token);
        params.put("Authorization", "Token " + G.token);
        params.put("Accept-Language", "en-US,en;q=0.8,fa;q=0.6,pt;q=0.4,ar;q=0.2,gl;q=0.2");

        return params;
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        try {
            String utf8String = new String(response.data, "UTF-8");
            return Response.success(utf8String, HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        }

    }


}

requestLike и requestView Код:

private void requestLike(final ImageButton likeButton, final long id) {

            MyVolleyStringRequest likeRequest = new MyVolleyStringRequest(Request.Method.POST, G.likeURL + id + "/like",
                    new RequestLikeStringListener(this));
            MyVolleySingleton.getInstance(getApplicationContext()).addToRequestQueue(likeRequest, "like");
    }

    private void requestView(long id) {
        MyVolleyStringRequest viewRequest = new MyVolleyStringRequest(Request.Method.GET, G.viewURL + id + "/view",
                new RequestViewStringListener());
        MyVolleySingleton.getInstance(getApplicationContext()).addToRequestQueue(viewRequest, "view");
    }

код интерфейса прослушивателя:

public interface StringResponseListener {
    void onSuccess(String response);
    void onFailure(VolleyError error);
}

код внутреннего класса:

public static class RequestLikeStringListener implements StringResponseListener{

        WeakReference<NewsActivity> contextWeakReference;
        RequestLikeStringListener(NewsActivity context){
            contextWeakReference = new WeakReference<>(context);

        }

        public void onSuccess(String response) {
            try {
                NewsActivity context = contextWeakReference.get();
                if(context != null) {
                    ImageButton likeButton = (ImageButton) context.findViewById(R.id.likeButton);
                    Log.e("requestLikeJson", response);
                    JSONObject likeObject = new JSONObject(response);
                    int likeNumbers = likeObject.getInt("likes");

                    if (likeButton.getTag().equals("notliked")) {
                        context.setLikeChangeInDatabase(false, contextWeakReference.get().id, likeNumbers);
                    } else {
                        context.setLikeChangeInDatabase(true, contextWeakReference.get().id, likeNumbers);
                    }
                }

            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        public void onFailure(VolleyError error) {
            NewsActivity context = contextWeakReference.get();
            if(context != null) {
                ImageButton likeButton = (ImageButton) context.findViewById(R.id.likeButton);
                Toast.makeText(context, "No Internet connection", Toast.LENGTH_LONG).show();
                try {
                    error.printStackTrace();
                    Log.e("network error", new String(error.networkResponse.data));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (likeButton.getTag().equals("notliked")) {
                    likeButton.setTag("liked");
                    context.likeButtonImageSetter();
                } else {
                    likeButton.setTag("notliked");
                    context.likeButtonImageSetter();
                }
                Animation shake = AnimationUtils.loadAnimation(context, R.anim.actionfeedback);
                likeButton.startAnimation(shake);
            }
        }

надеюсь, что это поможет кому-то!