【问题标题】:Is it necessary to store images in memory to show on list view in android是否有必要将图像存储在内存中以显示在 android 的列表视图中
【发布时间】:2012-12-25 18:38:21
【问题描述】:

我在列表的每一行都显示一个ImageView

为此,我使用 Drawable.createFromStream 在另一个 AsyncTask 中从网络下载图像 并将它们作为 Drawable 存储在 ArrayList 中,我将其传递给扩展 BaseAdapter 类的适配器类。

但图像是用高分辨率相机拍摄的,因此尺寸可能非常大。 我收到OutOfMemory 错误。

所以我的问题:

  1. 将图像存储为可绘制或位图或任何其他格式更有效?

  2. 通过将所有图像存储在内存中(在数组列表中),我做得对吗?即我在想,一旦我得到一张图片,我会在 ImageView 上显示它,而不是存储在 ArrayList 中。

  3. 有什么办法吗,我可以在下载后压缩图片,这样它们会占用更少的内存空间。

我的总码在here

【问题讨论】:

    标签: android image listview memory-management drawable


    【解决方案1】:

    Android 文档提供了一个很好的示例,展示了如何在您的 android 应用程序中处理位图。该示例使用磁盘和内存缓存并在后台加载图像。通过这样做,主 UI 线程不会因加载图像而减慢速度。

    Loading Bitmaps effectively

    在示例中,图片是从 picasa 加载的。但是,修改该示例很容易,以便使用本地存储的图片。您只需要编写自己的 ImageLoader,从 'ImageResizer' 扩展:

    public class ImageLoader extends ImageResizer {
        public ImageLoader(Context context, int imageWidth, int imageHeight) {
            super(context, imageWidth, imageHeight);
        }
        public ImageLoader(Context context, int imageSize) {
            super(context, imageSize);
        }
        @Override
        protected Bitmap processBitmap(Object data) {
            return decodeSampledBitmapFromFile((String)data, imageWidth, imageHeight);
        }
    }
    

    但要直接回答您的问题:可以将图像加载为位图。但是您必须使用缓存和弱引用,以便可以对图像进行垃圾收集,以防它们在屏幕上不可见。缓存它们并使用后台任务进行加载可以实现流畅的 UI。

    【讨论】:

    • 感谢 4 ur ans,但这一切都是为了在本地访问图像,在我的情况下,我必须从互联网下载每张图像,这就是我苦苦挣扎的地方,因为那时我必须存储(持有)我要传递给适配器的列表中的所有图像。
    • 如果您必须从互联网上下载图像,请使用原始的“有效加载位图”示例和“ImageFetcher”类。
    【解决方案2】:
    1. 我认为将高密度图像存储到内存中没有任何效率 - 完全不建议将大量图像作为位图存储在内存中(对你有好处,你有一个好的设备;))
    2. 见第 1 页
    3. 尝试缩小图像以适应设备的需要——尽管这不是一项简单的工作。另见View.setTag(Object tag)

    适配器

    public class MyImageListAdapter extends BaseAdapter implements ImageLoadingNotifier {
    
    private LayoutInflater inflater = null;
    
    public MyImageListAdapter() {
        inflater = LayoutInflater)HomeActivity.this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    
    public int getCount() {
        return listImageInfo.size();
    }
    
    public Object getItem(int position) {
        return listImageInfo.get(position);
    }
    
    public long getItemId(int position) {
        return position;
    }
    
    public View getView(int position, View convertView, ViewGroup parent) {
        View vi = convertView;
        if (convertView == null) {
            vi = inflater.inflate(R.layout.list_row, null);
        }
    
        TextView tvName = (TextView) vi.findViewById(R.id.tv_name);
        TextView tvTime = (TextView) vi.findViewById(R.id.tv_time);
        ImageView image = (ImageView) vi.findViewById(R.id.iv_image);
        final Button btnDelete = (Button) vi.findViewById(R.id.btn_delete);
    
    
        image.setImageDrawable(R.drawable.default_placeholder);//set default place-holder
        new GetDrawableFromUrl(listImageInfo.get(position), vi).execute();
    
        tvName.setText("Name: " + listImageInfo.get(position).getImage_name());
        tvTime.setText("Date: " + listImageInfo.get(position).getDate_created());
    
        btnDelete.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                final int position = listView.getPositionForView((View) v.getParent());
                positionOgBtnToDelete = position;
                Log.v("delete btn clicked", "delete btn no: " + position);
                Toast.makeText(HomeActivity.this, "Btn delete position: " + position, Toast.LENGTH_LONG).show();
                showAlertToConfirmDelete();
            }
        });
        return vi;
    }
    
    }
    

    AsyncTask GetDrawableFromUrl

    public class GetDrawableFromUrl extends AsyncTask<Void, Void, Drawable> {
    public ImageInfo imageInfoObj;
    private ImageView view;
    
    
    GetDrawableFromUrl(ImageInfo imageInfo, ImageView view) {
        imageInfoObj = imageInfo;
        this.view = view;
    }
    
    @Override
    protected Drawable doInBackground(Void... params) {
        try {
            return Drawable.createFromStream(((java.io.InputStream) new java.net.URL(imageInfoObj.getImageUrl()).getContent()), "src_name");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    protected void onPostExecute(Drawable drawable) {
        if (drawable != null) {
            //imageInfoObj.setImage(drawable);
            this.view.setImageDrawable(drawable);
            //listImageInfo.add(imageInfoObj); //this one is called when the json is parsed
            showImagesInList(); //don't know what it does (??)
        }
    }
    

    }

    JSON 解析

    JSONArray jsonArray = jsonObj.getJSONArray("result");
                for (int i = 0; i < jsonArray.length(); i++) {
                    JSONObject jsonObjInner = jsonArray.getJSONObject(i);
    
                    ImageInfo imageInfo = new ImageInfo();
                    imageInfo.setImageUrl("http://www.dvimaytech.com/markphoto/" + jsonObjInner.getString("image"));
    
                    //new GetDrawableFromUrl(imageInfo).execute(); //don't needed here
    
                    imageInfo.setEmail(jsonObjInner.getString("emailid"));
                    imageInfo.setImage_id(jsonObjInner.getString("image_id"));
                    imageInfo.setImage_name(jsonObjInner.getString("image_name"));
                    imageInfo.setAmount(jsonObjInner.getString("amount"));
                    imageInfo.setImage_description(jsonObjInner.getString("image_description"));
                    imageInfo.setDate_created(jsonObjInner.getString("date_created"));
    
                    listImageInfo.add(imageInfo);
                }
    

    而且,使用任何类型的 List 图像都变得不必要了 :)

    解析json对象的时候不用启动异步任务(GetDrawableFromUrl),可以在getView(...)方法启动任务。这样,您将不会被限制将可绘制对象存储到 ArrayList,因为您将在下载图像后修改 ImageView。并且,默认情况下,您可以放置​​一个占位符,直到下载图像(或者如果有一些网络错误)。

    这样,只有在为特定项目调用 getView 方法时,图像才会开始下载。

    底线是ListView 中的每个视图都将保留对其特定可绘制对象的引用(使用vi.setTag(image) 设置。

    如果这有帮助,你知道该怎么做;)

    【讨论】:

    • 感谢 4 位您的回答,但在您的示例中,您使用了 img = listImageInfo.get(position).getImage();即将图像存储在列表视图中。我现在也在做同样的事情。这就是我的问题,是否有必要在 listImageInfo 中存储(保存)图像?
    • 抱歉,我是新来的,所以无法理解 setTag 在这种情况下如何帮助我
    • @WhyandHow I have modified my answer
    • 请帮助我,我不知道如何(确切地在哪里)在 getView() 中启动 AsyncTask。即如果我在 if(img == null) 中启动它,那么每次 vi.setTag(img);将在获取图像之前执行。在 asynctask 中获取图像后,如何将其设置为相应的 imageView
    • @WhyandHow here,试试看是否可以
    【解决方案3】:

    有一个相当不错的库调用AQuery。您可以使用它,只需编写 2 行代码即可轻松获取内存和文件缓存等所有内容。所以你甚至不需要准备一个drawable,你可以直接从Adapter.getView()回调中调用它。

    AQuery aq = new AQuery(rowView);
    aq.id(R.id.image).image(url, false, true);
    

    希望对你有帮助!

    来自 AQuery 文档:

    下采样(处理大图像) 我们正在从网络加载一个巨大的图像,但我们只需要图像大于 200 像素宽。传入 200 的目标宽度将对图像进行下采样以节省内存。Aquery 只会下采样 2 (2,4,8...) 的幂以获得良好的图像质量和 效率。生成的图像宽度将在 200 到 399 像素之间

    String imageUrl = "http://farm6.static.flickr.com/5035/5802797131_a729dac808_b.jpg";
    aq.id(R.id.image1).image(imageUrl, true, true, 200, 0);

    【讨论】:

    • 我现在明白了,如果您在 getView() 回调中使用它,请使用 new AQuery(View v) 构造函数,其中 v 是您所在行的视图。
    • 谢谢,现在它可以工作了,现在我只需要检查大量图像。
    • 它可以工作,但没有大量图像应用程序被挂起(没有响应):(
    • 我也使用 AndroidQuery,并且喜欢它。在另一个有类似问题的帖子中提出了同样的答案。
    • @Why and How about large images:我已经用 AQuery doc 更新了我的答案。所以请仔细阅读,它有很多选择code.google.com/p/android-query/wiki/ImageLoading
    猜你喜欢
    • 2019-03-15
    • 1970-01-01
    • 1970-01-01
    • 2011-08-11
    • 2015-09-08
    • 2015-07-06
    • 1970-01-01
    • 2012-07-18
    • 1970-01-01
    相关资源
    最近更新 更多