web-dev-qa-db-fra.com

Erreur de mémoire insuffisante lors du chargement de plus d'images dans Glide

Modifié:

  • Dans mon application, je charge plus de 300 images sur la page d'accueil. J'ai utilisé glide pour charger des images. Je suis en train Out of Memory Error.

J'ai utilisé un gros tas vrai dans manifeste:

Android:largeHeap="true"

Version Glide:

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

Version appareil/Android:

Version de l'appareil Nexus 6.0

Toutes les images que je reçois de Json seraient de 800 Ko à 1 Mo.

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>

code adaptateur:

@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)

Je ne sais pas comment résoudre ce problème de MOO. Veuillez partager votre suggestion, si vous connaissez déjà ce problème.

14
Stephen

Ce n'est pas une solution exacte à votre problème, mais vous devez garder ces choses à l'esprit lors du chargement des images dans une liste à l'aide de Glide.

La principale partie menaçante de votre problème est la taille de l'image. L'image que vous obtenez fait presque 1 Mo chacune! Ce qui est en fait trop grand pour les afficher dans une liste de plus de 300 articles. Donc, si vous faites également du côté serveur, il est toujours recommandé d'avoir les images dans plusieurs tailles différentes.

Par exemple, en cas d'afficher une liste d'amis avec leurs photos de profil, je suggère que vous obteniez d'abord la liste complète sur le serveur. Récupérez ensuite toutes les images de profil et stockez-les localement. Remplissez ensuite le ListView. Et la partie la plus importante est lors du téléchargement d'une photo de profil d'un utilisateur sur le serveur, après l'avoir téléchargée, le serveur doit en conserver plusieurs tailles, par exemple version basse, moyenne et haute résolution. Ainsi, lors de la diffusion des URL de l'image de profil pour ListView, le serveur peut fournir les images en basse résolution car elles seront probablement utilisées pour les miniatures.

Utiliser RecyclerView au lieu de ListView est également un bon appel. Mais cela ne résoudra pas le problème que vous avez ici lorsque vous êtes dans un appareil bas de gamme.

OMM n'a rien à voir avec ce que vous pouvez résoudre par programme. Vous devez redimensionner votre image dans une version de résolution inférieure.

Vous pouvez également vérifier le Glide mécanisme de mise en cache aussi. Je vous suggère d'utiliser la stratégie de mise en cache afin que chaque fois que vous n'ayez pas à charger l'image depuis le serveur.

Bonne chance.

9
Reaz Murshed
  1. Assurez-vous que ImageView a match_parent ou dp fixe car les dimensions wrap_content permettent à Glide de charger des bitmaps pleine résolution.
  2. .placeholder () affiche une image au lieu d'un espace vide lors du chargement d'un grand bitmap
  3. .thumbnail (float) charge rapidement une version sous-échantillonnée pendant que l'image plus grande se charge en arrière-plan
  4. Regardez également autour de Glide issues , peut-être que vous trouverez quelque chose d'utile.
5
Jamil Hasnine Tamim

L'utilisation de Glide ne garantit aucun Out of Memory erreurs, vous devez utiliser plusieurs petites étapes pour réduire la probabilité de ne pas obtenir OOM's.

Étape 1: Comprendre le mécanisme de mise en cache dans Glide

Étape 2: Je préfère charger thumbnails dans recyclerview

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

N'oubliez pas de toujours demander une image de petite taille si des images plus grandes ou HD ne sont pas nécessaires.

Utilisez recyclerView au lieu de ListView. Il élément unique réutilisable pour le rendu des éléments. J'utilise glide avec recyclerView où je charge des fonds d'écran avec plus de 100 éléments.

Dans ListView chaque fois que vous créez une vue, si vous avez 100+ vues et cela créera 100+ vues où comme dans recyclerview il crée combien d'éléments visibles sont là dans l'écran +2.

2

J'ai fait face au même problème. Je partage la façon dont je l'ai résolu. Créez un dossier nommé drawable-nodpi Placez vos fichiers golive_load_image Et golive_cancel_im‌​age Dans ce dossier, et supprimez ces deux fichiers image d'un autre endroit comme drawable-ldpi, drawable-hdpi Etc (si vous en avez). Et ajoutez 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);
2

La raison est que lors du défilement, glide continue de faire le processus d'image, même les vues associées sont supprimées de la liste. Ajoutez ce code dans le onScrollStateChanged de votre listview.

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

Une approche différente peut probablement être adoptée pour résoudre ce problème. Pour ce faire, vous pouvez utiliser un autre ImageAdapter avec

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

cela ne plante pas en utilisant MediaStoreThumbFetcher

Pour avoir plus de contrôle sur la charge, procédez comme suit avec 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);
    }
}

En utilisant les API suivantes, nous pouvons déterminer la mémoire libre laissée et la taille du bitmap

Vous pouvez vérifier la mémoire disponible et les détails du bitmap (si nécessaire) en tant que pré-vérification

Vérifiez la quantité de mémoire libre restante

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

Vérifiez la taille du bitmap que nous voulons charger

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

Pour plus de détails, passez par méthodes Java pour vérifier la mémoire et le bitmap et discussion github

1
Sreehari

Afin d'éviter une erreur de mémoire insuffisante, vous pouvez simplement prendre des précautions pour vous assurer qu'elle ne se produira pas. Donc, la réponse à cette question est en fait un tas de suggestions que l'on peut suggérer. Moi aussi.

Comme suggéré par @ Reaz Murshed je recommande également d'avoir les images dans plusieurs tailles différentes. En dehors de cela, je voudrais ajouter quelques éléments supplémentaires qui pourraient vous aider à analyser ce problème et à le résoudre.

Si je me souviens bien, OOM a toujours été une erreur d'utilisation, largeHeap ne fera que la retarder; ou si c'est une charge importante, ce n'est peut-être pas possible. Je vous suggère donc de suivre ce lien pour diagnostiquer les fuites de mémoire.

Les traces de pile de OutOfMemoryErrors n'aident pas du tout à les diagnostiquer. Il vous dit juste qu'il est cassé et que quelque chose a rempli la mémoire. Ce remplissage se produit bien avant que l'exception réelle ne soit levée. Cela implique également qu'en général, tout ce qui jette le OOM n'est pas réellement le coupable. La seule exception à cela est lorsque la quantité de mémoire allouée est simplement trop grande, par exemple: un tableau à allouer est plus grand que la mémoire maximale, alors vous savez que certains calculs se sont vraiment mal passés, comme le ferait une image 32000x32000 @ 4 prendre environ 4 Go de mémoire.

Si vous pouvez reproduire: obtenez un vidage de tas et analysez l'utilisation de votre application. Étapes de diagnostic normales du MOO:

  • Reproduire l'exception (attendez de la voir dans LogCat)

  • Prenez un vidage de tas (pour analyser les fuites de mémoire)

  • Analysez-le pour les gros objets et les fuites

Dans le lien partagé ci-dessus, il y a peu d'autres liens concernant how to take heap dump? et issues identiques à celui-ci.

Je vous suggère donc d'analyser les fuites de mémoire et de prendre les mesures nécessaires pour éviter les MOO.

J'espère que cela vous aidera.

1
Pravin Divraniya
  • vos images ne doivent pas être trop grandes (si elles le sont, utilisez .thumbnail(...f))
  • utilisez .skipMemoryCache(true) si vous n'êtes pas obligé de conserver les images dans le cache
  • vous pouvez utiliser .diskCacheStrategy(DiskCacheStrategy.NONE) pour désactiver le cache disque
1
Misagh