由GridView中的多个(> 120)getView调用引起的OutOfMemoryError

时间:2013-02-14 17:49:40

标签: android gridview user-interface out-of-memory

我正在使用GridView来显示图像。图像从源下载并添加到BitmapCache。 GridView位于ViewFlipper(其中有一个ListView作为第二个View)中。我第一次使用GridView,但是当我使用ListView时,我曾多次使用Adapters。

目前,Feed仅提供两张图片。但是,当我启动包含GridView的Fragment时,我得到一个OutOfMemoryError导致bei BitmapFactory.decodeStream()。当我深入研究logcat时,我注意到我的适配器里面的GridView的getView()被多次调用。 我知道如果多次调用getView()并没有什么特别之处,但我的适配器中的getView() - 方法仅在位置0被调用超过120次。我真的不明白为什么它经常被调用。但我很确定这会导致我的内存问题,因为此方法会在几秒钟内尝试加载位图超过100次。

由于我已经尝试使用ViewHolder回收我的视图,我现在很无奈,我希望有人能解释一下getView()的大量调用和/或可能会给我一个提示来解决我的问题

getView() - 我的适配器的方法:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;

    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

    if (convertView == null) {
        holder = new ViewHolder();
        convertView = inflater.inflate(R.layout.pictures_grid_item, parent, false);
        holder.image = (ImageView) convertView.findViewById(R.id.picturesGridImage);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
        holder.image.setImageBitmap(null);
    }

    Picture picture = (Picture) pictureList.get(position);
    String imageUrl = picture.getUrl();

    if (!TextUtils.isEmpty(imageUrl)) {
        holder.image.setTag(imageUrl);
        ImageLoader.getInstance(context).loadImageWithTagCheck(holder.image);
    }

    return convertView;
}


private static class ViewHolder {
    ImageView image;
}

loadImageWithTagCheck() -method只是检查图像是否已经下载(应该是这种情况)

包含视图的片段:

public class PicturesFragment extends BaseFragment {

private List<Parcelable> pictureList;
private PicturesGridAdapter adapter;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.pictures_fragment, container, false);
    // TODO: Remove final after development
    final MediaActivity activity = (MediaActivity) getActivity();

    pictureList = activity.getPictures();

    adapter = new PicturesGridAdapter(activity, pictureList);

    GridView gridview = (GridView) view.findViewById(R.id.picturesGrid);
    gridview.setAdapter(adapter);

    gridview.setOnItemClickListener(new OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
            Toast.makeText(activity, "" + position, Toast.LENGTH_SHORT).show();
        }
    });
    return view;
}
}

BTW:我没有在任何地方使用* wrap_content *。

编辑: 这是图像加载器的代码。当然,ImageLoader是导致outOfMemoryError的问题。但我认为问题恰恰与适配器有关,因为在创建视图之后,对位置0的120个getView()调用可能不对。并且适配器只创建一次,因此在我的适配器的单个实例中进行了> 120次调用。 (这是一个非常庞大而复杂的项目,所以“简单”的图像加载器有很多代码)

    public void loadImageWithTagCheck(final ImageView view) {
    final String url = (String) view.getTag();
    final Handler uiHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
        }
    };
    if (imageHandler != null) {
        imageHandler.post(new Runnable() {

            @Override
            public void run() {
                final Bitmap bmp = getImage(url, view);
                uiHandler.post(new Runnable() {

                    @Override
                    public void run() {
                        String tagUrl = (String) view.getTag();
                        if (tagUrl.equals(url) && bmp != null
                                && !bmp.isRecycled()) {
                            scaleBitmapAndAdjustViewByHeight(view, bmp);
                        } else if (bmp != null) {
                            bmp.recycle();
                        }
                    }
                });
            }
        });
    }
}

     private Bitmap getImage(String url, View v) {
        Bitmap bmp = null;

        if (url != null && !TextUtils.isEmpty(url)) {
            String md5Url = Utility.md5(url);
            if (cache.containsKey(md5Url)) {
                bmp = cache.getBitmap(md5Url);
            } else {
                HttpGet httpGet = new HttpGet();
                HttpClient httpClient = new DefaultHttpClient();

                HttpResponse response = null;
                try {
                    URI uri = new URI(url);
                    httpGet.setURI(uri);
                    response = httpClient.execute(httpGet);

                    if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                        HttpEntity entity = response.getEntity();
                        if (entity != null) {
                            final BufferedInputStream buffIn = new BufferedInputStream(
                                    entity.getContent(), Utils.IO_BUFFER_SIZE);
                            BitmapFactory.Options options = new BitmapFactory.Options();
                            options.inJustDecodeBounds = true;
                            options.outWidth = v.getWidth();
                            options.outHeight = v.getHeight();
                            options.inPurgeable = true;
                            options.inInputShareable = true;
                            options.inPreferredConfig = Bitmap.Config.RGB_565;

                            bmp = BitmapFactory.decodeStream(buffIn, null,
                                    options);
                        }
                    }
                } catch (URISyntaxException e) {
                    e.printStackTrace();
                } catch (ClientProtocolException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                }

                if (bmp != null) {
                    cache.put(md5Url, bmp);
                }
            }
        }

        return bmp;
    }

private void scaleBitmapAndAdjustViewByHeight(final ImageView view,
        final Bitmap bmp) {
    ViewTreeObserver vto = view.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        @SuppressLint("NewApi")
        @SuppressWarnings("deprecation")
        @Override
        public void onGlobalLayout() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(
                        this);
            } else {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(
                        this);
            }

            // Get current dimensions
            int width = bmp.getWidth();
            int height = bmp.getHeight();

            // Determine how much to scale: the dimension requiring less
            // scaling is closer to the its side. This way the image always
            // stays inside your bounding box AND either x/y axis touches
            // it.
            int imageViewHeightFromXMLinPixels = view.getHeight();
            float xScale = (float) ((imageViewHeightFromXMLinPixels * 2.75) / width);
            float yScale = ((float) imageViewHeightFromXMLinPixels)
                    / height;
            float scale = (xScale <= yScale) ? xScale : yScale;

            // Create a matrix for the scaling and add the scaling data
            Matrix matrix = new Matrix();
            matrix.postScale(scale, scale);

            // Create a new bitmap
            Bitmap scaledBitmap = Bitmap.createBitmap(bmp, 0, 0, width,
                    height, matrix, true);
            width = scaledBitmap.getWidth(); // re-use

            view.setImageBitmap(scaledBitmap);
            view.getLayoutParams().width = width;
        }
    });
    view.requestLayout();
}

1 个答案:

答案 0 :(得分:2)

摆脱scaleBitmapAndAdjustViewByHeight(...)方法。 相反,做一个简单的view.setImageBitmap(bmp)

为什么呢? scaleBitmapAndAdjustViewByHeight(...)调用view.requestLayout(),这可能导致调用适配器getView(...)并以死锁结束,最后是OutOfMemoryError。