web-dev-qa-db-ja.com

メモリ不足エラーImageViewの問題

私は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)
31
Max

堅実なコードであるケンの答えを追加するために、私は彼がそれをセットアップした後にそれをノックダウンすると思いました。

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

注:既にリサイクルしたイメージを交換しようとしている場合、これは機能しません。 LOGCATでこのようなものが得られます

Canvas:リサイクルビットマップを使用しよう

したがって、異なる画像の束を非同期にロードする必要がない場合に私が今していることは、フラグメントと大きな背景画像を処理するときにonDestroyにこれを置くだけです:

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

    imageView.setImageDrawable(null);
}
35
whyoz

つかいます

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

新しい画像に変更する前に!!

28
Ken

これらの_OutOfMemory Exception_の問題にまだ直面している Glide 画像読み込みライブラリを使用している場合は、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_を使用します。できれば、fitCenterまたはcenterCropを使用してください。

  • ImageViewで_wrap_content_を使用しないでください。代わりに_match_parent_を使用するか、widthのサイズを明示的に使用してheight/dpを指定してください。 reallyImageViewで_wrap_content_の使用を主張する場合、少なくとも_Android:maxHeight_/_Android:maxWidth_を設定します。
  • dontAnimate()リクエストでGlide.with()...を使用してアニメーションをオフにします。
  • (リスト/グリッドで行うように)潜在的に大きな画像を大量に読み込む場合は、リクエストでthumbnail(float sizeMultiplier) loadを指定します。例:

    _Glide.with(context)
       .load(imageUri)
       .thumbnail(0.5f)
       .dontAnimate()
       .into(iconImageView);
    _
  • Glide.get(context).setMemoryCategory(MemoryCategory.LOW)を使用して、アプリの特定の段階でGlideのメモリフットプリントを一時的に下げます。

  • 必要な場合にのみメモリにキャッシュします。skipMemoryCache(true)リクエストでGlide.with()...を使用して無効にすることができます。これにより、イメージがディスクにキャッシュされます。これは、メモリ内キャッシュを使用しているため、おそらく必要になります。
  • ローカルリソースからDrawableをロードする場合、ロードしようとしているイメージが非常に大きくないことを確認してください。オンラインで利用可能な画像圧縮ツールがたくさんあります。これらのツールは、外観の品質を維持しながら、画像のサイズを縮小します。
  • ローカルリソースからロードする場合は、.diskCacheStrategy(DiskCacheStrategy.NONE)を使用します。
  • 必要に応じてGlideキャッシュをトリムするためにAndroidが提供するonTrimMemory(int level)コールバックにフックします。

    _@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);
    }
    _
  • これがstill発生している場合、「すべてを試した」後でも、問題はアプリケーション(GASP!)、およびGlideは、それを_OutOfMemory Exception_ゾーンにプッシュしている唯一のものです...したがって、アプリケーションでメモリリークがないことを確認してください。 _Android Studio_は、アプリのメモリ消費の問題を識別するためのツールを提供します。
  • 最後に、 GlideのGitHubの問題ページ を確認してください。問題を修正するための洞察を提供する可能性のある同様の問題があります。リポジトリは非常にうまく管理されており、非常に役立ちます。
16
Sakiboy

画像にはあらゆる形とサイズがあります。多くの場合、通常のアプリケーションユーザーインターフェイス(UI)に必要なサイズよりも大きくなっています。たとえば、システムギャラリーアプリケーションは、Androidデバイスのカメラを使用して撮影した写真を表示します。通常、デバイスの画面密度よりもはるかに高い解像度です。

限られたメモリで作業していることを考えると、理想的にはメモリに低解像度バージョンのみをロードしたいです。低解像度バージョンは、それを表示するUIコンポーネントのサイズと一致する必要があります。より高い解像度の画像は目に見える利点を提供しませんが、それでも貴重なメモリを消費し、追加のオンザフライスケーリングにより追加のパフォーマンスオーバーヘッドが発生します。

ソース: 大きなビットマップを効率的に読み込む

上記の情報に基づいて、画像を次のように設定する代わりに、次のことをお勧めします。

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;
    }
6
Ivo Stoyanov

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メソッドを「Methods」クラス(その他の便利なメソッド)に配置します。

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

}
2
user25

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のピカソもそれを行う ピカソはURIから描画可能なリソースをロードする

2
JohnnyLambada

@Sakiboyの回答への追加メモ。私はおそらく答えには遅すぎますが、ここに私の解決策がありますが、多くのコード変更を行う必要なく動作することがわかりました。

  • Glideを使用して、すべてのキャッシュを処理します。
  • より多くのメモリをクリアするには、すべてのviewsを手動で削除し、ImageViewビットマップ/描画可能変数をnullに設定し、すべてのイベントハンドラーとリスナーをクリアする必要があります。
  • activityまたはfragmentにあるすべての変数をnullに設定します。
  • ロジックをonDestroy内に配置する必要があり、うまくいくはずです。
  • オプションの手順は、コードの最後にSystem.gc()を追加することです。

前に述べたものをすべてクリアした後。フラグメント/アクティビティが破壊されるたびにメモリが低下することに気付くでしょう。

0
SolidSnake