Home » Android » android – Adding a simple ScrollView to Gallery causes a memory leak

android – Adding a simple ScrollView to Gallery causes a memory leak

Posted by: admin June 15, 2020 Leave a comment

Questions:

I’ve run into what I can only categorize as a memory leak for ScrollView elements when using the Gallery component.

A short background. I’ve got an existing app that is a photo slideshow app.
It uses the Gallery component, but each element in the adapter is displayed in full-screen.
(full source is available at this link)

The adapter View element consist of an ImageView, and two TextViews for title and description.
As the photos are of a quite high-resolution, the app uses quite a lot of memory but the Gallery has in general manage to recycle them well.

However, when I am now implementing a ScrollView for the description TextView, I almost immediately run into memory problems. This the only change I made

<ScrollView
android:id="@+id/description_scroller"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:fillViewport="true">
  <TextView
    android:id="@+id/slideshow_description"
    android:textSize="@dimen/description_font_size"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textColor="@color/white"
    android:layout_below="@id/slideshow_title"
    android:singleLine="false"
    android:maxLines="4"/>  
</ScrollView> 

I did a heap dump and could clearly see that it was the Scrollview which was the root of the memory problems.

Here are two screenshots from the heap dump analysis. Note that the ScrollView retains a reference to mParent which includes the large photo I use
Heap analysis - leak candidate
Heap analysis - drilldown to a single ScrollView

PS same problem occurs if I use the TextView’s scrolling (android:scrollbars = “vertical” and .setMovementMethod(new ScrollingMovementMethod());

PSS Tried switching off persistent drawing cache, but no different dreaandroid:persistentDrawingCache=”none”

How to&Answers:

Just add this -> android:isScrollContainer=”false”

<ScrollView
    android:id="@+id/description_scroller"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:scrollbars="vertical"
    android:fillViewport="true"
    android:isScrollContainer="false">

There is some source why this is appear:
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/view/View.java

the problem is:

setScrollContainer(boolean isScrollContainer)

by default:

boolean setScrollContainer = false;  

but in some cases like this

if (!setScrollContainer && (viewFlagValues&SCROLLBARS_VERTICAL) != 0) {
    setScrollContainer(true);
}

it can be true, and when it happends

/**
* Change whether this view is one of the set of scrollable containers in
* its window. This will be used to determine whether the window can
* resize or must pan when a soft input area is open — scrollable
* containers allow the window to use resize mode since the container
* will appropriately shrink.
*/

public void setScrollContainer(boolean isScrollContainer) {
    if (isScrollContainer) {
        if (mAttachInfo != null && (mPrivateFlags&SCROLL_CONTAINER_ADDED) == 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= SCROLL_CONTAINER_ADDED;
        }
        mPrivateFlags |= SCROLL_CONTAINER;
    } else {
        if ((mPrivateFlags&SCROLL_CONTAINER_ADDED) != 0) {
            mAttachInfo.mScrollContainers.remove(this);
        }
        mPrivateFlags &= ~(SCROLL_CONTAINER|SCROLL_CONTAINER_ADDED);
    }
}

mAttachInfo.mScrollContainers.add(this) – all view put into ArrayList this lead to leak of memory sometimes

Answer:

Have you tried removing the scroll view whenever it’s container view scrolls off the screen? I’m not sure if that works for you but its worth a shot? Alternatively, try calling setScrollContainer(false) on the scroll view when it leaves the screen. That seems to remove the view from the mScrollContainers set.

Also, this question, answered by Dianne Hackborn (android engineer), explicitly states not to use scrollable views inside of a Gallery. Maybe this issue is why?

Answer:

Yes i noticed the problem, sorry for my previous comment, i’ve tried to empty the Drawables
by setting previous Drawable.setCallBack(null); but didnt work, btw i have nearly the same project, i use ViewFlipper instead of Gallery, so i can control every thing, and i just use 2 Views in it, and switch between them, and no memory leak, and why not you resize the Image before displaying it, so it will reduce memory usage (search SO for resizing Image before reading it)

Answer:

Try moving “android:layout_below=”@id/slideshow_title” in TextView to ScrollView.

Answer:

Ended up with implementing a workaround that uses a TextSwitcher that is automatically changed to the remaining substring every x seconds.

Here is the relevant xml definition from the layout

        <TextSwitcher 
        android:id="@+id/slideshow_description"
        android:textSize="@dimen/description_font_size"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
            <TextView
            android:id="@+id/slideshow_description_anim1"
            android:textSize="@dimen/description_font_size"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxLines="2"
            android:textColor="@color/white"
            android:singleLine="false"/>
                        <TextView
            android:id="@+id/slideshow_description_anim2"
            android:textSize="@dimen/description_font_size"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxLines="2"
            android:textColor="@color/white"
            android:singleLine="false"/>
    </TextSwitcher>

Here I add the transition animation to the TextSwitcher (in the adapter’s getView method)

final TextSwitcher slideshowDescription = (TextSwitcher)slideshowView.findViewById(R.id.slideshow_description);
            Animation outAnim = AnimationUtils.loadAnimation(context,
                    R.anim.slide_out_down);
            Animation inAnim = AnimationUtils.loadAnimation(context,
                    R.anim.slide_in_up);        

            slideshowDescription.setInAnimation(inAnim);
            slideshowDescription.setOutAnimation(outAnim);

Here is how I swap to the part of the description

        private void updateScrollingDescription(SlideshowPhoto currentSlideshowPhoto, TextSwitcher switcherDescription){
        String description = currentSlideshowPhoto.getDescription();

        TextView descriptionView = ((TextView)switcherDescription.getCurrentView());
        //note currentDescription may contain more text that is shown (but is always a substring
        String currentDescription = descriptionView.getText().toString();

        if(currentDescription == null || description==null){
            return;
        }

        int indexEndCurrentDescription= descriptionView.getLayout().getLineEnd(1);      

        //if we are not displaying all characters, let swap to the not displayed substring
        if(indexEndCurrentDescription>0 && indexEndCurrentDescription<currentDescription.length()){
            String newDescription = currentDescription.substring(indexEndCurrentDescription);
            switcherDescription.setText(newDescription);    
        }else if(indexEndCurrentDescription>=currentDescription.length() && indexEndCurrentDescription<description.length()){
            //if we are displaying the last of the text, but the text has multiple sections. Display the  first one again
            switcherDescription.setText(description);   
        }else {
            //do nothing (ie. leave the text)
        }           

    }

And finally, here is where I setup the Timer which causes it to update every 3.5 seconds

        public void setUpScrollingOfDescription(){
        final CustomGallery gallery = (CustomGallery) findViewById(R.id.gallery);
        //use the same timer. Cancel if running
        if(timerDescriptionScrolling!=null){
            timerDescriptionScrolling.cancel();
        }

        timerDescriptionScrolling = new Timer("TextScrolling");
        final Activity activity = this;
        long msBetweenSwaps=3500;

        //schedule this to 
        timerDescriptionScrolling.scheduleAtFixedRate(
            new TimerTask() {
                int i=0;
                public void run() {                     
                    activity.runOnUiThread(new Runnable() {
                        public void run() {
                            SlideshowPhoto currentSlideshowPhoto = (SlideshowPhoto)imageAdapter.getItem(gallery.getSelectedItemPosition());

                            View currentRootView = gallery.getSelectedView();
                            TextSwitcher switcherDescription = (TextSwitcher)currentRootView.findViewById(R.id.slideshow_description);

                            updateScrollingDescription(currentSlideshowPhoto,switcherDescription);

                            //this is the max times we will swap (to make sure we don't create an infinite timer by mistake
                            if(i>30){
                                timerDescriptionScrolling.cancel();
                            }
                            i++;
                        }
                    });

                }
            }, msBetweenSwaps, msBetweenSwaps);
    }

Finally I can put this problem to a rest 🙂