In gridview adapter, getView(position == 0) was invoked too many times to measure layout when setImageBitmap() in a loader
Asked Answered
P

4

23

I have a GridView for showing some icons.

BEFORE I had read this Displaying Bitmaps Efficiently from Android developer site, I was decoding bitmap from local path directly in getView() of adapter, like this :

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

this way works fine anyway, I called it [Direct Mode], the output log for getView() method should be :

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

then I am trying to change the code to [Loader Mode] mentioned in article Displaying Bitmaps Efficiently, as following :

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

in loadIcon() :

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

in Loader's listener :

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

Basically, it is same as the training code, actually, this way works fine as well. However, I found something different, in this mode the getView() method in adapter was invoked too many times, however, these repeat call to this method always with "position" parameter == 0, it means something invoke getView(0, X, X) repeatedly.

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)

It is not good because I am using a loader in getView(). I have checked the source code and found they are originally called by imageView.setImageBitmap(result) in loader's onLoadComplete method, and in 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();
    }
}

here, requestLayout() is View's method and always executes in either [Direct Mode] or [Loader Mode], in View.class :

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

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

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

however the difference is: in [Direct Mode], the mParent.requestLayout() is invoked once, but in [Loader Mode], every time when i call imageView.setImageBitmap(result);, the mParent.requestLayout() will be invoked as well, it means mParent.isLayoutRequested() return false, and mParent.requestLayout(); will cause the GridView measure its kid's layout by calling obtainView() to first kid and then cause 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);
    ...

So, my question is: why mParent.isLayoutRequested() return false if I am using [loader mode]? or is it just a normal case ?

Persiflage answered 24/9, 2012 at 8:24 Comment(4)
As far as I know it is normal, Android can call any method of your adapter at any time and as many time as it likes. It even varies from version to version. Never assume anything about it. GridView is similar to ListView so check out this great talk youtube.com/watch?v=wDBM6wVEO70. Unless you are getting some crash or memory low error, don't worry about it and let it call your methods. And yes in [loader mode] android will refresh GUI later whenever onLoadFinish() is called.Saviour
Check "Settings>Developer Options>Show Layout Updates" and launch your app. If GUI is settled when your all loaders are finished then you are good to go.Saviour
Did you find a way around this? I get this too.Mccarley
possible duplicate of custom listview adapter getView method being called multiple times, and in no coherent orderGliadin
L
7

isLayoutRequested is just there to tell you if a layout is already pending for this View. That is, after requestLayout is called, isLayoutRequested will return true until the next layout pass completes. The only reason for this check in requestLayout is to avoid repeatedly calling requestLayout on the parent if it's about to do layout anyway. isLayoutRequested is a red herring here: it's not the cause of onMeasure being called repeatedly.

The root problem is that ImageView requests a new layout whenever you change its drawable. This is necessary for two reasons:-

  1. The size of the ImageView might depend on that of the drawable, if adjustViewBounds is set. This might in turn affect the sizes of other views, depending on the layout: the ImageView doesn't itself have enough information to know.
  2. ImageView.onMeasure is responsible for working out how much the drawable has to be resized to fit in the ImageView's bounds, according to the scale mode. If the new drawable isn't the same size as the old drawable, the ImageView must be measured again to recompute the required scaling.

You can only fix the problem of having too many loaders by keeping a local cache of Bitmaps returned by the loaders. The cache might have all the Bitmaps if you know there aren't that many, or just the n most recently used ones. In your getView, first check if the Bitmap for that item exists in the cache, and if so, return an ImageView already set to that Bitmap. Only if it's not in the cache do you need to use a loader.

Be careful: if the underlying data can change, you now need to make sure to invalidate the cache at the same time as calling invalidate on the GridView or notifying through ContentResolver. I've used some homebrew code to achieve this in my app, and it works nicely for me, but the good folks at Square have an open-source library called Picasso to do all the hard work for you if you prefer.

Leadership answered 9/8, 2013 at 11:32 Comment(0)
F
1

This is normal behavioral, android can call getView for same position multiple time. It's on developer to get/set thumbnail in getView only when required (i.e. If thumbnail was not set Or thumbnail path has changes). In other cases just return convertView, which we get as parameter in getView.

Furlani answered 2/1, 2013 at 16:14 Comment(0)
S
1

I had the same problem. Grid is always measuring its first child, even if I am in 30 position.
I just bypass the whole getView code by adding this check in getView top:

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

This does not stop getView being called, but at least texts, images and layout changes don' t run.

Stav answered 11/11, 2015 at 12:55 Comment(1)
If you can have more than one item per row, change the "1" with the max number of items per row.Antilles
W
0

try adjust your xml layout on anyone place that references the height, because the android will do a measure to draw eachtime and will redraw the cell up again. Try use on listview match_parent and on cell at row an exactly height. sorry my bad english.

Wiegand answered 25/5, 2013 at 20:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.