web-dev-qa-db-ja.com

Glideでさらに画像をロードするとメモリ不足エラーが発生する

編集済み:

  • 私のアプリケーションでは、ホームページに300以上の画像を読み込んでいます。 glideを使用して画像をロードしました。 Out of Memory Error

manifestで大きなヒープtrueを使用しました:

Android:largeHeap="true"

グライドバージョン:

compile 'com.github.bumptech.glide:glide:3.7.0'

デバイス/ Androidバージョン:

Nexus Device 6.0バージョン

Jsonから取得するすべての画像は800kbから1mbです

activity_layout:

<RelativeLayout
    Android:id="@+id/home_layout_bottom"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:layout_below="@+id/home_layout_top_recycler"
    Android:layout_margin="5dp">

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/rv_list_tab_home_recycler"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:clipToPadding="false"
        Android:scrollbars="vertical"
        Android:visibility="visible" />

    <TextView
        Android:id="@+id/no_user_posts_item_tv_recycler"
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:layout_below="@+id/rv_list_tab_home_recycler"
        Android:layout_marginTop="80dp"
        Android:layout_centerHorizontal="true"
        Android:text="@string/txt_no_posts_available"
        Android:textColor="@color/txt_common_black"
        Android:textSize="@dimen/txt_size" />
</RelativeLayout>

アダプターコード:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = null;

    final HomePostItems rowItem = getItem(position);

    LayoutInflater mInflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
    if (convertView == null) {

        convertView = mInflater.inflate(R.layout.lv_adapter_post_items_layout, null);

      holder = new ViewHolder();

      holder.ivPostedImage = (ImageView) convertView.findViewById(R.id.iv_posted_img);


        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

      ..................

          Glide.with(context).load(rowItem.getPosteduserpostimage())
                        .placeholder(R.drawable.golive_load_image).error(R.drawable.golive_cancel_image)
                        .override(600, 200)
                        .into(holder.ivPostedImage);

adapter_layout.xml:

<RelativeLayout
    Android:id="@+id/rl_lv_user_post_adapter_img_holder_home"
    Android:layout_width="match_parent"
    Android:layout_height="300dp"
    Android:layout_marginLeft="1dp"
    Android:layout_marginRight="1dp"
    Android:layout_below="@+id/tv_user_posted_msg_post_items_home" >

    <ImageView
        Android:id="@+id/iv_posted_img_home"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_centerInParent="true"
        Android:scaleType="fitXY"
        Android:background="#ffffff"
        Android:contentDescription="@string/cont_desc"/>
</RelativeLayout>

Logcat:

Request threw uncaught throwable
Java.util.concurrent.ExecutionException: Java.lang.OutOfMemoryError: Failed to allocate a 6365196 byte allocation with 865912 free bytes and 845KB until OOM
at Java.util.concurrent.FutureTask.report(FutureTask.Java:94)
at Java.util.concurrent.FutureTask.get(FutureTask.Java:164)
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor.afterExecute(FifoPriorityThreadPoolExecutor.Java:96)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1121)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:588)
at Java.lang.Thread.run(Thread.Java:818)
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.Java:118)
Caused by: Java.lang.OutOfMemoryError: Failed to allocate a 6365196 byte allocation with 865912 free bytes and 845KB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at Android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
at Android.graphics.BitmapFactory.decodeStreamInternal(BitmapFactory.Java:635)
at Android.graphics.BitmapFactory.decodeStream(BitmapFactory.Java:611)
at com.bumptech.glide.load.resource.bitmap.Downsampler.decodeStream(Downsampler.Java:329)
at com.bumptech.glide.load.resource.bitmap.Downsampler.downsampleWithSize(Downsampler.Java:220)
at com.bumptech.glide.load.resource.bitmap.Downsampler.decode(Downsampler.Java:153)
at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.Java:50)
at com.bumptech.glide.load.resource.bitmap.StreamBitmapDecoder.decode(StreamBitmapDecoder.Java:19)
at com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder.decode(ImageVideoBitmapDecoder.Java:39)
at com.bumptech.glide.load.resource.bitmap.ImageVideoBitmapDecoder.decode(ImageVideoBitmapDecoder.Java:20)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decodeBitmapWrapper(GifBitmapWrapperResourceDecoder.Java:121)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decodeStream(GifBitmapWrapperResourceDecoder.Java:94)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.Java:71)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.Java:61)
at com.bumptech.glide.load.resource.gifbitmap.GifBitmapWrapperResourceDecoder.decode(GifBitmapWrapperResourceDecoder.Java:22)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromSourceData(DecodeJob.Java:190)
at com.bumptech.glide.load.engine.DecodeJob.decodeSource(DecodeJob.Java:177)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromSource(DecodeJob.Java:128)
at com.bumptech.glide.load.engine.EngineRunnable.decodeFromSource(EngineRunnable.Java:122)
at com.bumptech.glide.load.engine.EngineRunnable.decode(EngineRunnable.Java:101)
at com.bumptech.glide.load.engine.EngineRunnable.run(EngineRunnable.Java:58)
at Java.util.concurrent.Executors$RunnableAdapter.call(Executors.Java:423)
at Java.util.concurrent.FutureTask.run(FutureTask.Java:237)
at Java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.Java:1113)
at Java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.Java:588) 
at Java.lang.Thread.run(Thread.Java:818) 
at com.bumptech.glide.load.engine.executor.FifoPriorityThreadPoolExecutor$DefaultThreadFactory$1.run(FifoPriorityThreadPoolExecutor.Java:118)

このOOMの問題を解決する方法がわかりません。この問題に既に精通している場合は、提案を共有してください。

14
Stephen
  • この問題を解決するには、recyclerviewの上に配置されたネストされたスクロールビューを削除します。 OutOfMemoryエラーが発生した理由は、ホームページに200を超える画像を読み込むときに、recyclerviewの上のネストされたスクロールビューを使用しているため、200の画像すべてを読み込むことを意味します。

  • そのため、アダプターでlogcatイメージビューの幅と高さを1つずつ確認することはできません。

  • ネストされたスクロールビューを削除すると、メモリ不足エラーが修正されます。ホームアクティビティにアクセスしたときに、デバイスに表示される画像が3つしか読み込まれないためです。

  • this 、ネストされたスクロールビューの代わりにスクロールを使用する方法も確認してください。

11
Stephen

これは問題の正確な解決策ではありませんが、Glideを使用してリスト内の画像を読み込む際にこれらのことを覚えておく必要があります。

問題の主な脅威部分は画像サイズです。取得している画像はそれぞれ1MB近くです!実際には、300以上のアイテムを含むリストに表示するには大きすぎます。したがって、サーバー側でも作業を行う場合は、いくつかの異なるサイズの画像を使用することを常にお勧めします。

たとえば、友人のリストとそのプロフィール写真を表示する場合、最初にサーバーからリスト全体を取得することをお勧めします。次に、すべてのプロファイル画像を取得し、ローカルに保存します。次に、ListViewを設定します。そして、最も重要な部分は、ユーザーのプロフィール写真をサーバーにアップロードすることです。アップロード後、サーバーはいくつかのサイズを維持する必要があります。低、中、高解像度バージョン。したがって、ListViewのプロファイル画像のURLを提供している間、サーバーはサムネイルに使用される可能性が高いため、低解像度の画像を提供します。

RecyclerViewの代わりにListViewを使用することも適切な呼び出しです。ただし、ローエンドデバイスを使用しているときにここで発生する問題は解決しません。

OMMは、プログラムで解決できることとは関係ありません。画像を低解像度バージョンにサイズ変更する必要があります。

Glideの キャッシュメカニズム も確認できます。キャッシュ戦略を使用して、毎回サーバーからイメージをロードする必要がないようにすることをお勧めします。

幸運を。

9
Reaz Murshed
  1. 寸法wrap_contentがGlideにフル解像度のビットマップをロードさせるため、ImageViewにmatch_parentまたは固定dpがあることを確認してください。
  2. .placeholder()は、大きなビットマップのロード中に空のスペースではなく画像を表示します
  3. .thumbnail(float)は、大きな画像がバックグラウンドで読み込まれている間、ダウンサンプリングされたバージョンを高速に読み込みます
  4. また、 グライドの問題 を見渡して、何か役に立つかもしれません。
5

Glideを使用しても、Out of Memoryエラー、いくつかの小さな手順を使用して、OOM's

ステップ1:Glideの キャッシングメカニズム を理解する

ステップ2:thumbnailsをrecyclerviewにロードしたい

Glide  
    .with( context )
    .load( UsageExampleGifAndVideos.gifUrl )
    .thumbnail( 0.1f )
    .into( imageView2 );

大きな画像やHD画像が必要ない場合は、常に小さなサイズの画像をリクエストすることを忘れないでください。

recyclerViewの代わりにListViewを使用します。アイテムをレンダリングするための再利用可能な単一アイテム。私はgliderecyclerViewとともに使用しており、100個以上のアイテムを含む壁紙をロードしています。

リストビューでは、ビューを作成するたびに、100以上のビューがあり、recyclerviewのように画面+2に表示されるアイテムの数を作成する100以上のビューを作成します。

私は同様の問題に直面した。私はそれを解決した方法を共有しています。 _drawable-nodpi_という名前のフォルダーを作成し、そのフォルダーに_golive_load_image_および_golive_cancel_im‌​age_ファイルを置き、それらの2つの画像ファイルを_drawable-ldpi_、_drawable-hdpi_などのような他の場所から削除します(そこにあるなら)。 skipMemoryCache( true )を追加します

_     Glide.with(context).load(rowItem.getPosteduserpostimage())
                            .skipMemoryCache( true )
                            .placeholder(R.drawable.golive_load_image).error(R.drawable.golive_cancel_image)
                            .override(600, 200)
                            .into(holder.ivPostedImage);
_

その理由は、スクロール中、関連するビューがリストから削除されても、グライドが画像処理を実行し続けるためです。リストビューのonScrollStateChangedにこのコードを追加します。

if (view.getContext() != null) {
        switch (scrollState) {
            case SCROLL_STATE_IDLE:
                Glide.with(view.getContext()).resumeRequests();
                break;
            case SCROLL_STATE_TOUCH_SCROLL:
            case SCROLL_STATE_FLING:
                Glide.with(view.getContext()).pauseRequests();
                break;
        }
    }
1
invisbo

これを解決するために、おそらく別のアプローチを取ることができます。これを実現するには、異なる ImageAdapter を使用できます

Glide.with(mActivity).loadFromMediaStore(_imageInfo.getmUri())

これは MediaStoreThumbFetcher を使用してクラッシュしません

負荷をより細かく制御するには、Glide v4を使用して以下を実行します

// usage:
Glide.with(mActivity).load(_imageInfo)....

// in GlideModule.registerComponents
registry.prepend(ImageInfo.class, ImageInfo.class, new UnitModelLoader.Factory<ImageInfo>());
registry.prepend(ImageInfo.class, Bitmap.class, new ImageInfoBitmapDecoder(context));

class ImageInfoBitmapDecoder implements ResourceDecoder<ImageInfo, Bitmap> {
    private final ContentResolver contentResolver;
    private final BitmapPool pool;
    public ImageInfoBitmapDecoder(Context context) {
        this.contentResolver = context.getContentResolver();
        this.pool = Glide.get(context).getBitmapPool();
    }
    @Override public boolean handles(ImageInfo source, Options options) { return true; }
    @Override public @Nullable Resource<Bitmap> decode(ImageInfo source, int width, int height, Options options) {
        Bitmap thumb = Thumbnails.getThumbnail(contentResolver, source.getmId(), Thumbnails.MINI_KIND, null);
        return BitmapResource.obtain(thumb, pool);
    }
}

次のAPIを使用して、空きメモリとビットマップのサイズを把握できます

事前チェックとして、使用可能なメモリとビットマップの詳細(必要な場合)を確認できます。

空きメモリの残量を確認します

public static final float BYTES_IN_MB = 1024.0f * 1024.0f;

    public static float megabytesFree() {
        final Runtime rt = Runtime.getRuntime();
        final float bytesUsed = rt.totalMemory();
        final float mbUsed = bytesUsed/BYTES_IN_MB;
        final float mbFree = megabytesAvailable() - mbUsed;
        return mbFree;
    }

    public static float megabytesAvailable() {
        final Runtime rt = Runtime.getRuntime();
        final float bytesAvailable = rt.maxMemory();
        return bytesAvailable/BYTES_IN_MB;
}

ロードしたいビットマップの大きさを確認してください

private void readBitmapInfo() {
        final Resources res = getActivity().getResources();
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, R.drawable.brasil, options);
        final float imageHeight = options.outHeight;
        final float imageWidth = options.outWidth;
        final String imageMimeType = options.outMimeType;
        Log.d(TAG, "w,h, type:"+imageWidth+", "+imageHeight+", "+imageMimeType);
        Log.d(TAG, "estimated memory required in MB: "+imageWidth * imageHeight * BYTES_PER_PX/MemUtils.BYTES_IN_MB);
}

詳細については、 メモリとビットマップをチェックするJavaメソッド および githubディスカッション を参照してください。

1
Sreehari

メモリ不足エラーを防ぐために、予防策を講じて、発生しないことを確認することができます。したがって、この質問の答えは、実際には提案できる多くの提案です。私もそうです。

@ Reaz Murshedが示唆するように、いくつかの異なるサイズの画像を使用することもお勧めします。これとは別に、この問題の分析と解決に役立つと思われるものをいくつか追加します。

私が覚えている限り、OOMは常に使用エラーでしたが、largeHeapはそれを遅らせるだけです。または、負荷が大きい場合は不可能かもしれません。したがって、メモリリークを診断するには this リンクに従うことをお勧めします。

OutOfMemoryErrorsのスタックトレースは、それらの診断にはまったく役立ちません。壊れていると何かが記憶を一杯にしたことを伝えるだけです。この充填は、実際の例外がスローされるかなり前に発生します。これは、通常、OOMをスローするものが実際に犯人ではないことも意味します。唯一の例外は、割り当てたいメモリの量が単純に大きすぎる場合です。たとえば、割り当てられる配列が最大メモリよりも大きい場合、32000x32000 @ 4イメージのように計算が本当にうまくいかなかったことがわかります。約4GBのメモリを使用します。

再現できる場合:ヒープダンプを取得し、アプリの使用状況を分析します。通常のOOM診断手順:

  • 例外を再現します(LogCatで表示されるまで待ちます)

  • ヒープダンプを取得する(メモリリークを分析するには)

  • 大きなオブジェクトとリークについて分析する

上記の共有リンクには、how to take heap dump?および issues に関して、これと同じリンクがほとんどありません。

したがって、メモリリークを分析し、OOMを防ぐために必要な手順を実行することをお勧めします。

これがお役に立てば幸いです。

1
  • 画像が大きすぎてはいけません(大きすぎる場合は.thumbnail(...f)を使用してください)
  • キャッシュに画像を保持することを強制されていない場合は、.skipMemoryCache(true)を使用します
  • .diskCacheStrategy(DiskCacheStrategy.NONE)を使用して、ディスクキャッシュを非アクティブ化できます。
1
Misagh