I have a FragmentStatePagerAdapter with a bunch of Fragments that are loaded and destroyed as the user swipes around. The Fragments each contain some text, and some very large images. I'm using Picasso to load and cache the images, and each Fragment has its own single instance of Picasso, which is shutdown in onDestroy().
When onDestroy() is called for each Fragment, I'd also like to totally clear the memory cache associated with the Picasso instance. I've tried creating a PicassoTools class like this answer says, and while that empties the cache (according to the debugger,) it doesn't seem to release the memory associated with the cache, according to the memory monitor. Here's my code for onDestroy():
#Override
public void onDestroy() {
PicassoTools.clearCache(picasso);
//release cache memory here somehow
picasso.shutdown();
super.onDestroy();
}
After I clear the cache, how can I totally release all the memory associated with it?
UPDATE: Here's my PicassoTools.clearCache() method, which is called onDestroy(). I added Bitmap recycle()ing, but that didn't seem to make a difference.
public static void clearCache(Picasso p) {
ArrayList<String> keys = new ArrayList<>();
keys.addAll(((LoopableLruCache) p.cache).keySet()); //LoopableLruCache is an extension of Picasso's LruCache, just with a keySet() method for looping through it easier
for (String key : keys) {
Bitmap bmp = p.cache.get(key);
if (!bmp.isRecycled()) {
bmp.recycle();
}
bmp = null;
p.invalidate(key);
}
p.cache.clear();
}
Related
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.
I read a lot about memory leaks in the last few days, and came across some interesting stuff.
I saw this answer to a basic Android bitmap-related memory leak question (the answer is from 2011) and I was wondering if this is still the case.
If I'm using views that contain bitmaps in my activity (ImageViews, TextViews...), do I really need to unbind their drawables when destroying the activity?
Is this only in some cases or always?
It's no longer necessary as of 4.0, as the callback is now stored in a WeakReference.
From 2.3.7:
public final void setCallback(Callback cb) {
mCallback = cb;
}
and in 4.0.1:
public final void setCallback(Callback cb) {
mCallback = new WeakReference<Callback>(cb);
}
Problem:
I'm saving some data in a singleton class... Sometimes it happens, that this singleton returns null data, from which I derive, that it was destroyed...
My idea/thoughts:
Actually, I thought, the singleton will live as long as the application lives and as long as the application remembers anything else like fragments state for example, my singleton will exist with it's last data too. Isn't this correct?
concrete problem:
My case is following: I go from my main fragment to a sub fragment and save an selected object in my singleton... I stop using my app and come back after some time. My app remembers it's state and recreates the fragments, my fragment want to get the selected object from my singleton and get's null.
I thought, a singleton should exist as long as the application exists and therefore needs no saving... Whenever the application is ended, nothing will be restored anyway and my app won't remember anything, so that's ok anyway. Is that a wrong assumption?
I need an answer to this question, because if I'm sure, that above thoughts are correct, I at least know, that I have to search for the problem somewhere else...
Here is a short summury of what I've found out (or have had forgotten)
Activitys can be recreated, although the application was destroyed
Singletons can be garbage collected if not referenzed from somewhere
So you HAVE TO SAVE your singletons! Otherwise, whenever your phone is on low memory, it may kill the application and create a NEW application, but RECREATE the activities...
For me, as I'm actually always use a single activity with fragments, it is easy to solve the problem:
when I create an activity, I call a static restore function (BEFORE calling get!!!)
in the onSaveInstanceState of the activity a always save the singleton to the bundle
=> so my singleton looks like following (base structure)
public class DataCache implements Parcelable
{
private static final String TAG = DataCache.class.getName();
private static DataCache mCache = null;
public static synchronized final DataCache get()
{
if (mCache == null)
mCache = new DataCache();
return mCache;
}
private DataCache()
{
// init...
}
public void save(Bundle outState)
{
outState.putParcelable(TAG, this);
}
public static void restore(Bundle savedInstanceState)
{
if (savedInstanceState != null && savedInstanceState.containsKey(TAG))
{
// only restore, if necessary, i.e. if application was destroyed but activity saved our last cache
if (mCache == null)
mCache = savedInstanceState.getParcelable(TAG);
}
}
}
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
I just read a blogpost by Romain Guy on how to avoid memory leaks in Android.
In the article he gives this example:
private static Drawable sBackground;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
Romain said:
This example is one of the simplest cases of leaking the Context.
My question is, how do you modify it correctly?
Just like this?
TextView label = new TextView(Context.getApplicationContext());
I tested both ways and the results are the same. I can't locate the difference. And I think that this is more correct than the Application context. Because this is a reference to Activity, that is to say, the TextView belongs to that Activity.
Could someone give me an explanation for this?
The actual problem with that code isn't the context passed to create the drawable, but private static Drawable sBackground;
The static Drawable is created with the Activity as the context, so in THAT case, there's a static reference to a Drawable that references the Activity, and that's why there's a leak. As long as that reference exists, the Activity will be kept in memory, leaking all of its views.
So it's the Drawable which should be created using the application context, not the TextView. Creating the TextView with "this" is perfectly fine.
edit : Actually, that might not make a big difference, the problem is that once the drawable is binded to a view, there's a reference to the view, which references the activity. So you need to "unbind" the drawable when you exit the activity.
I'm not sure if Romain had updated his blog entry since you read it, but he's pretty clear on how to avoid the leaks, even pointing you to an example in the Android OS. Note that I fixed the broken link in Romain's blog entry via archive.org.
This example is one of the simplest cases of leaking the Context and
you can see how we worked around it in the Home screen's source
code (look for the unbindDrawables() method) by setting the stored
drawables' callbacks to null when the activity is destroyed.
Interestingly enough, there are cases where you can create a chain of
leaked contexts, and they are bad. They make you run out of memory
rather quickly.
There are two easy ways to avoid context-related memory leaks. The
most obvious one is to avoid escaping the context outside of its own
scope. The example above showed the case of a static reference but
inner classes and their implicit reference to the outer class can be
equally dangerous. The second solution is to use the Application
context. This context will live as long as your application is alive
and does not depend on the activities life cycle. If you plan on
keeping long-lived objects that need a context, remember the
application object. You can obtain it easily by calling
Context.getApplicationContext() or Activity.getApplication().
In summary, to avoid context-related memory leaks, remember the
following:
Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the
activity itself)
Try using the context-application instead of a context-activity
Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside. The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
A garbage collector is not an insurance against memory leaks
Memory leaks at that code mostly happen when you rotate your screen (that is, changing the orientation state) so your activity was destroyed and created again for the new orientation. There's a lot of explanation about memory leaks.
You can take a look at one of the Google I/O 2011 video about Memory Management here. In the video, you can also use the memory management tools like Memory Analyzer available to download here.
I don't know if you are having any trouble with this in your app, but I have created a drop in solution that fixes all the android memory leak issues with standard android classes: http://code.google.com/p/android/issues/detail?id=8488#c51
public abstract class BetterActivity extends Activity
{
#Override
protected void onResume()
{
System.gc();
super.onResume();
}
#Override
protected void onPause()
{
super.onPause();
System.gc();
}
#Override
public void setContentView(int layoutResID)
{
ViewGroup mainView = (ViewGroup)
LayoutInflater.from(this).inflate(layoutResID, null);
setContentView(mainView);
}
#Override
public void setContentView(View view)
{
super.setContentView(view);
m_contentView = (ViewGroup)view;
}
#Override
public void setContentView(View view, LayoutParams params)
{
super.setContentView(view, params);
m_contentView = (ViewGroup)view;
}
#Override
protected void onDestroy()
{
super.onDestroy();
// Fixes android memory issue 8488 :
// http://code.google.com/p/android/issues/detail?id=8488
nullViewDrawablesRecursive(m_contentView);
m_contentView = null;
System.gc();
}
private void nullViewDrawablesRecursive(View view)
{
if(view != null)
{
try
{
ViewGroup viewGroup = (ViewGroup)view;
int childCount = viewGroup.getChildCount();
for(int index = 0; index < childCount; index++)
{
View child = viewGroup.getChildAt(index);
nullViewDrawablesRecursive(child);
}
}
catch(Exception e)
{
}
nullViewDrawable(view);
}
}
private void nullViewDrawable(View view)
{
try
{
view.setBackgroundDrawable(null);
}
catch(Exception e)
{
}
try
{
ImageView imageView = (ImageView)view;
imageView.setImageDrawable(null);
imageView.setBackgroundDrawable(null);
}
catch(Exception e)
{
}
}
// The top level content view.
private ViewGroup m_contentView = null;
}