Android Volley Memory Leaks
Asked Answered
A

3

6

I use google's volley library and I have been battling memory leaks in my apps for weaks now. I have done soo much research and tried soo much already but now I just do not know what to do. This is a sample code:

SplashActivity.java

public class SplashActivity extends AppCompatActivity {

    Context mContext;
    AuthRequest mAuthRequest;
    GetTokenOnSuccessListener mGetTokenOnSuccessListener;
    GetTokenOnErrorListener mGetTokenOnErrorListener;
    private ConfigTable mConfigTable;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initialiseViewsAndComponents();
    }

    @Override
    protected void onStart() {
        super.onStart();
        getAuthToken();
    }

    private void initialiseViewsAndComponents() {
        mContext = SplashActivity.this;
        mAuthRequest = new AuthRequest(mContext);
        mGetTokenOnSuccessListener = new GetTokenOnSuccessListener(mContext);
        mGetTokenOnErrorListener = new GetTokenOnErrorListener(mContext);
        mConfigTable = new ConfigTable(mContext);
    }

    private void getAuthToken() {
        if (!mConfigTable.get("INITIALISED").equals("")) {
            mAuthRequest.guest(mGetTokenOnSuccessListener, mGetTokenOnErrorListener);
        } else {
            Intent mainIntent = new Intent(mContext, MainActivity.class);
            startActivity(mainIntent);
        }
    }

}

GetTokenOnSuccessListener.java

public class GetTokenOnSuccessListener implements Response.Listener<JSONObject> {

    //private Activity mActivity;
    private Context mContext;
    private ConfigTable mConfigTable;
    private int mSuccess = 0;
    private String mMessage = "";

    public GetTokenOnSuccessListener(Context context) {
        //this.mActivity = context;
        this.mContext = context;
        this.mConfigTable = new ConfigTable(this.mContext);
    }

    @Override
    public void onResponse(JSONObject response) {
        try {
            mSuccess = Integer.parseInt(response.get("success").toString());
            mMessage = response.get("message").toString();

            if (mSuccess == 1) {
                mConfigTable.setAuthToken(response.get("message").toString());
                Intent mainIntent = new Intent(mContext, MainActivity.class);
                mContext.startActivity(mainIntent);
                ((SplashActivity) mContext).finish();
            } else {
                Toast.makeText(mContext, "Lol access denied, could not retrieve token from server.", Toast.LENGTH_SHORT).show();
            }


        } catch (JSONException e) {
            e.printStackTrace();
            Toast.makeText(mContext, "Lol access denied, could not retrieve token from server.", Toast.LENGTH_SHORT).show();
        }
    }
}

GetTokenOnErrorListener.java

public class GetTokenOnErrorListener implements Response.ErrorListener {

    private Context mContext;

    public GetTokenOnErrorListener(Context context) {
        this.mContext = context;
    }

    @Override
    public void onErrorResponse(VolleyError error) {
        Utils.showNetworkResponse(mContext, error);
    }
}

Okay now I moved the response listeners to their own separate classes based on something I read online thinking it will resolve the leak but no it did not. I added code to cancel all pending requests onDestroy() based on the request's tag but still I had memory leaks.

This is just my splash activity and the leaks here are small, I have a feeling it's because I call finish() but I don't get that because I call it after the request has been completed successfully. All my other activities have similar codes but leak more memory as much as 11mb.

So my question is has anyone worked with the volley library? How do I use it and avoid memory leaks?

Using this version:

compile 'com.android.volley:volley:1.0.0'
Agribusiness answered 30/10, 2017 at 22:15 Comment(5)
github.com/google/volley/issues/81Flageolet
I realize this isn't immediately helpful, but I highly recommend switching off Volley. It's historically been undocumented. If the same library was made by anyone else, no one would use it. RxJava has proven itself to be a better abstraction on Android for not just http requests, but all asynchronous data, and provides the ability to unsubscribe from steams of data so that there are no more memory leaks. RxJava + Retrofit is a much better fit when compared to Volley. I highly recommend migrating.Gnarl
@Gnarl how would he use Rx, if he doesn't understand the core principles even in a regular request? This leak can be avoided regardless of the library used.Daughtry
It's hard to blame him for not understanding an undocumented library :-P, although you are right, Rx is a step up in difficulty.Gnarl
@Gnarl I mean, it's not a Volley's fault in this particular case. He just has a strong reference to Activity in the ongoing request, and he doesn't remove this reference in onDestroy. That's it. Basically the same would happen if you won't unsubscribe in Rx. Documentation to the library isn't necessary to understand some core principles.Daughtry
D
4

It's not enough just to "Move response listeners to their own separate classes". Your listeners have strong references to the Activity (mContext), introducing a leak during the request. It means that your Activity can't be garbage collected, while the request is ongoing. It's not really a Volley's fault, but rather a natural way of things.

You have couple of options in your case:

1) Pass a WeakReference<Context> to your listeners, instead a strong reference to Context. This way you won't introduce a leak and will have to check if this referenced Context isn't yet null, when you try to access it. But I'd rather go for the 2nd option.

2) Set mContext to null in listeners, when Activity's onDestroy() is called. And perform null check as well, when you are trying to do something with Context in listener. So as soon as Activity will be destroyed, you'll remove other strong references to it, allowing GC to collect it normally.

Daughtry answered 11/11, 2017 at 23:26 Comment(3)
Thank you for your time, will try it and see if it helps.Agribusiness
One question though, assuming I do not store the context in a variable and instead I use say MainActivity.this what do I do about that then? Because I cannot set MainActivtiy.this to null. Forgive me I am quite new to android dev.Agribusiness
@user3718908 This is effectively the same as storing the reference to Context, as your inner (or inner anonymous) class has an implicit reference to it always. So don't do itDaughtry
G
3

Please update to latest volley version they have fixed memory leaks. 'com.android.volley:volley:1.1.0-rc1'

https://github.com/google/volley/releases/tag/1.1.0-rc1

Gingras answered 31/10, 2017 at 5:55 Comment(1)
Tried it still having leaks.Agribusiness
T
-2

You are always creating a new activity. Please try this instead. Read more about it here: https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TOP

Intent mainIntent = new Intent(mContext, MainActivity.class);
mainIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
mContext.startActivity(mainIntent);
Thao answered 8/11, 2017 at 1:55 Comment(1)
This shouldn't make any difference because MainActivity is showed only once from the Splash screen. Also, starting the same activity multiple times doesn't create a memory leak unless you have the code that creates the memory leak.Hesper

© 2022 - 2024 — McMap. All rights reserved.