Image adapter leaking memory - android

I have a simple ListActivity that shows images and I inizialize my OkHttpClient for Picasso Builder in the constructor of the ImageAdapter class:
picassoClient = new OkHttpClient();
picassoClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain
.request()
.newBuilder()
.addHeader("Cookie","xyz")
.build();
return chain.proceed(newRequest);
}
});
new Picasso.Builder(context).downloader(new OkHttpDownloader(picassoClient)).build();
then in getView() I use Picasso to load images in ImageView:
Picasso.with(context).load(xyzUrl).fit().centerCrop().into(vImage);
It works well, but on device's rotation i see that heap size sometimes slowly grows, sometimes quickly and sometimes remains stable. Only rarely it drops. Am i leaking memory or is there something wrong in code?
EDIT:
I inserted this code after Picasso's call in the getView()
if (BuildConfig.DEBUG) {
Log.i("HEAP SIZE",
String.valueOf((Runtime.getRuntime().totalMemory() / 1024)
- (Runtime.getRuntime().freeMemory() / 1024)));
}
and I found that the heap size's growth happens in the getView() after loading bitmap into ImageView.
What is wrong?
EDIT 2:
tried to set static ImageAdapter, nothing changes
EDIT 3:
tried with RecyclerView instead of ListView, same behavior: heap size grows continuously while scrolling image list stepping by 30-40 bytes at every onBindViewHolder(). After device's rotation heap size grows sometimes stepping by even 2-3 Mbytes. Rarely it drops.
Why heap size slowly but continuously grows and why am I leaking some cache or some cached bitmaps after device's rotation?
UPDATE:
tried adapter without the code in the constructor (that is without new OkHttpClient and new Picasso.Builder), it works and the heap size now drops well remaining stable. Then, what is the correct way to initialize the client with cookies headers management?
UPSHOT:
finally I created my PicassoInstance class, which creates a unique static Picasso singleton and set it as the Picasso Library's singleton. Then I set it in my adapter constructor
PicassoInstance.setPicassoSingleton(context);
It works well, and it is a correct way I hope.
public class PicassoInstance {
private static Picasso myPicassoInstance = null;
public static void setPicassoSingleton(Context context) {
if (myPicassoInstance == null) {
myPicassoInstance = createMyPicassoInstance(context);
Picasso.setSingletonInstance(myPicassoInstance);
if (BuildConfig.DEBUG) {
Log.i("PICASSO INSTANCE", "CREATED");
}
}
}
private static Picasso createMyPicassoInstance(Context context) {
OkHttpClient myOkHttpClient = new OkHttpClient();
myOkHttpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Request newRequest = chain.request().newBuilder()
.addHeader("Cookie", "xyz").build();
if (BuildConfig.DEBUG) {
Log.i("ON INTERCEPT", "COOKIE ADDED");
}
return chain.proceed(newRequest);
}
});
return new Picasso.Builder(context).downloader(
new OkHttpDownloader(myOkHttpClient)).build();
}
}

The picasso instance being returned from PicassoBuilder.build() should be a singleton, and when you need to use picasso throughout the app you should be accessing that singleton, instead of Picasso.with... you should be accessing
YourClass.getMyPicassoSingleton().with...
Otherwise you're keeping separate caches, etc for those picasso instances
edit: as I noted below, you can also call
picasso.setSingletonInstance(myPicasso);
right where you invoke the build method above, which would also solve your problem without holding onto the singleton yourself. that is probably a cleaner solution

I cannot close it as too broad, but I'd recommend you took a memory dump and gave it a long hard look in Eclipse Memory Analizer Tool to find which references are still being kept alive and by who.
This is also a great write up on it.
Adapters as fields are leaky. Views contain context which contains views. And fragments are even worse offenders. ListActivities were an API1 tool that should have been deprecated long ago. All very leak prone, but that's the Android way.

Related

Avoiding memory leak while calling retrofit 2

By following this article I found that calling Retrofit enqueue() on onCreate() method may cause a memory leak.
Here is what the article says, doing this:
Calling Retrofit in the main thread
public class MoviesActivity extends Activity {
private TextView mNoOfMoviesThisWeek;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_movies_activity);
mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);
MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();
repository.getMoviesThisWeek()
.enqueue(new Callback<List<Movie>>() {
#Override
public void onResponse(Call<List<Movie>> call,
Response<List<Movie>> response) {
int numberOfMovies = response.body().size();
mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
}
#Override
public void onFailure(Call<List<Movie>> call, Throwable t) {
// Oops.
}
});
}
}
Now if this network call runs on a very slow connection and before the call ends, the Activity is rotated or destroyed somehow, then the entire Activity instance will be leaked.
I tried to do the same thing on my app. I called a big content (240 objects) usign enqueue() in onCreate() method. Then while the content was loading I rotated the device multiple times and LeakCanary showed me a memory leak in the Activity as the article said.
Then I tried two approachs to avoid the memory leak:
First option
Calling retrofit execute() method on a background thread using static inner class.
Calling Retrofit in a background thread
private static class RetrofitCall extends AsyncTask<Void, Void, List<Show>> {
private WeakReference<TextView> numberOfShows;
public RetrofitCall(TextView numberOfShows) {
this.numberOfShows = new WeakReference<>(numberOfShows);
}
#Override
protected List<Show> doInBackground(Void... voids) {
List<Show> showList = new ArrayList<>();
if (!isCancelled()) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(TvMazeService.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
TvMazeService service = retrofit.create(TvMazeService.class);
try {
Response<List<Show>> response = service.getShows().execute();
if (response.isSuccessful()) {
showList = response.body();
}
return showList;
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onPostExecute(List<Show> shows) {
super.onPostExecute(shows);
TextView textView = numberOfShows.get();
if (textView != null) {
String number = String.valueOf(shows.size());
textView.setText(number);
}
}
}
Then I tried to get the memory leak using LeakCanary again and it happened that the memory leak was gone.
Second option
Using ViewModel.
As you can see in the documentation, while using ViewModel I called retrofit asynchronous in the ViewModel class and when the screen is rotated (activity is destroyed) it does not need to load the data again as it remains saved.
This approach also did not give the a memory leak and was the best while talking about memory.
Questions
1) Then, using ViewModel to call Retrofit is the best option and it really avoid memory leak?
2) Is there any problem to call retrofit using enqueue() in onCreate() as MoviesActivity does?
3) In this approaches, which one is the best to make a call to authenticate a user?
1) Using ViewModel in the correct way does not cause memory leaks and is a good option. You can see the google's video explanation, and also this lecture talking about the difference between MVP and MVVM. This second lecture gives a really good explanation about the topic.
2) Calling retrofit enqueue() in onCreate() is a problem and it causes a memory leak. The problem is that the first time you start your activity it calls retrofit, then when you rotate your device, all the activity is destroyed and recreated again. If you rotate the device before the data is loaded completed, retrofit will be called for the second time when onCreate() is called again, and if you keep doing it 10 times, retrofit will be called 10 times, and then you stop rotating the device. The result from the calls will start to come, bzzz :( the result will be displayed 10 times because you called it 10 times. This implies in a huge memory leak. If you implement this approach and use LeakCanary you will see the leak.
3) What is the best approach?
Using enqueue() method in onCreate() is definitely not good.
Static inner classes (using AsyncTask) is good, but it does not survive to configuration changes because you need to cancel it in onDestroy(). This is why it does not cause a memory leak because the Task is canceled in onDestroy().
MVP is a really good approach for making retrofit calls. You can learn more in this medium article and the source code is here.
Read about the differences between MVP and MVVM as in this article.
Finally, Google is advising devs to use ViewModel in these scenarios.
You can follow my discussion in another question. Where we are talking about the same subject but while sign in a user to the server.
The reason why you will got memory leaks if calling enqueue() in onCreate() is that the enqueued calls will hold a reference to your activity instance, because the callback instance(anonymous class) passed to it is holding a reference to the enclosing class instance. As long as you cancel it before onDestroy(), there won't be a problem.

How to make Volley NetworkImageView worke offline

I use Volley NetworkImageView to download images from internet and show in my listview. Now I want to make Volley NetworkImageView show saved images when there is no network available. Volley has already cached images by URL as a key because when I use
Entry entry = SingletonRequestQueue.getInstance(context).getRequestQueue().getCache().get(imageURL);
the entry.data is not null. But my problem is that image resolutions are high and I can not use
Bitmap b = BitmapFactory.decodeByteArray(entry.data, 0, entry.data.length);
because it creates a lot of lag and I have to reinvent the wheel because again I must create asynctask see when listview has scrolled to cancel decoding, recycling the bitmap, creating in memory cache, finding best insample value and ...
so better Idea is just do some tricks that make Volley NetworkImageView use its own DiskLRUCache to show them when there is no network.
Any idea?
My code:
public class SingletonRequestQueue {
private static SingletonRequestQueue mInstance;
private RequestQueue mRequestQueue;
private ImageLoader mImageLoader;
private static Context mCtx;
private LruBitmapCache mLruBitmapCache;
private SingletonRequestQueue(Context context) {
mCtx = context;
mRequestQueue = getRequestQueue();
mLruBitmapCache = new LruBitmapCache(LruBitmapCache.getCacheSize(context));
mImageLoader = new ImageLoader(mRequestQueue,mLruBitmapCache);
}
public static synchronized SingletonRequestQueue getInstance(Context context) {
if (mInstance == null) {
mInstance = new SingletonRequestQueue(context);
}
return mInstance;
}
public RequestQueue getRequestQueue() {
if (mRequestQueue == null) {
// getApplicationContext() is key, it keeps you from leaking the
// Activity or BroadcastReceiver if someone passes one in.
mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext(),new OkHttpStack());
// mRequestQueue = Volley.newRequestQueue(mCtx.getApplicationContext());
}
return mRequestQueue;
}
public <T> void addToRequestQueue(Request<T> req) {
getRequestQueue().add(req);
}
public ImageLoader getImageLoader() {
return mImageLoader;
}
public LruBitmapCache getLruBitmapCache() {
return mLruBitmapCache;
}
public void setLruBitmapCache(LruBitmapCache lruBitmapCache) {
mLruBitmapCache = lruBitmapCache;
}
}
and in my adapter:
public IssueListAdapter(Context context, int resource, List<Issue> objects) {
super(context, resource, objects);
this.context = context;
this.mIssueList = objects;
mImageLoader = SingletonRequestQueue.getInstance(context).getImageLoader();
}
public static class ViewHolder{
public NetworkImageView mNetworkImageView;
public TextView mFee;
public TextView mName;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
holder = new ViewHolder();
LayoutInflater inflater =
(LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.gridview_issuelist_item, parent, false);
holder.mNetworkImageView = (NetworkImageView)convertView.findViewById(R.id.NetworkImageView_MainActivity_issue_image);
holder.mName = (TextView)convertView.findViewById(R.id.TextView_MainActivity_name);
holder.mFee = (TextView)convertView.findViewById(R.id.TextView_MainActivity_fee);
Utility.settingTypfaceFont(context, holder.mName);
Utility.settingTypfaceFont(context, holder.mFee);
convertView.setTag(holder);
}else{
holder = (ViewHolder)(convertView.getTag());
}
final Issue issue = mIssueList.get(position);
holder.mName.setText(issue.getTitle());
holder.mFee.setText(String.valueOf(issue.getFee()));
String imageURL = issue.getPublicCover();
holder.mNetworkImageView.setImageUrl(imageURL, mImageLoader);
holder.mNetworkImageView.setDefaultImageResId(R.drawable.placeholder2);;
/*
Entry entry = SingletonRequestQueue.getInstance(context).getRequestQueue().getCache().get(imageURL);
if(entry != null && entry.data != null){
byte[] imageByte = entry.data;
loadBitmap(imageByte, holder.mNetworkImageView,imageURL);
}else{
holder.mNetworkImageView.setImageUrl(imageURL, mImageLoader);
}*/
return convertView;
}
#Override
public int getCount() {
if(mIssueList != null){
return mIssueList.size();
}
else{
return 0;
}
}
public List<Issue> getIssueList() {
return mIssueList;
}
}
I prefer to use Volley/retrofit with Android-Universal-Image-Loader
/Picasso, picture loader libs have done a great job in loading and caching images indeed.
They handle everything with a single line of code by default:
Picasso.with(context).load("http://i.imgur.com/DvpvklR.png").into(imageView);
Also you can animate, resize your images and add placeholder while they loading.
Just add this line in BasicNetwork class
if (!ConnectivityUtils.isNetworkEnabled(CardApplication.getContext()) && request instanceof ImageRequest) {
VolleyLog.e("Cached response", "No Network Connectivity for Url=", request.getUrl());
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED,
request.getCacheEntry().data, responseHeaders, true);
}
and for data request expiry you can change the Cached.Entry using using own HttpHeaderParser
Here is Link which explain thing in detail
When you restart your app in offline, the last thing you can rely on just the Disk Cache(i.e. DiskBasedCache). Volley's local cache consist of network data and the response headers. But in this situation, we just need to focusing on the Cache-Control header. For instance, if the server-side return that header is "Cache-Control: max-age=604800", that's tell Volley to cache the response resource for 604800 seconds( source at HttpHeaderParser.parseCacheHeaders() ). Then next time we retrieving the same url's data would checking if exceeded the cache expire time, finally decide retrieve from network or local.
Follow your describe, I suppose your server-side deliver you a value like Cache-Control:must-revalidate|proxy-revalidate|no-cache|no-store, that's why you can't reuse the last retrieved data when you were in offline.
Right now there is question came : once we can manipulate the cache expire time, we'll be capable of increase that time to a large enough value so we can ensure us use that data in offline.
Unfortunately, Volley does not support us to do this. So if you can make the server-side to delivering a viable max-age for this?
If not, I'd suggest you to change to another library which fulfill this desired. and there actually have one can be your friend, is Netroid. It's based on Volley and offered a few improvements, that won't make you change your current code very much. With it, control the expire time would be far easier, and more features would be come with.
mImageLoader = new SelfImageLoader(mRequestQueue, mLruBitmapCache) {
#Override
public void makeRequest(ImageRequest request) {
// you can manipulate the cache expire time with one line code.
request.setCacheExpireTime(TimeUnit.DAYS, 10);
// you can even according to the different request to
// set up the corresponding expire time.
if (request.getUrl().contains("/for_one_day/")) {
request.setCacheExpireTime(TimeUnit.DAYS, 1);
} else {
request.setCacheExpireTime(TimeUnit.DAYS, 10);
}
}
};
the full code was on the project's sample module, i hope this can be helpful.
Hello #mmlooloo I have created a project which use DiskLRUCache and Volley. Here's the link of my repository DiskLRUCache using Volley. Hope it will helps you to show saved image. Thanks.
Search internet for "android how to check internet connectivity"
implement that and check it in your cache implementation (like LruCache).
if(networkAvailable()){ getFromNetwork()} else { getFromCache()}
logic is ok? then just try.
It seems your cache impl class is LruBitmapCache.
then how about check connectivity in that class?
public Bitmap getBitmap(String url) {
if(networkAvailable()/* this is your impl */){
// dont use cache
return null;
}
return getFromCache(); // or something like that;
}
If I understand you correctly, you would benefit if the memory cache provided to the ImageLoader class that's used by your NetworkImageView will be persisted between app runs, without losing the fact that it's a memory cache.
That memory cache keeps the correctly sized bitmap in normal operation - which you would like available even if the network goes down.
So here's an idea: every time you're app is closed, persist on file the images from the cache. The next time you load your app, when you create the memory cache - check for a persisted version on the disk, and if it's available - populate the memory cache from the disk.
There are several approaches you can take to decide when to persist an image and when to delete it.
Here's one approach: create a hybrid memory / disk cache. It would work exactly the same as your memory cache works now with the following differences:
Every time putBitmap() is called, along with your normal operation, save an encoded version of the bitmap to the disk in a background thread / AsyncTask.
Every time a bitmap is removed from the cache (I'm assuming you have some sort of space constraint on the cache), delete that file from the disk on a background thread / AsyncTask.
Create a "loadFromDisk" task, to be performed in the background every time a memory cache is created (once per app run) to populate your memory cache with the available images from the disk.
You can't avoid decoding the bitmaps, however you can cut the size and having to deal with resizing large bitmaps.
Does this help you?

RoboSpice cache on memory difference object types

Have a look at LruCacheObjectPersister.java, this is an in-memory object persister, based on the Android LRUCache. I want to extend this base class to cache any pojos class beside String or bitmap in memory. My goal is execute many kinds of requests in memory and get the result in cache, it look like this:
class Request1 extends SpiceRequest<Book>{}
class Request2 extends SpiceRequest<Category>{}
getSpiceManager().execute(new Request1(), CACHE_KEY, 5 * DurationInMillis.ONE_MINUTE, new RequestListener<Book>(){});
getSpiceManager().execute(new Request2(), CACHE_KEY, 5 * DurationInMillis.ONE_MINUTE, new RequestListener<Category>(){});
My problem is how to tell the cache manager should support these type in spice service like this:
#Override
public CacheManager createCacheManager(Application application) {
CacheManager cacheManager = new CacheManager();
// I want to store any types instead of bitmap
LruCacheBitmapObjectPersister lruCacheBitmapObjectPersister = new LruCacheBitmapObjectPersister(inFileBitmapObjectPersister, 1024*1024);
cacheManager.addPersister(inFileBitmapObjectPersister);
return cacheManager;
}
How can I do this?
You should create a custom LruCacheXXXXObjectPersister and register it in a custom service. Follow how things are done with bitmaps, and keep the same mechanism for your own POJOs.

View Pager Memory Leak with Bitmaps and Volley

I'm using View Pager to show images which are downloaded from the network in my application. The number of images could be from 5 to 20. I'm using Volley library to do the network operations. The app wasn't taking much memory before but now after adding the view pager, the app takes a lot of memory and every time i open this activity, the memory used in heap increase (checked from the log messages). I also used Eclipse Memory analyzer to check where the leak was and it is definitely the bitmaps and the multiple instances of this activity. There is definitely a leak, as this activity isn't getting GC'ed, some references are keeping this from getting garbage collected. I've added my implementation of the view pager here.
public class ViewPagerAdapter extends PagerAdapter {
Context context;
public ViewPagerAdapter(Context context) {
this.context = context;
}
#Override
public int getCount() {
return photoReferences.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((RelativeLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
final ImageView im;
final ProgressBar pb;
View itemView = inflater.inflate(R.layout.place_photos_item, container, false);
im = (ImageView) itemView.findViewById(R.id.placeImage);
attributes = (TextView) itemView.findViewById(R.id.placeAttributes);
pb = (ProgressBar) itemView.findViewById(R.id.progressBarPhoto);
imageLoader.get(url, new ImageListener() {
public void onErrorResponse(VolleyError arg0) {
im.setImageResource(R.drawable.onErrorImage);
}
public void onResponse(ImageContainer response, boolean arg1) {
if (response.getBitmap() != null) {
im.startAnimation(AnimationUtils.loadAnimation(context, android.R.anim.fade_in));
im.setImageBitmap(response.getBitmap());
pb.setVisibility(View.GONE);
}
}
});
((ViewPager) container).addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((RelativeLayout) object);
}
}
Also, I'm using the Bitmap Cache of size 3 times the number of screenBytes(screenWidth * screenHeight * 4). I'm testing on Nexus 4 running 4.3 and I never run into a OOM exception cause the heap size is huge on this device but the app can take more than 100 mb of memory(it will crash on most devices) if I open the activity again and again, and before it used to take around 16-20 mbs of memory no matter what. Here's the cache code.
public class BitmapCache extends LruCache<Object, Object> implements ImageCache {
public BitmapCache(int maxSize) {
super(maxSize);
}
#Override
public Bitmap getBitmap(String url) {
return (Bitmap) get(url);
}
#Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
Could anyone please suggest me what should I do to catch the leak? Is there anything wrong in the View Pager or my Volley usage? I'm not happy with the transition of the Pager as well, lags a bit, is that related?
Update: Here's the screenshot of MAT, possible leak. This is on every activity that uses Volley library. I've been reading a lot but I couldn't solve the problem. Is volley causing leak or am I doing something terribly wrong?
You can find your leak by using MAT. First you run your app and leak a few activity instances. Then you grab a snapshot of the heap and look for those leaked Activity objects... you can use 'Object Query Language' (OQL) to find them by type (e.g. "SELECT * FROM com.foo.FooActivity").
Once you've found a leaked object, right-click on it and ask MAT to trace all its incoming references back to their GC roots. The leaked reference will be one of those.
For a better introduction to the technique you could try this article:
http://android-developers.blogspot.co.uk/2011/03/memory-analysis-for-android.html
I guess you are using using Viewpager and Imageviews
About image views you are using powerful image downloading and caching library like latest Volley Imageloading(really helpful for large size images) to improve the image loading capabilities in a efficient way.
About Viewpager you have to use efficient adapter FragmentStatePagerAdapter:
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
please think before you are using FragmentPagerAdapter becouse it stores the whole fragment in memory, and could increase a memory overhead if a large amount of fragments are used in ViewPager. In contrary its sibling, FragmentStatePagerAdapter only stores the savedInstanceState of fragments, and destroys all the fragments when they lose focus. Therefore FragmentStatePagerAdapter should be used when we have to use dynamic fragments, like fragments with widgets, as their data could be stored in the savedInstanceState. Also it wont affect the performance even if there are large number of fragments. In contrary its sibling FragmentPagerAdapter should be used when we need to store the whole fragment in memory. When I say the whole fragment is kept in memory it means, its instances wont be destroyed and would create a memory overhead. Therefore it is advised to use FragmentPagerAdapter only when there are low number of fragments for ViewPager. It would be even better if the fragments are static, since they would not be having large amount of objects whose instances would be stored. Hope this clears out the difference between Android FragmentPagerAdapter and FragmentStatePagerAdapter.
Try to learn Google android gallary app example, use image view loading animations to make a great user experience.
I hope this will solves your grow heap problems.
Credits:FragmentPagerAdapter vs FragmentStatePagerAdapter
You forget to recycle your downloaded Bitmaps as they become unneeded.
Basically, every Bitmap you handle manually, you have to recyle().
That being said, your destroyItem() method should look something like this:
public void destroyItem(ViewGroup container, int position, Object object) {
RelativeLayout rl = (RelativeLayout) object;
ImageView im = rl.findViewById(R.id.image_view);
bitmapDrawable = (BitmapDrawable) im.getDrawable();
if (bitmapDrawable != null && bitmapDrawable.getBitmap() != null) {
bitmap = bitmapDrawable.getBitmap();
bitmap.recycle();
}
container.removeView(rl);
}
You should check out the new version of Volley , old version did cause the leak problem.
In old version ,Volley has 4 thread do request , And each of them will keep a request , and request keep strong reference of listener , and your response listener do something with the ImageView , ImageView keep the Activity context. so all of your View is leaked.
In MAT use select * from instanceof android.app.Activity you will see your Activity is leaked.
New Version of Volley has fixed this problem . please check out here
And use this will help your find out your leaked Activity , leakcanary

Cancel All Volley Requests Android

At the moment i´m using mRequestQueue.cancelAll(getActivity()) at on stop method in a fragment but apparently when i move the phone from landscape to portrait it is still returning the data made in the request but causing crash because the holders for the data dosent exist anymore. any sample code of how to do it properly?
Instead of using a tag for cancelAll, make an all-pass RequestFilter.
mRequestQueue.cancelAll(new RequestQueue.RequestFilter() {
#Override
public boolean apply(Request<?> request) {
return true;
}
});
EDIT: This cancels all Requests from all activities/fragments, and doesn't work favorably with the Activity Lifecycle. The best way to manage this is to add a String tag unique to your fragment.
You should set the tag to an object, not a method.
By setting the tag to getActivity(), you are asking Volley to use a dynamic method call on the main thread as a reference to the request which is happening on a background thread.
So when the background thread is trying to cancel the requests, the activity could already be dead.
Rather than using getActivity(), use this or some other object or string.
This is good practice for any Tag, and you should also beware of leaking your activity.
Solutions:
You could use the current object:
request.setTag(this);
or, the static class object
request.setTag(MyFragment.class);
or, as a constant in a separate class:
request.setTag(CustomTags.LIST_REQUESTS);
CustomTags.LIST_REQUESTS being the best in my opinion (less chance of leaking activity)
Something like this:
public class CustomTags
{
public static final String LIST_REQUESTS="CustomTags:LIST_REQUESTS";
}
Update
I just noticed I was making a mistake in tagging my requests in Volley (though the solutions I posted above are fine).
I still thought I would update here an important thing to keep in mind. Volley tags by identity not value.
Thus, it is important to keep in mind that a tag that is merely the same string value, and not the same object itself, will not be recognized as the same tag.
It's similar to the difference between
String a1 = "A";
String a2 = "A";
a1 == a2; //evaluates to false
String a1 = "A";
String a2 = "A";
a1.equals(a2); // evaluates to true
I know this answer comes in late, but in case anyone else is having this problem:
In my implementation the Tag was being set (and overwritten) at the point where the request was added to the queue.
So despite that I was cancelling the request with my Tag, the tag on the request queue was not the same (as it was previously overwritten) and it was not cancelled.
Logging the requests running and printing out the tags, led me to the solution:
mRequestQueue.cancelAll(new RequestQueue.RequestFilter() {
#Override
public boolean apply(Request<?> request) {
Log.d("DEBUG","request running: "+request.getTag().toString());
return true;
}
});
Which tag did you use when making the requests? If you didn't set a tag on each of your requests it may never work. As far as I see, Volley does NOT automatically set a tag for your requests
If you add request to queue from framgment, you should cancel like this: mRequestQueue.cancelAll(this) . And sorry if it didn't work - i didn't test this solution. But i hope this help to you.
In Kotlin
requestQueue?.cancelAll { true }
Are you setting the tag of the requests to the activity? That's the only way the code you are providing will work. The cancelAll method searches all of the requests with the tag of whatever tag you provided and cancels them.
In Case Of Fragment;
Use only One
RequestQueue rQueue;
Initialize it in OnCreate method;
And use it for all volley request;
and at the end
#Override
public void onStop () {
super.onStop();
if (rQueue != null) {
rQueue.cancelAll(this);
}
}
I've struggled with the memory leak for the longest time until I found I called stop() from the class 'RequestQueue'.
//Initialize the object
RequestQueue requestQueue =
Volley.newRequestQueue(getActivity().getApplicationContext());
//Release the object
requestQueue.stop();
requestQueue = null;
The class says it "Stops the cache and network dispatchers." Whatever that means...

Categories

Resources