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'
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.
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
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);
Related
Using Retrofit to check values in database for a login operation. I don't want to move forward without checking this thus want this to occur synchronically.
I am using the execute method instead of enqueue but still getting the result asynchronously. I understand if I do it all in the same class, it might work but I am trying to keep things separately to prevent code duplicate for other future calls. Please advice what I am doing wrong or how I could go about addressing this. Thanks.
Following method occurs when I click a submit button. This method is at my Activity.
Note that everything in this method works. Problem is after startService, it doesn't wait to get information from my rest service and instead jumps straight to the Toast. After that it carries on to perform the execute method which is useless by then.
private void sendLoginRequest(User user){
String token = verificationHelper.createToken(properties.API_KEY, user, 60000);
Retrofit retrofit = retrofitHelper.getRetrofit(properties.BASE_URL);
UserRepository userRepository = retrofit.create(UserRepository.class);
Call<User> call = userRepository.login(token);
if(call != null){
syncService = new SyncService(call);
Intent i = new Intent(this, SyncService.class);
startService(i);
if(verificationHelper.isValidLogin()){
goToMainActivity();
}
}
Toast.makeText(this, "Invalid Login", Toast.LENGTH_SHORT).show();
}
This is my service which gets into the onHandleIntent method after the Toast is called. (Expecting this to be called first and validated before ever going to the Toast).
public class SyncService extends IntentService {
private VerificationHelper verificationHelper = new VerificationHelper();
private PropertiesUtil properties = new PropertiesUtil();
private RetrofitHelper retrofitHelper = new RetrofitHelper(verificationHelper, properties);
private static Call<User> staticCall;
public SyncService(Call<User> call) {
super("SyncService");
staticCall = call;
}
public SyncService(){
super("SyncService");
}
#Override
protected void onHandleIntent(Intent intent) {
retrofitHelper.performCallBackSync(staticCall);
}
}
This is over at my RetrofitHelper for reference.
public class RetrofitHelper {
private VerificationHelper verificationHelper;
private PropertiesUtil properties;
public RetrofitHelper(VerificationHelper verificationHelper, PropertiesUtil properties) {
this.verificationHelper = verificationHelper;
this.properties = properties;
}
public void performCallBackSync(Call<User> call){
try {
// some logic that handles and verifies token values.
verificationHelper.setValidLogin(true);
} catch (IOException e) {
//some error handling
}
}
}
I struggled with some issues about design complex tasks with fragments
use. Fragments and asynchronous approach are quite new for me, so I think it will be better to describe my app.
Application
App works with GitHub API and has two screens: list of repositories and details about selected one. For retrieving data from json I use Retrofit and store it in SQLite. Little remark As I understood Retrofit can be used asynchronously but in case of additional work with DB it is better to use asynchronous approach for operations under DB also. In my case I'm checking internet connection: in case of absence I load data from DB. Otherwise I upgrade DB and then use it. Now I want to add fragments for different screen density support( usual master - detail workflow).
And my questions are
Where is the better place to run async tasks? Is it a right solution to make it in activity and then pass result to fragments?
What is the better solution for asynchronous processing? As I understood from my search about that, AsyncTask is deprecated but the easiest solution.
AsyncTask is a pain in the rear. Many beginners still seem to use it, but imho it's not worth learning it. Sooner or later you'll be bugged out by it because AsyncTask is error prone, has lots of caveats and tons of boilerplate code.
Retrofit does make it's calls asynchronously automatically, so you've got that covered already. Retrofit also plays very nice with RxJava which is I guess considered the way of doing asynchronous things on Android these days.
RxJava has a steeper learning curve initially than other patterns, but it's worth learning. If you got your database stuff working already, it won't be much work making ti asynchronous with Rx.
As for
Is it a right solution to make it in activity and then pass result to
fragments?
If you don't follow an MVP design approach, which is okay, in my opinion it's absolutely okay to do 'business logic' stuff in the Fragment and not let the Fragment call the Activity, then let the Activity get back to the Fragment. Whichever is easier for you and suits your app.
You can place your background thread anywhere, but make sure it is cancelled if the class is garbage collected, and don't keep references (Context, callbacks) in your thread.
public class MyActivity extends Activity {
private static interface OnDownloadThreadCompleteListener {
public void onDone(String data);
}
private static class DownloaderThread extends AsyncTask<Void, Void, String> {
private OnDownloadThreadCompleteListener mListener;
public DownloaderThread(OnDownloadThreadCompleteListener listener) {
mListener = listener;
}
#Override
protected String doInbackground(Void... args) {
// Do your network request here
return result;
}
#Override
public void onPostExecute(String data) {
if (mListener != null && !isCancelled()) {
mListener.onDone(data);
}
mListener = null;
}
#Override
public void onCancelled() {
mListener = null;
}
}
private DownloaderThread mThread;
private OnDownloadThreadCompleteListener mListener;
#Override
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
mListener = new OnDownloadThreadCompleteListener() {
#Override
public void onDone(String data) {
Fragment fragment = getFragmentManager().findFragmentByTag("fragment_git");
fragment.show(data);
}
}
mThread = new DownloaderThread(mListener);
findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mThread.execute(null, null, null);
}
}
}
#Override
public void onPause() {
super.onPause();
if (mThread != null) {
mThread.cancel(true);
}
mThread = null;
mListener = null
}
}
I started learning about Android Development, and I read about static variables are bad and may leak memory because they are not garbage collectable.
I've used some in certain situations, but I am so concerned it may leak memory.
Can someone please look at my code below and see if they leak memory or not?
MainActivity.java
public class MainActivity extends Activity {
public static boolean IS_ACTIVITY_OPEN;
public static ImageView image;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
IS_ACTIVITY_OPEN = true;
....
....
VoiceReceiver = new BroadcastReceiver() {
#Override
public void onReceive(final Context context, Intent intent) {
.....
};
registerReceiver(VoiceReceiver, new IntentFilter(BroadCastReceivers.VoiceIntent));
#Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(VoiceReceiver);
IS_ACTIVITY_OPEN = false;
}
}
Picture.java
MainActivity.image.setImageBitmap(resizedBitmap);
.....
.....
BroadCast.java
if (!MainAcitivty.IS_ACTIVITY_OPEN) {
//start an activity
Intent intent2 = new Intent(context, MainAcitivty.class);
intent2.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent2);
handler= new Handler();
MyPostDelay = new Runnable() {
#Override
public void run() {
context.sendBroadcast(new Intent(VoiceIntent));
}
};
handler.postDelayed(MyPostDelay, 300);
}
else
{
context.sendBroadcast(new Intent(VoiceleIntent));
}
Thank you very much in advance.
Take a look at Avoiding memory leaks article on the Android Developers Blog. Keeping a static field holding a Context, or any other class that has a (strong) reference to a Context (such as any View) will mean that the garbage collector will not be able to reclaim the storage allocated by the Context. If the Context is an Application, thats OK because they live for as long as your app does and wouldn't be garbage collected anyway. But in case of Views, Context is likely an Activity which should be garbage collected as soon as possible.
That doesn't mean that all static fields will catastrophically leak memory. If they're primitive types, or simple classes, or even more complex classes with weak references to other classes, they might not prevent the garbage collector from reclaiming a lot of memory. But generally having static and especially public static fields is a code smell and should probably be avoided so the code is easier to maintain later.
After reading this article, I started thinking about memory leaks with Volley.
Usually, the listeners implemented with Volley have either an implicit or explicit reference to the outer class (the activity). for example:
JsonObjectRequest jsonObjReq = new JsonObjectRequest(Request.Method.GET,
url, null,
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
updateLayout();
}
}
in this case there is an implicit reference... or I may want to create a custom JsonObjectRequest to internalize the response handling, and need to pass in a reference to the calling activity in its constructor.
Now lets say I start a web request, but before the response comes back, I navigate away from the activity that started the request. From what I understand the JsonObjectRequest object would keep a reference to my activity and prevent it from being Garbage collected.
-Am I understanding this correctly, is this a legitimate fear?
-Does the Volley library automatically deal with this?
-If am creating a custom JsonObjectRequest and passing in a "this" (reference to activity), do I need to create a WeakReference to the activity?
Based on looking at the volley code, calling cancel doesn't really avoid the memory leak because the reference never gets cleared and the reference isn't weak. Calling cancel only avoids Volley from delivering the response to the listener.
My solution to the problem would have to be cloning and modifying the library myself.
One of the solutions can be to make base ErrorListener reference inside of base Request.java to be weak reference. And similarly the same can be done to the Listener inside of JsonRequest.java.
The other solution can be to manually clear the reference upon cancel being called. inside of cancel(), set the mErrorListener and mListener to null. With this solution though, you'll have to remove the final modifier from the field declaration otherwise you wouldn't be allowed to set the reference to null.
Hope this helps.
I hava faced memory leak when using volley just like the way you write here.everytime I new a new listener.
I used leakcanary to detect leaking.
When I read about your article, http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html ,and used WeakReference to activity and callback interface(myself customed), it solved.
I used volley as a singleton.
public interface VolleyCallback {
void onSuccess(JSONObject result);
void onFailed(String error);
}
private static class SListener implements Response.Listener<JSONObject> {
private final WeakReference<Activity> activityWeakReference;
private final WeakReference<VolleyCallback> callbackWeakReference;
public SListener(Activity activity, VolleyCallback callback) {
activityWeakReference = new WeakReference<Activity>(activity);
callbackWeakReference = new WeakReference<VolleyCallback>(callback);
}
#Override
public void onResponse(JSONObject jsonObject) {
Activity act = activityWeakReference.get();
VolleyCallback vc = callbackWeakReference.get();
if (act != null && vc != null) {
LogUtil.d(TAG, act.toString() + " " + jsonObject.toString());
something you need to do;
vc.onSuccess(jsonObject);
}
}
I also read this answer,How to use WeakReference in Java and Android development? ,the second answer give an example just like your article provide.It's good.
I am probably late to this party by a year but I just figured a way to solve this issue. Here it is:
public interface VolleyCallback {
void onSuccess(JSONObject result);
void onFailed(String error);
}
private static class SListener implements VolleyCallback {
private final WeakReference<MainActivity> activityWeakReference;
public SListener(MainActivity activity, VolleyCallback callback) {
activityWeakReference = new WeakReference<>(activity);
}
#Override
void onSuccess(JSONObject result){
}
#Override
void onFailed(String error){
}
}
here you can use activityWeakReference.get() to access all the UI elements of the MainActivity too.
Found this from http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html. This way we don't need to cancel any requests in onStop(). Remember to use activityWeakReference.get().isFinishing && activityWeakReference.get()!=null to avoid crashes when the activity does not exist.
I'm using another class to run some stuff in the background while the main activity is being displayed, and passing that activity's context to this background class. I'm starting another activity from this background class, but am unable to call overridePendingTransition here because "method overridePendingTransition(int, int) is undefined for the type BackgroundClass."
public class GetUPC extends AsyncTask<Void, Void, Void>
{
#Override
protected void onPreExecute()
{
...
}
#Override
protected Void doInBackground(Void... arg0)
{
...
boolean dairy;
if(theDairy.equals("N"))
{
//milk test
dairy=true;
}
else
{
dairy=false;
}
//depending on if there is a warning it will either display the warning screen or skip it
if(dairy)
{
Intent intent_warn = new Intent(context, WarningScreen.class);
intent_warn.putExtra("Name", str_name);
intent_warn.putExtra("Size", str_size);
intent_warn.putExtra("Price", str_price);
intent_warn.putExtra("Carbs", str_carbs);
intent_warn.putExtra("Protein", str_protein);
intent_warn.putExtra("Fiber", str_fiber);
intent_warn.putExtra("Sugar", str_sugar);
intent_warn.putExtra("SatFat", str_satFat);
intent_warn.putExtra("TotFat", str_totFat);
intent_warn.putExtra("Cholesterol", str_cholesterol);
intent_warn.putExtra("Sodium", str_sodium);
intent_warn.putExtra("Potassium", str_potassium);
intent_warn.putExtra("Calories", str_calories);
intent_warn.putExtra("Warning", "Contains Dairy");
intent_warn.putExtra("WarningRed", true);
Log.e("Warning",intent_warn.getExtras().getString("Warning"));
context.startActivity(intent_warn);
overridePendingTransition(R.layout.fade_in, R.layout.fade_out); //THIS PART ISN'T WORKING//
}
else
{
Intent intent_menu = new Intent(context, DisplayScreen.class);
intent_menu.putExtra("Name", str_name);
intent_menu.putExtra("Size", str_size);
intent_menu.putExtra("Price", str_price);
intent_menu.putExtra("Carbs", str_carbs);
intent_menu.putExtra("Protein", str_protein);
intent_menu.putExtra("Fiber", str_fiber);
intent_menu.putExtra("Sugar", str_sugar);
intent_menu.putExtra("SatFat", str_satFat);
intent_menu.putExtra("TotFat", str_totFat);
intent_menu.putExtra("Cholesterol", str_cholesterol);
intent_menu.putExtra("Sodium", str_sodium);
intent_menu.putExtra("Potassium", str_potassium);
intent_menu.putExtra("Calories", str_calories);
intent_menu.putExtra("Warning", "Contains no allergens");
intent_menu.putExtra("WarningRed", false);
Log.e("Warning",intent_menu.getExtras().getString("Warning"));
context.startActivity(intent_menu);
}
Log.e("KYLE_DATA_UPCH",str_name+" "+str_price+""+str_size);
}
}
catch (JSONException e)
{
e.printStackTrace();
}
}
else
{
Log.e("ServiceHandler", "Couldn't get any data from the url");
_errorCode=3;
}
return null;
}
#Override
protected void onPostExecute(Void result)
{
...
}
}
So I actually was able to solve the problem by calling overridePendingTransition on the context, casted to an Activity.
((Activity) context).overridePendingTransition(R.layout.fade_in, R.layout.fade_out);
I realize that this is not the best practice and could get messy with a more complex application, but for our purposes right now I think this is ok. I would like to investigate #bariscan Kayaoglu's solution eventually, as it seems more robust.
Just to add some safety to Tims answer, check if the context is an instance of Activity before casting it:
if (context instanceof Activity) {
((Activity) context).overridePendingTransition(R.layout.fade_in, R.layout.fade_out);
}
This simply makes sure that you call a method that is really there. You could also use a try/catch, but I figure this will be enough
Better to create an interface and callback its method.
myInterface mInterface;
public interface myInterface {
public abstract void myTask();
}
public GetUPC(myInterface mInterface) {
this.mInterface = mInterface;
}
and in your doInBackground method, when you are done, call
mInterface.myTask();
Don't forget to implement your interface to your activity and send this to your constructor when you are creating your async task.
myAsyncTask = new GetUPC(this);
And your development platform will inform you to implement unimplemented methods like myTask(). You can do whatever you want in that method while you can access your activity.
I don't think that would be a good idea. Since your class is an Async Task, the activity you would choose may not be the active activity since your async task will work in background. But if you consider memory leaks and null pointers, you can just send your activity to your constructor.
You can't do any UI updation part from background thread, But if wanted to do the same then override onProgressUpdate of AsyncTask class and past your activity starting code in that. to invoke this methode call publishProgress . Before starting activity you have to cancel your AsyncTask otherwise your application will cress.