【问题标题】:Out of Memory Error ImageView issue内存不足错误 ImageView 问题
【发布时间】:2012-04-17 22:40:27
【问题描述】:

我是 Android 编程新手,我收到一条错误消息,提示我的应用程序内存不足,这个示例是我从一本书中复制的,它适用于小图片分辨率,但是当我添加了几张图片出现更大的分辨率内存不足错误,可能是我做错了什么,或者只是不知道我应该使用图像的所有内容,如果有人知道我应该更改什么以便不会再次出现此错误,请帮助。谢谢期待!

源代码:

public class ImageViewsActivity extends Activity {
//the images to display
Integer[] imageIDs={
        R.drawable.pic1,
        R.drawable.pic2,
        R.drawable.pic3,
        R.drawable.pic4,
        R.drawable.pic5
};  
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    final ImageView iv=(ImageView) findViewById(R.id.image1);

    Gallery gallery=(Gallery) findViewById(R.id.gallery);
    gallery.setAdapter(new ImageAdapter(this));
  gallery.setOnItemClickListener(new OnItemClickListener(){
        public void onItemClick(AdapterView<?> parent, View v, int position, long id){
            Toast.makeText(getBaseContext(), "pic"+(position+1)+" selected", Toast.LENGTH_SHORT).show();

            //display the image selected
            try{iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
              iv.setImageResource(imageIDs[position]);}catch(OutOfMemoryError e){
                     iv.setImageBitmap(null);
                }
        }
    });


}

public class ImageAdapter extends BaseAdapter{
    private Context context;
    private int itemBackground;

    public ImageAdapter(Context c){
        context=c;
        //setting the style
        TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
        itemBackground = a.getResourceId(R.styleable.Gallery1_android_galleryItemBackground, 0);
        a.recycle();
    }

    //returns the number of images
    public int getCount() {
        // TODO Auto-generated method stub
        return imageIDs.length;
    }

    //returns the ID of an item
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    //returns the ID of an item
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    //returns an ImageView view
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        ImageView iv= new ImageView(context);
        iv.setImageResource(imageIDs[position]);
        iv.setScaleType(ImageView.ScaleType.FIT_XY);
        iv.setLayoutParams(new Gallery.LayoutParams(150,120));
        iv.setBackgroundResource(itemBackground);

        return iv;
    }
}}

此处出现错误:

04-18 10:38:31.661: D/dalvikvm(10152): Debugger has detached; object registry had 442 entries
04-18 10:38:31.661: D/AndroidRuntime(10152): Shutting down VM
04-18 10:38:31.661: W/dalvikvm(10152): threadid=1: thread exiting with uncaught exception (group=0x4001d820)
04-18 10:38:31.691: E/AndroidRuntime(10152): FATAL EXCEPTION: main
04-18 10:38:31.691: E/AndroidRuntime(10152): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.graphics.Bitmap.nativeCreate(Native Method)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.graphics.Bitmap.createBitmap(Bitmap.java:499)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.graphics.Bitmap.createBitmap(Bitmap.java:466)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:371)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:539)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:508)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:365)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:728)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.content.res.Resources.loadDrawable(Resources.java:1740)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.content.res.Resources.getDrawable(Resources.java:612)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.widget.ImageView.resolveUri(ImageView.java:520)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.widget.ImageView.setImageResource(ImageView.java:305)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at image.view.GalleryView$ImageAdapter.getView(GalleryView.java:95)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.widget.Gallery.makeAndAddView(Gallery.java:776)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.widget.Gallery.fillToGalleryLeft(Gallery.java:695)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.widget.Gallery.trackMotionScroll(Gallery.java:406)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.widget.Gallery$FlingRunnable.run(Gallery.java:1397)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.os.Handler.handleCallback(Handler.java:618)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.os.Handler.dispatchMessage(Handler.java:123)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.os.Looper.loop(Looper.java:154)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at android.app.ActivityThread.main(ActivityThread.java:4668)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at java.lang.reflect.Method.invokeNative(Native Method)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at java.lang.reflect.Method.invoke(Method.java:552)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:917)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:674)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at dalvik.system.NativeStart.main(Native Method)

【问题讨论】:

  • 请发布与错误相关的整个堆栈跟踪
  • a) 不要使用大图像,或 b) 在运行时缩小图像以减少内存使用。如果我的代码正确,你只会显示 150x120 像素版本。
  • 显示图片时内存不足是个大问题。在此处查看本教程:Displaying Bitmaps Efficiently
  • 我遇到了同样的问题。就我而言,问题是由于图像尺寸过大造成的。因此,当我调用 setImageResource() 时,会发生内存不足错误。

标签: android out-of-memory


【解决方案1】:

要添加 Ken 的答案,这是一段可靠的代码,我想我会在他设置后将其击倒:

    if(imageView != null) {
        ((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();
    }
    imageView = (ImageView) view.findViewById(R.id.imageView);
    imageView.setImageResource(resID);

注意:如果您尝试交换已回收的图像,这将不起作用。你会在 LOGCAT 中得到类似的东西

画布:尝试使用回收的位图

所以如果我不必异步加载一堆不同的图像,我现在要做的就是在处理片段和大背景图像时将它放在 onDestroy 中:

@Override
public void onDestroy() {
    super.onDestroy();

    imageView.setImageDrawable(null);
}

【讨论】:

  • onDestroy() 想法很棒!但是,如果您在没有可绘制对象的情况下在 ImageView 上投射 BitmapDrawable 并执行 .getBitmap() ,它只会给您 NullPointerException ;)
  • 您不想在 onDestroyView() 中执行此操作 (setIMageDrawable(null)) 吗?
  • @jj,考虑到古怪的Fragment 生命周期,我认为onDestroyView() 是更好的地方。如果您在 ViewPager 中使用 Fragments,您肯定会想要这样做。
  • 您好,先生。 imageView.setImageDrawable(null); 没办法,碎片很多
【解决方案2】:

对于那些使用Glide 图像加载库的人,他们仍然遇到这些OutOfMemory Exception 问题,您可以做很多事情来使Glide 使用更少的内存并希望解决您的问题。以下是其中的一些:

  • 不要在ImageView 中使用android:scaleType="fitXY"。所以如果你是ImageView 看起来像这样:

    <ImageView android:id="@android:id/icon"
           android:layout_width="@dimen/width"
           android:layout_height="@dimen/height"
           android:adjustViewBounds="true" 
           android:scaleType="fitXY" 
          <!-- DON'T USE "fitXY"! -->
    />
    

    ImageView 更改为使用不同的android:scaleType,最好是:fitCentercenterCrop

  • 不要在ImageView 中使用wrap_content,而是使用match_parent 或使用dp 中的大小显式指定width/height。如果你真的坚持在你的ImageView 中使用wrap_content,至少设置一个android:maxHeight/android:maxWidth
  • 在您的 Glide.with()... 请求中使用 dontAnimate() 关闭动画。
  • 如果您要加载大量可能较大的图像(就像在列表/网格中那样),请在请求中指定 thumbnail(float sizeMultiplier) 加载。例如:

    Glide.with(context)
       .load(imageUri)
       .thumbnail(0.5f)
       .dontAnimate()
       .into(iconImageView);
    
  • 在您的应用程序的某些阶段使用:Glide.get(context).setMemoryCategory(MemoryCategory.LOW) 暂时降低 Glide 的内存占用量。

  • 仅在需要时缓存在内存中,您可以在您的Glide.with()... 请求中使用:skipMemoryCache(true) 将其关闭。这仍然会将图像缓存到磁盘上,因为您之前使用的是内存缓存,所以您可能会需要它。
  • 如果您从本地资源加载Drawable,请确保您尝试加载的图像不是超大的。网上有很多图像压缩工具。这些工具将缩小图像的大小,同时保持其外观质量。
  • 如果从本地资源加载,请使用.diskCacheStrategy(DiskCacheStrategy.NONE)
  • 挂钩到 Android 提供的 onTrimMemory(int level) 回调,以根据需要修剪 Glide 缓存。例如。

    @Override
    public void onTrimMemory(int level)
    {
        super.onTrimMemory(level);
        Glide.get(this).trimMemory(level);
    }
    
  • 如果在 RecyclerView 中显示图像,您可以在回收视图时明确清除 Glide,如下所示:

    @Override
    public void onViewRecycled(MyAdapter.MyViewHolder holder)
    {
        super.onViewRecycled(holder);
        Glide.clear(holder.imageView);
    }
    
  • 如果这种情况仍然发生,即使在您“尝试了所有方法”之后,问题也可能出在 您的应用程序(GASP!),而 Glide 只是将它推到OutOfMemory Exception 区域的一件事......所以请确保您的应用程序中没有任何内存泄漏。 Android Studio 提供了用于识别应用中的内存消耗问题的工具。
  • 最后检查issue page on Glide's GitHub,寻找类似的问题,可以帮助您深入了解如何解决您的问题。回购管理得非常好,他们非常有帮助。

【讨论】:

  • 很棒的工作,谢谢!我现在正在使用毕加索,但一些提示可能很少。
  • 好提示.. 另外,每当活动或片段被销毁时,我会进行一些手动删除并处置一些组件、处理程序、侦听器、视图。我所做的测试对我有很大帮助。
  • @SolidSnake 我同意,我在onDestroy()(或任何相关的回调)中也这样做。类似于您应该如何明确清除 onViewRecycled() 中的图像视图。
  • 我在ConstraintLayout 内有我的ImageView,并使用0dp 作为宽度/高度。它是否会以任何方式影响图像加载,例如您提到的wrap_content
  • @Micer,这是一个很好的问题。我认为约束布局足够智能,可以根据约束(w/ match_constraint set - 0dp)测量 imageView 的大小。 Wrap_content 是有问题的,因为 ImageView 直到花时间计算后才知道它的大小。
【解决方案3】:

使用

((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();

更换新图片之前!!

【讨论】:

  • 垃圾收集器可能不够快,这意味着它并非在所有场合都有效。
  • 我同意有时 GC 不够快。因此,如果我们必须多次使用具有完全相同大小的 Bitmap,我们可以制作一个 BitMapPool 来重用 Bitmap,而不是回收它。如果回收的BitMap过多,可能会影响UI性能,因为回收的时间太长。
【解决方案4】:

图片有各种形状和大小。在许多情况下,它们更大 比典型的应用程序用户界面 (UI) 所需的更多。为了 例如,系统图库应用程序显示使用拍摄的照片 您的 Android 设备的摄像头通常要高得多 分辨率高于设备的屏幕密度。

鉴于您的内存有限,理想情况下您只需要 在内存中加载较低分辨率的版本。较低的分辨率 版本应该与显示它的 UI 组件的大小相匹配。一个 分辨率更高的图像不会提供任何可见的好处, 但仍会占用宝贵的内存并产生额外的性能 由于额外的动态缩放而产生的开销。

来源:Loading Large Bitmaps Efficiently

根据以上信息,我建议您不要像这样设置图像:

setImageResource(resId);

这样设置:

setScaledImage(yourImageView, resId);

并复制粘贴以下方法:

    private void setScaledImage(ImageView imageView, final int resId) {
        final ImageView iv = imageView;
        ViewTreeObserver viewTreeObserver = iv.getViewTreeObserver();
        viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            public boolean onPreDraw() {
                iv.getViewTreeObserver().removeOnPreDrawListener(this);
                int imageViewHeight = iv.getMeasuredHeight();
                int imageViewWidth = iv.getMeasuredWidth();
                iv.setImageBitmap(
                        decodeSampledBitmapFromResource(getResources(),
                                resId, imageViewWidth, imageViewHeight));
                return true;
            }
        });
    }

    private static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                         int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds = true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    private static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {

        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) > reqHeight
                    && (halfWidth / inSampleSize) > reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

【讨论】:

    【解决方案5】:

    您可以将其留给 3rd 方库,例如 Glide

    //                imageView.setImageResource(imageId);
                    Glide.with(this)  // Activity or Fragment
                            .load(imageId)
                            .into(imageView);
    

    以下是如何将其添加到您的build.gradle

    compile group: 'com.github.bumptech.glide', name: 'glide', version: '3.7.0'
    

    Square 的毕加索也是如此Picasso load drawable resources from their URI

    【讨论】:

    • @Sakiboy 您需要提供更多信息才能获得帮助。
    • 仅仅建议使用库并不能解决这个问题,因为这些库(GlidePicasso)仍然可以遇到OOM 异常......它们不是银不受错误影响的项目符号。
    • 完全同意。但是,这两个库都将修复 OP 遇到的问题。肯定还有其他可靠的答案,详细说明如何更新 OP 的代码以避免他遇到的 OOM。但是,在 95% 的情况下,使用提到的其中一个库可能比使用自己的库更好。
    • 对于仍然面临这些OutOfMemory 问题的人,即使使用了Glide,请参阅我的回答以获取一些帮助解决问题的提示。
    • @sakiboy 我读到了你的答案——很棒的工作——强烈推荐那些使用 lib 但仍然有问题的人阅读。
    【解决方案6】:

    Google 有正确(完美)的答案:

    https://developer.android.com/training/displaying-bitmaps/load-bitmap.html

    我如何在片段中使用它的示例:

    private ImageView mImageView;
    private View view;
    private int viewWidth;
    private int viewHeight;
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        view = inflater.inflate(R.layout.fragment_episode_list, container, false);
        mImageView = (ImageView) view.findViewById(R.id.ImageView);
    
        ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
        if (viewTreeObserver.isAlive()) {
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    viewWidth = view.getMeasuredWidth();
                    viewHeight = view.getMeasuredHeight();
                    mImageView.setImageBitmap(Methods.decodeSampledBitmapFromResource(getResources(),
                                R.drawable.YourImageName, viewWidth, viewHeight));
                }
            });
        }
    
        return view;
    }
    

    我将这些 Google 方法放入我的“方法”类(放入任何其他有用的方法):

    public class Methods {
    
        ...
    
        public static int calculateInSampleSize(
                BitmapFactory.Options options, int reqWidth, int reqHeight) {
            // Raw height and width of image
            final int height = options.outHeight;
            final int width = options.outWidth;
            int inSampleSize = 1;
    
            if (height > reqHeight || width > reqWidth) {
    
                final int halfHeight = height / 2;
                final int halfWidth = width / 2;
    
                // Calculate the largest inSampleSize value that is a power of 2 and keeps both
                // height and width larger than the requested height and width.
                while ((halfHeight / inSampleSize) >= reqHeight
                        && (halfWidth / inSampleSize) >= reqWidth) {
                    inSampleSize *= 2;
                }
            }
    
            return inSampleSize;
        }
    
        public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                             int reqWidth, int reqHeight) {
    
            // First decode with inJustDecodeBounds=true to check dimensions
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(res, resId, options);
    
            // Calculate inSampleSize
            options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    
            // Decode bitmap with inSampleSize set
            options.inJustDecodeBounds = false;
            return BitmapFactory.decodeResource(res, resId, options);
        }
    
    }
    

    【讨论】:

      【解决方案7】:

      @Sakiboy 答案的附加说明。 虽然我的答案可能为时已晚,但这是我的解决方案,我发现它无需进行大量代码更改即可工作。

      • 使用Glide 处理所有缓存。
      • 要清除更多内存,您应该手动删除所有views 并将任何ImageView bitmap/drawable 设置为null 并清除所有事件处理程序和侦听器。
      • activityfragment 中的所有变量设置为null
      • 您需要将您的逻辑放入 onDestroy 中,然后您就可以开始了。
      • 可选步骤是在代码末尾添加System.gc()

      在清除了我之前提到的所有内容之后。您会注意到,每次片段/活动被破坏时,内存都会下降。

      【讨论】:

        【解决方案8】:

        当我在横向模式下的 imageview 中显示大图像时,我遇到了同样的问题。 所以我用这段代码解决了

                    File imgFile = new File(imageFile.getAbsolutePath()); // path of your file
        
                        FileInputStream fis = null;
                        try {
                            fis = new FileInputStream(imgFile);
                        } catch (FileNotFoundException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        BitmapFactory.Options options = new BitmapFactory.Options();
                        options.inSampleSize = 8;
                        options.inPurgeable = true;
                        options.inScaled = true;
                        Bitmap bm = BitmapFactory.decodeStream(fis, null,options);
                      profileIV.setImageBitmap(bm);
                 }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-09-19
          • 1970-01-01
          • 2013-08-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多