【问题标题】:Adding a simple ScrollView to Gallery causes a memory leak将简单的 ScrollView 添加到 Gallery 会导致内存泄漏
【发布时间】:2012-01-17 19:05:48
【问题描述】:

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

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

适配器视图元素由一个 ImageView 和两个用于标题和描述的 TextView 组成。 由于照片的分辨率相当高,该应用程序使用了大量内存,但画廊通常能够很好地回收它们。

但是,当我现在为描述 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 保留了对 mParent 的引用,其中包括我使用的大照片

如果我使用 TextView 的滚动(android:scrollbars = "vertical" and .setMovementMethod(new ScrollingMovementMethod());

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

【问题讨论】:

    标签: android memory scrollview


    【解决方案1】:

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

    另外,this question,由 Dianne Hackborn(安卓工程师)回答,明确声明不要在画廊内使用可滚动视图。也许这个问题是为什么?

    【讨论】:

    • 感谢您的意见。认为这与我将得到的答案一样接近。但是,我认为它不会完全解决它。当我在 onfling 或当我从应用程序触发转换时,我可能会删除滚动视图。但是当使用 notifyDataSetChanged 修改适配器时会更加困难
    【解决方案2】:

    只需添加这个 -> 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">
    

    出现这种情况的原因有一些: 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 这有时会导致内存泄漏

    【讨论】:

    • 太棒了,这似乎解决了内存泄漏。但是,我无法自动滚动 TextView。这是我尝试使用的代码 slideshowDescription.setText(slideshowPhoto.getDescription());滚动条滚动条 = 新滚动条(上下文); slideshowDescription.setScroller(scroller); scroller.computeScrollOffset(); int descriptionBottom = slideshowDescription.getBottom(); scroller.startScroll(slideshowDescription.getScrollX(), slideshowDescription.getScrollY(), 0, descriptionBottom, 400);
    • 似乎我遇到了一个臭名昭著的问题,高度和其他属性未初始化,因此值为 0。应该能够围绕它进行编码。谢谢
    • 没有让这种方法发挥作用,但找到了解决方法,我将发布
    【解决方案3】:

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

    【讨论】:

    • 如果我从头开始,我也会使用 ViewFlipper。但是现在我非常致力于使用画廊,这个 ScrollView 内存泄漏让我很烦
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      最终实现了一种使用 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);
          }
      

      终于可以解决这个问题了:)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-24
        • 1970-01-01
        • 2015-03-08
        • 2019-08-26
        • 1970-01-01
        • 1970-01-01
        • 2021-03-23
        • 2021-09-25
        相关资源
        最近更新 更多