1 year ago

#388165

test-img

Aeternis

How do I clean this Volley Handler memory leak?

(I know Volley's already deprecated and all, but I still have to use it for my project.) Anyways, I have a project where I'm scraping the Thingspeak API via REST. My current implementation is using a Handler and Runnable to repeat by retrieval and parsing code every x seconds. However, upon inspecting the profiler, I noticed a that my memory was continuously increasing, meaning I had a leak somewhere. (The top memory consumers were String[] and byte[], which I believe came from the retrieved JSON data.) If I had to guess, I'm probably not cleaning up my request queue properly, but I'm really stumped on what my exact leak is and how to clean it, so I'd greatly appreciate some help.

private Handler handler = new Handler();
private Runnable scrape;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}

@Override
protected void onResume() {
    super.onResume();
    connect(); // call at the start

    scrape = new Runnable() {
        @Override
        public void run() {
            connect(); // call every x ms
            handler.postDelayed(this, 5000);
        }
    };
    handler.postDelayed(scrape, 5000);
}

@Override
protected void onPause() {
    super.onPause();
    handler.removeCallbacksAndMessages(null);
    handler.removeMessages(0);
    handler.removeCallbacks(scrape, null);
}

private void connect() {
    MySingleton.getInstance(this.getApplicationContext()).getRequestQueue(); // sets up Volley request queue and offloads to background task

    JsonObjectRequest collectData = new JsonObjectRequest(
            Request.Method.GET,
            EMB_URL, // TODO: change appropriate url
            null,
            response -> {
                try {
                    JSONObject myResponse = new JSONObject(response.toString());

                    // TODO: cast to double to show the average
                    String ultrasonic = myResponse.getString("field1");
                    String flex1 = myResponse.getString("field2");
                    String flex2 = myResponse.getString("field3");
                   

                    PostureActivity.this.runOnUiThread(() -> {
                        TextView neck = findViewById(R.id.neck_number);
                        TextView back = findViewById(R.id.back_number);

                        neck.setText(ultrasonic);
                        back.setText(converter(flex1, flex2));
                    });

                } catch (JSONException e) { // what if response is null?
                    e.printStackTrace();
                }
            },
            error -> { // the error thrown is the one passed by the server, shown by one of the ff:
                if (error instanceof AuthFailureError) {
                    Toast.makeText(getApplicationContext(), "Authentication failed.", Toast.LENGTH_SHORT).show();
                } else if (error instanceof NetworkError) {
                    Toast.makeText(getApplicationContext(), "Could not reach the server. Please check your Internet connection, firewall or VPN settings.", Toast.LENGTH_SHORT).show();
                } else if (error instanceof ParseError) {
                    Toast.makeText(getApplicationContext(), "Cannot parse server response data.", Toast.LENGTH_SHORT).show();
                } else if (error instanceof ServerError) {
                    Toast.makeText(getApplicationContext(), "Server appears to be down.", Toast.LENGTH_SHORT).show();
                } else if (error instanceof TimeoutError) {
                    Toast.makeText(getApplicationContext(), "Took too long for server to respond.", Toast.LENGTH_SHORT).show();
                }
            }
    );

    MySingleton.getInstance(this).addToRequestQueue(collectData);

}

The code above is my main activity code, while my Singleton is:

public class MySingleton {
private static MySingleton instance;
private static Context ctx;
private RequestQueue requestQueue;

private MySingleton(Context context) {
    ctx = context;
    requestQueue = getRequestQueue();
}

public static synchronized MySingleton getInstance(Context context) {
    if (instance == null) {
        instance = new MySingleton(context);
    }
    return instance;
}

public RequestQueue getRequestQueue() {
    if (requestQueue == null) {
        // getApplicationContext() is key, it keeps you from leaking the
        // Activity or BroadcastReceiver if someone passes one in.
        requestQueue = Volley.newRequestQueue(ctx.getApplicationContext());
    }
    return requestQueue;
}

public <T> void addToRequestQueue(Request<T> req) {
    // catches VolleyError by itself, then passes the specific type to error listener
    req.setRetryPolicy(new DefaultRetryPolicy(
            // default is 2500/1/1
            DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, // 20 seconds to give time for data to reach?
            DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
            DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
    ));
    getRequestQueue().add(req);
}

public <T> void addToRequestQueue(Request<T> req, String tag) {
    // catches VolleyError by itself, then passes the specific type to error listener
    req.setRetryPolicy(new DefaultRetryPolicy(
            // default is 2500/1/1
            DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, // 20 seconds to give time for data to reach?
            DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
            DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
    ));
    getRequestQueue().add(req);
}

}

java

android

android-studio

android-volley

0 Answers

Your Answer

Accepted video resources