2012-04-17 116 views
25

我是Android編程新手,我得到一個錯誤,說我的應用程序內存不足,這個例子我從一本書複製而來,它使用小圖片分辨率,但是當我添加了一些分辨率較高的圖片出現內存錯誤,可能是我做了一些錯誤或者只是不知道我應該如何處理圖片,如果有人知道我應該更改哪些內容以免出現此錯誤再次,請求幫助。謝謝您期待!內存不足錯誤ImageView問題

的源代碼:

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) 
+0

請發佈與錯誤 – slayton 2012-04-17 22:41:49

+0

關聯的整個堆棧跟蹤a)不要使用大圖像,或b)在運行時縮小圖像以減少內存使用量。如果我的代碼正確,你只顯示150x120像素的版本。 – zapl 2012-04-17 22:47:28

+2

內存不足是顯示圖像時的一大問題。點擊此處查看本教程:[高效顯示位圖](http://developer.android.com/training/displaying-bitmaps/index.html) – 2012-04-18 01:09:51

回答

25

使用

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

變化到新的圖像之前!

+1

垃圾收集器的速度可能不夠快,這意味着它不適用於任何場合。 – 2014-04-26 21:25:04

+0

我同意有時GC不夠快。因此,如果我們必須多次使用Bitmap的大小,我們可以讓BitMapPool重用Bitmap,而不是回收它。如果您回收了太多的BitMap,可能會影響UI性能,因爲回收的時間太長。 – Ken 2015-03-23 01:56:42

29

要在肯的回答,這是一個堅實的一塊的代碼添加,我想我會敲下來後,他將其設置:

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); 
} 
+0

謝謝你解決了我的問題 – 2014-06-16 18:12:20

+1

onDestroy()的想法很棒!但是,如果你投了BitmapDrawable並且在ImageView上做了.getBitmap()而沒有drawable,它只會給你NullPointerException;) – 2015-10-25 10:06:16

+1

你不想在onDestroyView()中做這個('setIMageDrawable(null)')嗎? – 2016-10-12 20:56:58

5

圖片有各種形狀和大小。在許多情況下,它們比典型的應用程序用戶界面(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; 
    } 
-1

我在使用LANDSCAPE模式在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); 
     } 
2

你可以把它留給第三方庫如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' 

廣場的畢加索也這樣做Picasso load drawable resources from their URI

+0

我仍然收到使用Glide的錯誤... – Sakiboy 2017-04-26 21:02:13

+0

@Sakiboy你需要給予更多的信息,比獲得幫助。 – JohnnyLambada 2017-04-27 16:14:34

+0

僅僅建議使用庫並不能解決這個問題,因爲這些庫('Glide'&'Picasso')仍然可能會遇到'OOM'異常......它們不是錯誤不受影響的銀色子彈。 – Sakiboy 2017-04-27 16:47:44

1

谷歌有權(完美的)答案:

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

我把這些谷歌的方法來我的「方法」類(其他任何有用的方法):

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

} 
5

對於使用Glide圖像加載庫,誰仍在運行到這些那些問題,有很多事情可以使Glide使用更少的內存,並希望解決您的問題。這裏有幾個:

  • 不要使用android:scaleType="fitXY"ImageView內。所以,如果你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_parentdp明確使用一個大小指定width/height。如果您真的堅持在您的ImageView中使用wrap_content,至少請設置一個android:maxHeight/android:maxWidth
  • 關閉動畫:dontAnimate()請求Glide.with()...
  • 如果您正在加載大量可能較大的圖像(如列表/網格中所示),請在請求中指定thumbnail(float sizeMultiplier)加載。例如:

    Glide.with(context) 
        .load(imageUri) 
        .thumbnail(0.5f) 
        .dontAnimate() 
        .into(iconImageView); 
    
  • 暫時降低Glide的過程中您的應用程序的某些階段內存佔用使用:Glide.get(context).setMemoryCategory(MemoryCategory.LOW)

  • 如果您需要,只能在內存中緩存,您可以在Glide.with()...請求中使用:skipMemoryCache(true)將其關閉。這仍然會將圖像緩存到磁盤,因爲您在前面的內存緩存中,您可能需要這些磁盤。
  • 如果您使用本地資源加載Drawable,請確保您嘗試加載的圖片不是超級巨大。網上有很多圖像壓縮工具。這些工具會縮小圖像的大小,同時保持其外觀質量。
  • 如果從本地資源加載使用.diskCacheStrategy(DiskCacheStrategy.NONE)
  • 鉤入onTrimMemory(int level)回調,Android會根據需要修剪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,可能會提供解決問題的洞察力的類似問題。回購管理非常好,他們非常有幫助。
+0

真棒工作,謝謝!我現在正在使用畢加索,但一些提示可能會很少。 – 2017-06-23 17:15:46

+0

我不使用'畢加索'它需要太多的內存。所以我只有'滑翔'的提示。但是其中一些提示也可以用於「畢加索」。 – Sakiboy 2017-06-23 17:21:01