web-dev-qa-db-fra.com

BitmapFactory.decodeStream manque de mémoire malgré l'utilisation d'une taille d'échantillon réduite

J'ai lu de nombreux articles sur les problèmes d'allocation de mémoire liés au décodage de bitmaps, mais je ne parviens toujours pas à trouver la solution au problème suivant, même après avoir utilisé le code fourni sur le site Web officiel.

Voici mon code: 

public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) {

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len;
    try {
        while ((len = inputStream.read(buffer)) > -1) {
        baos.write(buffer, 0, len);
        }
        baos.flush();
        InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
        InputStream is2 = new ByteArrayInputStream(baos.toByteArray());

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is1, null, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inPurgeable = true;
        options.inInputShareable = true;
        options.inJustDecodeBounds = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        return BitmapFactory.decodeStream(is2, null, options);

    } catch (Exception e) {
        e.printStackTrace();

        return null;
    }
}

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

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }


    return inSampleSize;
}

bitmap = decodeSampledBitmapFromResource(inputStream, 600, 600); 

Je reçois "Erreur de mémoire insuffisante sur une allocation d'octets 3250016" dans cette ligne:

return BitmapFactory.decodeStream(is2, null, options);

Il me semble que 3,2 Mo est assez petit pour être alloué. Où vais-je mal? Comment puis-je résoudre ça?

EDIT

Après avoir examiné cette solution ICI de N-Joy, cela fonctionne bien avec la taille requise 300, mais ma taille requise est de 800; je reçois donc toujours l'erreur.

13
Goofy

La méthodedecodeSampledBitmapFromResourcen'est pas efficace en mémoire car elle utilise 3 flux: le baos ByteArrayOutputStream, ByteArrayInputStream is1 et ByteArrayInputStream is2, chacun d'entre eux stocke les mêmes données de flux de l'image (un tableau d'octets pour chaque ).

Et lorsque je teste avec mon appareil (LG Nexus 4) pour décoder une image 2560x1600 sur une carte SD à la taille cible 800, il faut quelque chose comme ceci: 

03-13 15:47:52.557: E/DecodeBitmap(11177): dalvikPss (beginning) = 1780
03-13 15:47:53.157: E/DecodeBitmap(11177): dalvikPss (decoding) = 26393
03-13 15:47:53.548: E/DecodeBitmap(11177): dalvikPss (after all) = 30401 time = 999

Nous pouvons voir: trop de mémoire allouée (28,5 Mo) juste pour décoder une image en pixels 4096000.

Solution : nous lisons InputStream et stockons les données directement dans un tableau d'octets et utilisons ce tableau d'octets pour le reste du travail. 
Exemple de code:

public Bitmap decodeSampledBitmapFromResourceMemOpt(
            InputStream inputStream, int reqWidth, int reqHeight) {

        byte[] byteArr = new byte[0];
        byte[] buffer = new byte[1024];
        int len;
        int count = 0;

        try {
            while ((len = inputStream.read(buffer)) > -1) {
                if (len != 0) {
                    if (count + len > byteArr.length) {
                        byte[] newbuf = new byte[(count + len) * 2];
                        System.arraycopy(byteArr, 0, newbuf, 0, count);
                        byteArr = newbuf;
                    }

                    System.arraycopy(buffer, 0, byteArr, count, len);
                    count += len;
                }
            }

            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(byteArr, 0, count, options);

            options.inSampleSize = calculateInSampleSize(options, reqWidth,
                    reqHeight);
            options.inPurgeable = true;
            options.inInputShareable = true;
            options.inJustDecodeBounds = false;
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;

            int[] pids = { Android.os.Process.myPid() };
            MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
            Log.e(TAG, "dalvikPss (decoding) = " + myMemInfo.dalvikPss);

            return BitmapFactory.decodeByteArray(byteArr, 0, count, options);

        } catch (Exception e) {
            e.printStackTrace();

            return null;
        }
    }

La méthode qui fait le calcul:

public void onButtonClicked(View v) {
        int[] pids = { Android.os.Process.myPid() };
        MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);

        long startTime = System.currentTimeMillis();

        FileInputStream inputStream;
        String filePath = Environment.getExternalStorageDirectory()
                .getAbsolutePath() + "/test2.png";
        File file = new File(filePath);
        try {
            inputStream = new FileInputStream(file);
//          mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
            mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream, 800,
                    800);
            ImageView imageView = (ImageView) findViewById(R.id.image);
            imageView.setImageBitmap(mBitmap);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (after all) = " + myMemInfo.dalvikPss
                + " time = " + (System.currentTimeMillis() - startTime));
    }

Et le résultat:

03-13 16:02:20.373: E/DecodeBitmap(13663): dalvikPss (beginning) = 1823
03-13 16:02:20.923: E/DecodeBitmap(13663): dalvikPss (decoding) = 18414
03-13 16:02:21.294: E/DecodeBitmap(13663): dalvikPss (after all) = 18414 time = 917
36
Binh Tran

Il s’agit d’un problème courant auquel l’utilisateur est normalement confronté lorsqu’il joue avec des images bitmap volumineuses. De nombreuses questions sont discutées sur place, ici , ici , ici et ici et bien d’autres , même si l'utilisateur n'est pas capable de manipuler la solution exacte.

Je suis tombé par hasard sur une bibliothèque qui gérait les bitmaps en douceur et sur les autres liens énumérés ci-dessous. J'espère que cela t'aides! 

smoothie-bibliothèque

Android-BitmapCache

Android-Universal-Image-LoaderSolution pour OutOfMemoryError: la taille du bitmap dépasse VM budget

4
RobinHood

ARGB_8888 utilise plus de mémoire car il prend une valeur de couleur alpha, ma suggestion est donc d'utiliser RGB_565 comme indiqué ICI

Remarque: la qualité sera un peu faible par rapport à ARGB_8888.

2
nidhi_adiga

J'ai eu beaucoup de problèmes avec l'utilisation de la mémoire Bitmap.

Résultats:

  • La plupart des appareils ont une mémoire de base limitée pour les graphiques, la plupart des petits appareils sont limités à 16 Mo pour l'ensemble des applications, pas seulement votre application.
  • Utilisez des bitmaps 4 bits ou 8 bits ou 16 bits, le cas échéant.
  • Essayez de dessiner des formes à partir de zéro, omettez les bitmaps si possible.
1
AVEbrahimi

Utilisez WebView pour charger dynamiquement autant d’images que vous le souhaitez. Il est construit à l’aide de NDK (niveau faible) et n’a donc pas de GDI restriction de mémoire. Il fonctionne sans heurts et rapidement :)

1
AVEbrahimi

Vous conservez probablement les références bitmap précédentes. J'imagine que vous exécutez ce code plusieurs fois et que vous n'exécutez jamais bitmap.recycle(). La mémoire s'épuise inévitablement.

1
Paul Lammertsma

Les problèmes de manque de mémoire lors du décodage des images bitmap ne sont pas souvent liés à la taille de l'image que vous décodez. Bien sûr, si vous essayez d'ouvrir une image 5000x5000px, vous échouerez avec une erreur OutOfMemoryError, mais avec une taille de 800x800px, c'est totalement raisonnable et devrait bien fonctionner.

Si votre appareil manque de mémoire avec une image de 3,2 Mo, c'est probablement parce que vous perdez du contexte quelque part dans l'application.

C'est la première partie de cet article :

Je suppose que le problème ne se trouve pas dans votre mise en page, il se situe ailleurs dans Votre code. et probablement que vous fuyez le contexte quelque part .

Cela signifie que vous utilisez le contexte d'activité dans des composants qui ne devraient pas, ce qui empêche leur récupération. Étant donné que les composants contiennent souvent des composants, ces activités ne sont pas GC et votre segment de mémoire Java augmentera très rapidement et votre application se bloquera à un moment ou à un autre.

Comme Raghunandan l'a dit, vous devrez utiliser MAT pour déterminer quelle activité/composante est tenue et supprimer la fuite de contexte.

Le meilleur moyen que j’ai trouvé pour le moment de détecter une fuite de contexte est le changement d’orientation. Par exemple, tournez votre ActivityMain plusieurs fois, exécutez MAT et vérifiez si vous n’avez qu’une seule instance d’ActivityMain. Si vous en avez plusieurs (autant que la rotation change), cela signifie qu'il y a une fuite de contexte.

J'ai trouvé il y a des années un bon tutoriel sur l'utilisation de MAT . Peut-être qu'il y en a un meilleur maintenant.

Autres publications sur les fuites de mémoire: 

Android - fuite de mémoire ou?

Erreur de mémoire insuffisante sur l'émulateur Android, mais pas sur le périphérique }

1
ol_v_er

Regardez cette vidéo. http://www.youtube.com/watch?v=_CruQY55HOk . N'utilisez pas system.gc () comme suggéré dans la vidéo. Utilisez un analyseur MAT pour détecter les fuites de mémoire. Le bitmap renvoyé est trop énorme, ce qui provoque une fuite de mémoire, je suppose. 

0
Raghunandan

Il semble que vous ayez une grande image à afficher.

Vous pouvez télécharger une image et la sauvegarder sur sdcard ( exemple ), puis utiliser le code utilisateur this Pour afficher l’image à partir de sdcard.

0
MAC