将简单的ScrollView添加到Gallery会导致内存泄漏

时间:2012-01-17 19:05:49

标签: android memory scrollview

在使用Gallery组件时,我遇到了只能归类为ScrollView元素的内存泄漏的内容。

简短背景。我有一个现有的应用程序是一个照片幻灯片应用程序。 它使用Gallery组件,但适配器中的每个元素都以全屏显示。 (完整资料来源于this link

适配器View元素包含一个ImageView,以及两个用于标题和描述的TextView。 由于这些照片分辨率非常高,因此该应用程序使用了相当多的内存,但Gallery通常会设法好好回收它们。

但是,当我现在为描述TextView实现ScrollView时,我几乎立即遇到了内存问题。这是我做的唯一改变

<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> 

我做了堆转储,可以清楚地看到Scrollview是内存问题的根源。

以下是堆转储分析的两个屏幕截图。请注意,ScrollView保留对mParent的引用,其中包含我使用的大型照片 Heap analysis - leak candidate Heap analysis - drilldown to a single ScrollView

如果我使用TextView的滚动(android:scrollbars =“vertical”和.setMovementMethod(new ScrollingMovementMethod()),PS会出现同样的问题;

PSS尝试关闭持久绘图缓存,但没有不同的dreaandroid:persistentDrawingCache =“none”

5 个答案:

答案 0 :(得分:4)

您是否尝试在容器视图滚出屏幕时删除滚动视图?我不确定这对你有用但值得一试吗?或者,尝试在滚动视图离开屏幕时调用setScrollContainer(false)。这似乎从mScrollContainers集中删除了视图。

此外,由Dianne Hackborn(安卓工程师)回答的this question明确表示不在Gallery中使用可滚动视图。也许这个问题是为什么?

答案 1 :(得分:3)

添加此内容 - &gt;机器人:isScrollContainer = “假”

<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">

有一些消息来源出现这种情况: http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/view/View.java

问题是:

setScrollContainer(boolean isScrollContainer)

默认情况下:

boolean setScrollContainer = false;  

但在某些情况下喜欢这个

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

它可能是真的,当它发生时

/ **  *更改此视图是否为其中一组可滚动容器  *它的窗口。这将用于确定窗口是否可以  *当软输入区域打开时,调整大小或必须平移 - 可滚动  *容器允许窗口使用自容器以来的调整大小模式  *将适当缩小。  * /

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) - 放入ArrayList的所有视图都会导致内存泄漏

答案 2 :(得分:2)

是的,我注意到了这个问题,对不起我以前的评论,我试图清空Drawables 通过设置上一个Drawable.setCallBack(null);但没有工作,顺便说一句,我有几乎相同的项目,我使用ViewFlipper而不是画廊,所以我可以控制每一件事,我只是在其中使用2个视图,并切换它们,没有内存泄漏,为什么不在显示之前调整图像大小,这样会减少内存使用量(在读取之前搜索SO以调整图像大小)

答案 3 :(得分:0)

尝试将TextView中的“android:layout_below =”@ id / slideshow_title“移动到ScrollView。

答案 4 :(得分:0)

最后实现了一个使用TextSwitcher的变通方法,该变换器每隔x秒自动更改为剩余的子字符串。

以下是布局

中的相关xml定义
        <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>

这里我将过渡动画添加到TextSwitcher(在适配器的getView方法中)

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);

以下是我如何交换描述的部分

        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)
        }           

    }

最后,我在这里设置了定时器,使其每3.5秒更新一次

        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);
    }

最后我可以把这个问题搁置一边:)