【问题标题】:In gridview adapter, getView(position == 0) was invoked too many times to measure layout when setImageBitmap() in a loader在 gridview 适配器中,getView(position == 0) 在加载器中的 setImageBitmap() 被调用太多次来测量布局
【发布时间】:2012-09-24 08:24:10
【问题描述】:

我有一个GridView 用于显示一些图标。

在我从 Android 开发者网站阅读此 Displaying Bitmaps Efficiently 之前,我直接在适配器的 getView() 中从本地路径解码位图,如下所示:

public View getView(int position, View convertView, ViewGroup parent) {
      ...
      ImageView icon = ...... (from getTag() of convertView)
      icon.setImageBitmap(BitmapUtil.decode(iconPath));
      ...
}

这种方式无论如何都很好,我称之为[直接模式],getView()方法的输出日志应该是:

getView(0)   // measure kid's layout.
getView(0)
getView(1)
getView(2)
...
getView(n)       // when scrolling gridview.
getView(n+1)
...
getView(n+3)    // scrolling again.
getView(n+4)
...

然后我正在尝试将代码更改为文章Displaying Bitmaps Efficiently中提到的[加载器模式],如下:

public View getView(int position, View convertView, ViewGroup parent) {
      ...
      ImageView icon = ...... (from getTag() of convertView)
      loadIcon(icon, iconPath);
      ...
}

loadIcon()

...
final CacheImageLoader loader = new CacheImageLoader(getActivity(), imageView, imageUrl, savePath);
final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), placeHolderBitmap, loader);
imageView.setImageDrawable(asyncDrawable);

在 Loader 的监听器中:

@Override
public void onLoadComplete(Loader<Bitmap> arg0, Bitmap arg1) {
    ...
    ImageView imageView = imageViewReference.get();
    if (result != null && imageView != null) {
        imageView.setImageBitmap(result);
    }
}

基本上和训练代码一样,其实这种方式也可以。但是,我发现了一些不同的东西,在这种模式下,适配器中的getView() 方法被调用了太多次,但是,这些对该方法的重复调用总是使用“位置”参数== 0,这意味着某些东西会重复调用 getView(0, X, X) .

getView(0)     // measure kid's layout.
getView(0)
getView(1)
getView(2)
...    
getView(0)     // loader completed then imageView.setImageBitmap(result); 
getView(0)     // same as above
getView(0)
getView(0)
...
getView(n)     // when scrolling gridview.
getView(n+1)
getView(n+2)
getView(0)     // loader completed then imageView.setImageBitmap(result); 
getView(0)     // same as above
getView(0)
...
getView(n+3)   // scrolling again.
getView(n+4)
getView(0)     // loader completed then imageView.setImageBitmap(result); 
getView(0)     // same as above
getView(0)

这不好,因为我在getView() 中使用了加载器。我检查了源代码,发现它们最初是由 imageView.setImageBitmap(result) 在 loader 的 onLoadComplete 方法和 ImageView 中调用的:

 /**
 * Sets a drawable as the content of this ImageView.
 * 
 * @param drawable The drawable to set
 */
public void setImageDrawable(Drawable drawable) {
        ...

        int oldWidth = mDrawableWidth;
        int oldHeight = mDrawableHeight;

        updateDrawable(drawable);

        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }
}

这里,requestLayout() 是 View 的方法,总是在 View.class 中的 [Direct Mode] 或 [Loader Mode] 中执行:

public void requestLayout() {
    mPrivateFlags |= FORCE_LAYOUT;
    mPrivateFlags |= INVALIDATED;

    if (mLayoutParams != null) {
        mLayoutParams.onResolveLayoutDirection(getResolvedLayoutDirection());
    }

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
}

但不同的是:在[直接模式]中,mParent.requestLayout()被调用一次,而在[加载器模式]中,每次我调用imageView.setImageBitmap(result);时,mParent.requestLayout()都会也被调用,这意味着mParent.isLayoutRequested() 返回falsemParent.requestLayout(); 将导致GridView 通过调用obtainView() 到第一个孩子然后导致getView(0, X, X) 测量其孩子的布局:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
    final int count = mItemCount;
    if (count > 0) {
        final View child = obtainView(0, mIsScrap);
    ...

所以,我的问题是:如果我使用[加载器模式],为什么mParent.isLayoutRequested() 会返回false?还是只是正常情况?

【问题讨论】:

  • 据我所知正常,Android可以随时调用你适配器的任何方法,想调用多少次就调用多少次。它甚至因版本而异。永远不要假设任何事情。 GridViewListView 类似,因此请查看这篇精彩的演讲 youtube.com/watch?v=wDBM6wVEO70。除非您遇到崩溃或内存不足错误,否则不要担心,让它调用您的方法。是的,在 [加载器模式] 中,每当调用 onLoadFinish() 时,android 都会稍后刷新 GUI。
  • 检查“设置>开发人员选项>显示布局更新”并启动您的应用程序。如果所有加载程序都完成后 GUI 已解决,那么您就可以开始了。
  • 您找到解决方法了吗?我也明白了。

标签: android android-view android-gridview


【解决方案1】:

isLayoutRequested 只是用来告诉您此View 的布局是否已挂起。也就是说,在调用requestLayout 之后,isLayoutRequested 将返回 true,直到下一次布局传递完成。在requestLayout 中进行此检查的唯一原因是避免在父级无论如何都要进行布局时重复调用requestLayoutisLayoutRequested 在这里是一个红鲱鱼:这不是onMeasure 被反复调用的原因。

根本问题是ImageView 会在您更改其可绘制对象时请求新布局。这是必要的,原因有两个:-

  1. 如果设置了adjustViewBounds,ImageView 的大小可能取决于drawable 的大小。这可能反过来影响其他视图的大小,具体取决于布局:ImageView 本身没有足够的信息来了解。
  2. ImageView.onMeasure 负责根据缩放模式计算可绘制对象的大小必须调整多少以适应 ImageView 的边界。如果新的 drawable 与旧的 drawable 大小不同,则必须再次测量 ImageView 以重新计算所需的缩放比例。

您只能通过保留加载程序返回的Bitmaps 的本地缓存来解决加载程序过多的问题。如果您知道没有那么多,或者只有最近使用的 n 个,缓存可能包含所有 Bitmaps。在您的getView 中,首先检查该项目的Bitmap 是否存在于缓存中,如果存在,则返回已设置为BitmapImageView。只有不在缓存中时才需要使用加载器。

注意:如果基础数据可能发生变化,您现在需要确保在调用invalidate 上的invalidate 或通过ContentResolver 通知的同时使缓存无效。我已经在我的应用程序中使用了一些自制代码来实现这一点,它对我来说效果很好,但是 Square 的好人有一个名为 Picasso 的开源库,如果你愿意,可以为你完成所有艰苦的工作。

【讨论】:

    【解决方案2】:

    这是正常行为,android 可以多次调用同一位置的 getView。开发人员仅在需要时才能在 getView 中获取/设置缩略图(即,如果未设置缩略图或缩略图路径发生更改)。在其他情况下,只需返回 convertView,我们将其作为 getView 中的参数。

    【讨论】:

      【解决方案3】:

      我遇到了同样的问题。 Grid 总是在测量它的第一个孩子,即使我在 30 的位置。
      我只是通过在 getView 顶部添加此检查来绕过整个 getView 代码:

      @Override
      public View getView(final int position, View convertView, ViewGroup parent) {
          // Patch for multiple getView for position 0
          if(convertView!=null && position==0 && viewGrid.getFirstVisiblePosition()>1) return convertView;
      

      这不会停止调用 getView,但至少文本、图像和布局更改不会运行。

      【讨论】:

      • 如果每行可以有多个项目,请将“1”更改为每行的最大项目数。
      【解决方案4】:

      尝试在任何引用高度的地方调整您的 xml 布局,因为 android 每次都会进行测量并重新绘制单元格。 尝试在 listview match_parent 和行上的单元格上使用一个精确的高度。 对不起,我的英语不好。

      【讨论】:

        猜你喜欢
        • 2014-11-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-27
        • 2013-07-11
        • 1970-01-01
        • 2015-04-11
        • 1970-01-01
        相关资源
        最近更新 更多