FragmentStatePagerAdapter memory issue - android

I'm making an application with the ViewPager class and with a FragmentStatePagerAdapter adapter. I've read that the difference between the mentioned adapter and FragmentPagerAdapter is that the later stores all pages in memory at once, whereas FragmentStatePagerAdapter has only 3 loaded in memory at any given time.
So, here is the issue. I have a ViewPager with about 50 pages. There is a fragment on each page with a single ImageView image(and some other elements). After scrolling through around 20 unique pages, I usually get the Out Of Memory Error. So, my question is: How am I supposed to configure FragmentStatePagerAdapter to only have about 3 pages loaded in memory at any given time? This is the code for my adapter:
mViewPager.setAdapter(new FragmentStatePagerAdapter(fm) {
#Override
public Fragment getItem(int position) {
Song song = mSongs.get(position);
return PlayFragment.newInstance(position);
}
#Override
public int getCount() {
return mSongs.size();
}
#Override
public void destroyItem(View collection, int position, Object o) {
View view = (View)o;
((ViewPager) collection).removeView(view);
view = null;
}
#Override
public Object instantiateItem(View context, int position) {
ImageView imageView = new ImageView(getApplicationContext());
imageView.findViewById(R.id.albumimage);
imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), position));
((ViewPager) context).addView(imageView);
return imageView;
}
});
The destroyItem and instantiateItem methods currently do nothing. I've added them after reading about this from someone else's question. There is no difference as of now if I have these two methods in my code or not.
I've read other questions similar to mine, but I have finally decided to ask a question after having attempted to solve the problem on my own with no good results.
I tried setting the ImageView to null in onDestroy(), but nothing happened.

Bitmap created by BitmapFactory.decodeResource(getResources(), position) has to be released manually by calling Bitmap.recycle()
https://developer.android.com/training/displaying-bitmaps/manage-memory.html

I've started using bitmaps as input for ImageView. The code below works fine.
albumimg = BitmapFactory.decodeFile(mSong.getImg());
mImg.setImageBitmap(albumimg);
mImg.setVisibility(View.VISIBLE);
And this in onDestroy() and onDestroyView():
if(albumimg != null) {`albumimg.recycle(); }`
Thanks for the help. :)

Related

WebViews in ViewPager are not loaded/rendered until page is shown

I use a ViewPager with many WebViews, it is for showing an ePub. My Problem is that the WebViews are just rendered/loaded (im not quite sure) wenn their corresponding page becomes visible. After this the page doesn't need to be re-rendered until it is destroyed from the ViewPagerAdapter.
The consequence is that there is always a white page for a little while. How can i pre-render the page that it scrolls smooth to next webView (which was not rendered before).
This is my PagerAdapter:
public class MagazineReaderPagerAdapter extends PagerAdapter {
private MagazineReaderActivity activity;
private EpubDocument epub;
public MagazineReaderPagerAdapter(Context ctx, EpubDocument epub)
{
activity = (MagazineReaderActivity) ctx;
this.epub = epub;
}
#Override
public int getCount() {
return epub.getContentDocuments().size();
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
ReaderWebViewMulti view = new ReaderWebViewMulti(activity);
view.loadContentDocument(epub.getContentDocuments().get(position), epub);
((ViewPager) container).addView(view, 0);
return view;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view.equals(object);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((View) object);
}
}
ReaderWebViewMulti extends from WebView ind implements a method loadContentDocument wich loads the content via loadDataWithBaseURL.
edit:
At activity-oncreate the setOffscreenPageLimit is set to 3
viewPager.setOffscreenPageLimit(3);
To point out what the problem is, i made a little video on YouTube
From second 4 you can see that every page is just rendered when it is already visible. When i go back everything is fine.
I had the same problem and fixed it by disabling hardware acceleration for the web view:
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
See WebView inside ViewPager or ScrollView - weird rendering bug on Android 3.0+
I put some thoughts into this. Scrolling the screen 1 pixel would render the right page:
class SomeClass implements OnPageChangeListener {
private ViewPager viewPager;
...
viewPager = ...
viewPager.setOnPageChangeListener(this);
...
#Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
// viewpager finished scrolling to a page
viewPager.scrollBy(1, 0);
}
}
}
You would need to check if you are coming from right or left and according to this scroll by -1 or 1 pixels (the page where you are coming from will stay in memory, so you don't need to reload that one). Only at the the very first scroll (after opening the app) the problem described above will still persist.
Drawback: you can see the page scrolling 1 pixel, if you look real closely.
Someone with more time than me could dive into the android code to see what exactly happends when 1 pixel comes into the screen, and mimic some of this behaviour.
set this property for your application in manifest file
android:hardwareAccelerated="false"

Android ViewPager Memory leak

I need to create ViewPager in Android with 5 slides, each consists of image and text. I have an array with resources for images:
private static final int[] images = {R.drawable.tutorial_step_01, R.drawable.tutorial_step_02, R.drawable.tutorial_step_03, R.drawable.tutorial_step_04, R.drawable.tutorial_step_05, R.drawable.tutorial_step_06};
then I create adapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
LinearLayout tv = (LinearLayout) inflater.inflate(R.layout.tut_slide, null);
TextView title = (TextView) tv.findViewById(R.id.tut_title);
title.setText(getResources().getText(titles[position]));
TextView content = (TextView) tv.findViewById(R.id.tut_content);
ImageView image = (ImageView) tv.findViewById(R.id.tut_image);
slide_image = BitmapFactory.decodeResource(getResources(), images[position]);
image.setImageBitmap(slide_image);
content.setText(getResources().getText(contents[position]));
((ViewPager) container).addView(tv, 0);
return tv;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((LinearLayout) object);
//
}
trouble is that fact android don't want to collect image after I choose another page. So, after 10-15 changes it goes out with OutOfMemory exception. Then I added to initializung rows
if (slide_image!= null) {
slide_image.recycle();
System.gc();
}
And it's work good! But except one thing: I have black screen instead of first image, whcih is replaced by real one after few flips. So I don't know what to do with such memory leaking
Well, I solved the problem finally. I faced it with a very similar case and as I've seen so many questions related to the same problem, I chose this question as it's yet not answered.
The PagerAdapter should call the destroyItem method not only when it surpasses the offLimitScreenPageLimit but also when a screen rotation occurs, but it doesn't, so it has to be forced to do so... to achieve it, you just have to set to null the adapter on the onStop or onDestroy method of the activity.
#Override protected void onDestroy(){
pager.setAdapter(null);
}
Cheers!
It's not clear what you are using but I encountered a similar problem.
I'm assuming you are using FragmentPagerAdapter.
When you scroll away using that adapter, it does not destroy the pages out of view and out of cache. If there is an ImageView in a fragment used by FragmentPageAdapter, OOM is inevitable
Just change the extend of the adapter to
FragmentStatePagerAdapter
This will destroy the fragments not in use and leave more memory free for new fragments.
It's still not perfect, I have found that sometimes I can scroll faster than the garbage collector picks up the destroyed bitmaps, but its pretty damn close.
If I was looking to improve it, I would override destroyItem, and then get the bitmap in use from the imageview and .recycle the bitmap.
Recycle ImageView's Bitmap
That behaviour shouldn't be related with a memory leak. It looks like it's related to when and how you recycle bitmaps within your viewpager updating lifecycle. Try calling onPageSelected() or notifyDatasetChanged() manually at some point on your initialization.
This solutions might not solve the problem completely, but give it a try. It's hard to tell with your explanation.
In My case I have 31 page in ViewPager. I use this :
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
if(dbCon!=null)
dbCon.close();
ViewPager viewPager = (ViewPager)container;
View view = (View) object;
viewPager.removeView(view);
}
and everthing works fine. Alhamdulillah.

Outofmemeoryerror (viewpager + imageviews)

i am showing 150+ images in viewpager, when page size crossed 70 app is crashing ,
all images are loading from network , and i have fallowed [link]: Strange out of memory issue while loading an image to a Bitmap object
and i am recycling it whenever page swiping reaches 4,
for 70 page app taking 200 MB of memory.
i need help from you, how to handle it
i have to show all pages with swiping...
i have also used Runtime.getRuntime().gc();
is any way to releasing memory if app memory is reaches the 50+ MB
thanks in advance
The complete solution can be found below, the important lines are those in the destroyItem method:
private class ContentPagerAdapter extends PagerAdapter {
#Override
public void destroyItem(View collection, int position, Object o) {
View view = (View)o;
((ViewPager) collection).removeView(view);
view = null;
}
#Override
public void finishUpdate(View arg0) {
// TODO Auto-generated method stub
}
#Override
public int getCount() {
return ids.length;
}
#Override
public Object instantiateItem(View context, int position) {
ImageView imageView = new ImageView(getApplicationContext());
imageView.findViewById(R.id.item_image);
imageView.setImageBitmap(BitmapFactory.decodeResource(getResources(), ids[position]));
((ViewPager) context).addView(imageView);
return imageView;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view==((ImageView)object);
}
#Override
public void restoreState(Parcelable arg0, ClassLoader arg1) {
// TODO Auto-generated method stub
}
#Override
public Parcelable saveState() {
// TODO Auto-generated method stub
return null;
}
#Override
public void startUpdate(View arg0) {
// TODO Auto-generated method stub
}
I think this happens because you have a memory leak, double check your variables, don't use static vars for anything big, use final when possible, make all members private.
i suggest you make a commit (or save your current code) and then try to do what i asked and see if it fixes it.
a code sample would let me tell you if you have memory leaks, maybe you can post the code somewhere like on github or google code
Bottom line: you could be doing everything right but a variable still holds a reference to your images so the garbage collector can't touch them.
I know saying you have a memory leak hurts but please don't be alarmed this happens to the best of the best, because it's so easy to happen.
NOTE: No matter how big the data i load from network apps never needed more than the size of 1 file if handled correctly.
Thanks
Sheetal,
Having looked at your code can you try the following:
#Override
public void destroyItem(View collection, int position, Object o) {
Log.d("DESTROY", "destroying view at position " + position);
View view = (View) o;
((ViewPager) collection).removeView(view);
view = null;
}
This should release the imageView for garbage collection.
Are you loading your images in the onCreateView() view method?
The framework seems to takes care of the memory management requirements when doing it this way. I had tried loading my images in my FragmentPageAdapter passing them into my Fragment constructor preloaded or as part of the Fragment instaniateItem method but these both gave me the issue that you are facing. In the end I passed the information needed to load each image into the Fragment constructor and then used these details in the onCreateView() method.
Mark
Sheetal,
I don't have my code in front of me at the minute but it should be something similar to the following:
public class MyFragment extends Fragment {
private Resources resources; // I load my images from different resources installed on the device
private int id;
public MyFragment() {
setRetainInstance(true); // this will prevent the app from crashing on screen rotation
}
public MyFragment(Resources resources, int id) {
this(); // this makes sure that setRetainInstance(true) is always called
this.resources = resources;
this.id = id;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ImageView imageView = (ImageView)inflater.infale(R.layout.fragmentview, container, false);
imageView.setImageBitmap(BitmapFactory.decodeResource(resources, id));
return imageView;
}
}
This is pretty much off the top of my head, so it might not be exact, but it is pretty close.
If you need any more help just shout, don't forget to mark this answer as having helped you:).
Sheetal,
As requested, I use the above code as follows:
In my FragmentActivity I do the following in the onCreate() method:
pager = (ViewPager)findViewById(R.id.pager);
imageAdapter = new MyPagerAdapter(this, pager, theResources, listOfIds.pageBitMapIds);
imageAdapter.setRecentListener(this);
pager.setAdapter(imageAdapter);
Then in my PagerAdapter I do the following:
#Override
public Object instantiateItem(ViewGroup container, int position) {
MyFragment myFragment = new MyFragment(resources, resourceIds[position], recentListener, position);
myFragments[position] = myFragment;
return super.instantiateItem(container, position);
}
#Override
public Fragment getItem(int position) {
return myFragments[position];
}
And then I use the code in my previous answer above.
This is obviously not complete code and it has been adapted from my actual code, but it should give you a good idea of what to do and where to do it.
Mark

Is it possible to change the position of gridview images while swiping?

Myself trying to load the images(from webservice) in gridview and it was succesfully done.
I used Lazy Adapter to load all images from web service.
If i click on some image it should be displayed in next Activity and while swiping that image next image should be displayed. Any Ideas???
This can be relatively easy realised using a ViewPager, I'd say. Consider the flow as follows: When the user selects a list item, pass on a list of all the images (e.g. their URIs) and an index indicating the one selected to the activity containing the ViewPager. You can then initialise the ViewPager's adapter with the passed on list and set the current item to display to the index.
Have a look at the API demos for some more hints on how to use a ViewPager, or read the blog post on developers.android.com. In stead of feeding Fragments as Views to the ViewPager, simply instantiate an ImageView - you may find this Q/A on SO useful for some pointers on how to do that. Also, probably equally important to read through is the documentation on PagerAdapter.
//Edit: Some pointers for coding up above suggestion:
Have your onItemClick create a new Intent, add the relevant data for retrieving the images as extra as well as the selected index and start the Activity:
#Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent(this, ImageViewer.class);
intent.putExtra(PAGE_POSITION, position);
intent.putStringArrayListExtra(IMAGE_LIST, mImages)
// or add a serializable, e.g. an ArrayList<T> with your POJOs
intent.putExtra(IMAGE_LIST, mImages);
startActivity(intent);
}
In your ImageViewer Activity containing the ViewPager, retrieve all the extras and initialise the ViewPager to the given position/index:
if (getIntent() != null && getIntent().getExtras() != null {
mImagePosition = getIntent().getExtras().getInt(PAGE_POSITION, 0);
mImageList = getIntent().getExtras().getStringArrayList(IMAGE_LIST);
// or if you used Serializables
mImageList = (ArrayList<T>) getIntent().getExtras().getSerializable(IMAGE_LIST);
mViewPager.setAdapter(new ImagePagerAdapter());
mViewPager.setCurrentItem(mImageIndex);
}
In the ImagePaderAdapter you instantiate the ImageViews containing the images you want to display. There are tons of examples out there, but it'll look something like this in the basis:
private class ImagePagerAdapter extends PagerAdapter {
public int getCount() {
return mImageList == null ? 0 : mImageList.size();
}
public Object instantiateItem(View pager, int position) {
if (mInflater == null) mInflater = (LayoutInflater) container.getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = mInflater.inflate(R.layout.image_pager_item_layout, null, false);
ImageView photoImageView = (ImageView) view.findViewById(R.id.photo_imageview);
mImageLoader().loadImage(mImageList.get(position), photoImageView);
((ViewPager) pager).addView(view, 0);
return view;
}
public void destroyItem(View pager, int position, Object object) {
((ViewPager) pager).removeView((View) object);
}
public boolean isViewFromObject(View view, Object object) {
return view.equals(object);
}
public void finishUpdate(View container) { }
public void restoreState(Parcelable state, ClassLoader loader) { }
public void startUpdate(View container) { }
public Parcelable saveState() { return null; }
}
Note that I have not tested the code above, nor is it meant to be complete. For instance, I assume you know how to get a reference to a LayoutInflater yourself and have a 'lazy' image loader that asynchronously sets an image against an ImageView. The image_pager_item_layout file can simply be an ImageView, but also a more complex hierarchy of views inside a ViewGroup (like with ListView and GridView), e.g. you can easily add an caption to images. It's quite similar to

Android: Viewpager and FragmentStatePageAdapter

I'm designing an app that allows users to flip between multiple pages in a ViewPager. I've been struggling trying to figure out how it is possible to remove a Fragment instance from a page when it is no longer visible on screen, cache it (to, say, a HashMap), and then restore it so that when the user flips back to that page, the views and everything else in it will be in the same state it was before removal. For example, my first page is a login screen that makes certain layout elements on that particular page visible/invisible on a successful login. When I flip forward enough pages then flip back to the first page, the layout is reset. This becomes more of a problem for another one of my pages which contains a huge, horizontal/vertical scrolling grid of data that I use a thread in the background to draw when it initializes. I use a progress dialog to notify the user of loading progress and that becomes really annoying everytime I have to load it.
So I did some research...
I browsed through the source code for FragmentStatePageAdapter and in the destroyItem() callback, the state of the Fragment instance being removed is saved to an ArrayList. When a new instance of the Fragment is being created in the instantiateItem() callback, if an instance of an item doesn't already exist (they keep track of this by using an ArrayList), a new Fragment instance is created and its saved state is initialized with the corresponding Fragment.SavedState data. Unfortunately, this data does not include the state that the Views were in although I noticed that for pages with a GridView/ListView, the state of the Views were somehow restored (if I scrolled to some random position, flipped a few pages and came back, it would not be reset).
According to the API:
The saved state can not contain dependencies on other fragments --
that is it can't use putFragment(Bundle, String, Fragment) to store a
fragment reference because that reference may not be valid when this
saved state is later used. Likewise the Fragment's target and result
code are not included in this state.
Being a noob to Android, I'm not quite sure I understand the last statement.
That being said, is there any way to cache View state? If not, I think I'll just go ahead and go with leaving all the fragment pages in memory.
I had the same problem problem and solved it by implementing these two functions
public void onSaveInstanceState (Bundle outState)
public void onActivityCreated (Bundle savedInstanceState)
on the fragments that I wanted to save. On the first function, you should save in the Bundle the date that you need to restore the views ( in my case I had a bunch of spinner so I used
an int array to save their positions). The second function, which is called when restoring your fragment, is where you implement the restoring process.
I hope this helps. I also made my adapter to inherit from FragmentStatePageAdapter but I am not sure that this is mandatory.
Listing of main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:text="Page 1" android:id="#+id/textViewHeader"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:gravity="center" android:padding="10dip" android:textStyle="bold"></TextView>
<android.support.v4.view.ViewPager
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:id="#+id/viewPager" />
</LinearLayout>
Setting up the ViewPager
ViewPager viewPager = (ViewPager) findViewById(R.id.viewPager);
MyPagerAdapter adapter = new MyPagerAdapter(this);
viewPager.setAdapter(adapter);
The PagerAdapter
#Override
public void destroyItem(View view, int arg1, Object object) {
((ViewPager) view).removeView((View)object);
}
#Override
public int getCount() {
return views.size();
}
#Override
public Object instantiateItem(View view, int position) {
View view = views.get(position);
((ViewPager) view).addView(view);
return view;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
look here for more details view pager example
Looking at the various documentation pieces, my best guess is that the views you are creating do not have an ID attached to them. Assuming that the fragment's saved state is created from Fragment.onSaveInstanceState, then the fragment will automatically save any view's state that has an id. You probably have a default id associated with your ListView/GridView if you created them from a layout file. You can also associate an id with the views by calling setId.
Also, for your custom filled fragment, you may also have to do something custom in onSaveInstanceState.
Here's an example of how I implemented caching in PagerAdapter. After filling the cache all future view requests are served from cache, only data is replaced.
public class TestPageAdapter extends PagerAdapter{
private int MAX_SIZE = 3;
private ArrayList<SoftReference<View>> pageCache = new ArrayList<SoftReference<View>>(3);
public TestPageAdapter(Context context){
// do some initialization
}
#Override
public int getCount() {
// number of pages
}
private void addToCache(View view){
if (pageCache.size() < MAX_SIZE){
pageCache.add(new SoftReference<View>(view));
} else {
for(int n = (pageCache.size()-1); n >= 0; n--) {
SoftReference<View> cachedView = pageCache.get(n);
if (cachedView.get() == null){
pageCache.set(n, new SoftReference<View>(view));
return;
}
}
}
}
private View fetchFromCache(){
for(int n = (pageCache.size()-1); n>= 0; n--) {
SoftReference<View> reference = pageCache.remove(n);
View view = reference.get();
if (view != null) {
return view;
}
}
return null;
}
#Override
public Object instantiateItem(View collection, int position) {
View view = fetchFromCache();
if (view == null) {
// not in cache, inflate manually
LayoutInflater inflater = (LayoutInflater) collection.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.page, null);
}
setData(view, position);
((ViewPager) collection).addView(view, 0);
return view;
}
private void setData(View view, int position){
// set page data (images, text ....)
}
public void setPrimaryItem(ViewGroup container, int position, Object object) {
currentItem = (View)object;
}
public View getCurrentItem() {
return currentItem;
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((View) object);
}
#Override
public void destroyItem(View collection, int arg1, Object view) {
((ViewPager) collection).removeView((View) view);
addToCache((View) view);
}
}
I also ran into this problem when I was using PagerSlidingTabStrip and using and instance of FragmentPagerAdapter, switching to FragmentStatePagerAdapter definitely worked.
Then I use onSaveInstanceState() to save sate

Categories

Resources