Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Activity leak while using volley listeners

I'm using volley library for sending requests and I have a memory leak. I traced it with leak canary and it seems to be from my requests mListeners. after some searching I cancel all my requests in my current activity but still I have a leak I could use some help thanks here is my download code: (note: I use singleton pattern for getting volley request queue)

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) {

        }
    }

and in my onStop() I have :

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

    }
like image 799
Aref Moradi Avatar asked Oct 20 '22 03:10

Aref Moradi


1 Answers

After some researching I found that the volley listeners cause the activity leak because they are instantiated as anonymous classes and therefore hold an implicit reference to their outer class (in this example news activity) so I made a separate class for VolleyRequest (just to make them customizable not really related to the leak) and made a listener interface which I implement as an innerclass and use a weak reference to have access to the activity

request code :

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 and requestView Code:

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");
    }

listener interface code :

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

innerclasses code:

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

hope it helps someone!

like image 84
Aref Moradi Avatar answered Oct 24 '22 14:10

Aref Moradi