3

我正在使用 GridView 来显示图像。图像从提要下载并添加到 BitmapCache。GridView 位于 ViewFlipper 内(它有一个 ListView 作为第二个视图)。我是第一次使用 GridView,但是当我使用 ListViews 时,我已经多次使用 Adapters。

目前,Feed 只提供两张图片。但是当我启动包含 GridView 的片段时,我得到一个 OutOfMemoryError 导致 bei BitmapFactory.decodeStream()。当我更深入地查看 logcat 时,我注意到我的 GridView 适配器内部的 getView() 被多次调用。我知道如果多次调用 getView() 并没有什么特别的,但是我的 Adapter 中的 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 () - 方法只检查图像是否已经下载(肯定应该是这种情况)

包含视图的片段:

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

顺便说一句:我没有在任何地方使用 *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();
}
4

1 回答 1

2

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

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

于 2013-02-15T11:40:52.233 回答