Question

J'ai une vue en liste avec deux boutons d'image sur chaque ligne. Lorsque vous cliquez sur la ligne de la liste, une nouvelle activité est lancée. J'ai dû créer mes propres onglets en raison d'un problème de disposition de la caméra. L'activité lancée pour le résultat est une carte. Si je clique sur mon bouton pour lancer l'aperçu de l'image (charger une image de la carte SD), l'application retourne de l'activité à l'activité listview au gestionnaire de résultats pour relancer ma nouvelle activité qui n'est rien plus qu'un widget d'image.

L'aperçu de l'image dans la vue liste est effectué avec le curseur et ListAdapter . Cela simplifie les choses, mais je ne sais pas comment mettre une image redimensionnée (c'est-à-dire une taille de bit plus petite et non pas un pixel comme le src du bouton d'image à la volée. J'ai donc redimensionné l'image qui est sorti de la caméra du téléphone.

Le problème, c’est que je reçois une erreur de mémoire insuffisante lorsqu’il essaie de revenir en arrière et de relancer la 2e activité.

  • Existe-t-il un moyen de construire facilement l'adaptateur de liste, ligne par ligne, où je peux redimensionner à la volée ( bit wise )?

Cela serait préférable car je dois également apporter quelques modifications aux propriétés des widgets / éléments de chaque ligne, car je ne parviens pas à sélectionner une ligne avec l'écran tactile en raison du problème de focus. ( Je peux utiliser la bille roulante. )

  • Je sais que je peux redimensionner et sauvegarder mon image hors bande, mais ce n'est pas vraiment ce que je veux faire, mais un exemple de code pour cela serait bien.

Dès que j'ai désactivé l'image dans la liste, tout a bien fonctionné.

FYI: Voici comment je le faisais:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

R.id.imagefilename est une ButtonImage .

Voici mon logcat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

J'ai également une nouvelle erreur lors de l'affichage d'une image:

01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
Était-ce utile?

La solution

La classe Formation Android , " Affichage des bitmaps de manière efficace , offre de bonnes informations pour comprendre et traiter l’exception java.lang.OutOfMemoryError: la taille d’un bitmap dépasse le budget de la machine virtuelle lors du chargement de bitmaps.

Lire les dimensions et le type de bitmap

La classe BitmapFactory fournit plusieurs méthodes de décodage ( decodeByteArray () , decodeFile () , decodeResource () , etc.) pour créer un Bitmap à partir de diverses sources. Choisissez la méthode de décodage la plus appropriée en fonction de la source de données de votre image. Ces méthodes tentent d'allouer de la mémoire au bitmap construit et peuvent par conséquent générer facilement une exception OutOfMemory . Chaque type de méthode de décodage comporte des signatures supplémentaires vous permettant de spécifier des options de décodage via la classe BitmapFactory.Options . Définir la propriété inJustDecodeBounds sur true lors du décodage évite l'allocation de mémoire, en renvoyant null pour l'objet bitmap mais en définissant outWidth , outHeight et outMimeType . Cette technique vous permet de lire les dimensions et le type des données d’image avant la construction (et l’allocation de mémoire) du bitmap.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

Pour éviter les exceptions java.lang.OutOfMemory , vérifiez les dimensions d'un bitmap avant de le décoder, sauf si vous faites absolument confiance à la source pour vous fournir des données d'image de taille prévisible, qui se logent facilement dans la mémoire disponible. .

Charger une version réduite en mémoire

Maintenant que les dimensions de l'image sont connues, elles peuvent être utilisées pour décider si l'image complète doit être chargée en mémoire ou si une version sous-échantillonnée doit être chargée à la place. Voici quelques facteurs à prendre en compte:

  • Estimation de l'utilisation de la mémoire lors du chargement de l'image complète en mémoire.
  • La quantité de mémoire que vous êtes prêt à engager pour charger cette image, en fonction des autres besoins en mémoire de votre application.
  • Dimensions du composant ImageView ou d'interface utilisateur cible dans lequel l'image doit être chargée.
  • Taille de l'écran et densité du périphérique actuel.

Par exemple, il ne vaut pas la peine de charger en mémoire une image 1024x768 pixels si elle est éventuellement affichée dans une vignette de 128x96 pixels dans un ImageView .

Pour demander au décodeur de sous-échantillonner l'image, en chargeant une version plus petite en mémoire, définissez inSampleSize sur true dans votre objet BitmapFactory.Options . . Par exemple, une image de résolution 2048x1536 qui est décodée avec un inSampleSize de 4 génère un bitmap d’environ 512x384. Le chargement en mémoire utilise 0,75 Mo au lieu de 12 Mo pour l'image complète (en supposant une configuration bitmap de ARGB_8888 ). Voici une méthode pour calculer une valeur de taille d’échantillon égale à une puissance de deux en fonction d’une largeur et d’une hauteur cibles:

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

Remarque : une puissance de deux est calculée car le décodeur utilise un   valeur finale en arrondissant à la puissance proche de deux, comme indiqué dans le    inSampleSize documentation.

Pour utiliser cette méthode, commencez par décoder avec inJustDecodeBounds sur true , transmettez les options, puis décodez-les à nouveau à l'aide de la nouvelle valeur inSampleSize . et inJustDecodeBounds défini sur false :

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

Cette méthode facilite le chargement d'une image bitmap de taille arbitrairement grande dans un ImageView qui affiche une vignette de 100 x 100 pixels, comme indiqué dans l'exemple de code suivant:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

Vous pouvez suivre un processus similaire pour décoder des bitmaps provenant d’autres sources, en remplaçant le code

Autres conseils

Pour corriger l'erreur OutOfMemory, procédez comme suit:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

Cette option inSampleSize réduit la consommation de mémoire.

Voici une méthode complète. Tout d'abord, il lit la taille de l'image sans décoder le contenu lui-même. Ensuite, il trouve la meilleure valeur inSampleSize . La valeur doit être égale à 2 et, finalement, l'image est décodée.

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

        // The new size we want to scale to
        final int REQUIRED_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            scale *= 2;
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}

J'ai apporté une petite amélioration au code de Fedor. Il fait fondamentalement la même chose, mais sans (à mon avis) laide tout en boucle et il en résulte toujours une puissance de deux. Félicitations à Fedor pour avoir créé la solution initiale, j’ai été bloqué jusqu’à ce que j’ai trouvé la sienne, puis j’ai pu faire celle-ci:)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}

Je viens d’expérience iOS et j’étais frustré de découvrir un problème lié à quelque chose d'aussi fondamental que le chargement et l'affichage d'une image. Après tout, tout le monde qui a ce problème essaie d’afficher des images de taille raisonnable. Quoi qu'il en soit, voici les deux modifications qui ont résolu mon problème (et ont rendu mon application très réactive).

1) À chaque fois que vous BitmapFactory.decodeXYZ () , veillez à transmettre un BitmapFactory.Options avec inPurgeable défini sur < code> true (et de préférence avec inInputShareable également défini sur true ).

2) N'utilisez JAMAIS Bitmap.createBitmap (width, height, Config.ARGB_8888) . Je veux dire JAMAIS! Je n'ai jamais eu cette chose pas soulevé d'erreur de mémoire après quelques passes. Aucune quantité de recycle () , System.gc () , quoi que vous ayez aidé. Il a toujours soulevé l'exception. L’une des autres méthodes qui fonctionnent réellement est d’avoir une image factice dans votre dessinable (ou un autre bitmap que vous avez décodé à l’étape 1 ci-dessus), de redimensionner celle-ci, puis de manipuler le bitmap obtenu (par exemple, en le transmettant à un canevas). pour plus de plaisir). Donc, ce que vous devez utiliser à la place est: Bitmap.createScaledBitmap (srcBitmap, largeur, hauteur, false) . Si, pour une raison quelconque, vous DEVEZ utiliser la méthode brute force create, transmettez au moins Config.ARGB_4444 .

Ceci est presque garanti pour vous faire économiser des heures sinon des jours. Tout ce qui parle de redimensionnement de l'image, etc. ne fonctionne pas vraiment (à moins que vous ne considériez une mauvaise taille ou une image dégradée comme solution).

C’est un bug connu , ce n'est pas à cause de gros des dossiers. Depuis qu'Android met en cache les dessins, il manque de mémoire après avoir utilisé peu d'images. Mais j'ai trouvé un autre moyen de le faire, en sautant le système de cache par défaut d'Android.

Solution : Déplacez les images sur "actifs". dossier et utilisez la fonction suivante pour obtenir BitmapDrawable:

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}

J'ai eu le même problème et je l'ai résolu en évitant les fonctions BitmapFactory.decodeStream ou decodeFile et en utilisant BitmapFactory.decodeFileDescriptor

decodeFileDescriptor semble appeler des méthodes natives différentes de celles de decodeStream / decodeFile.

Quoi qu’il en soit, c’est ce qui a bien fonctionné (notez que j’ai ajouté certaines options, mais ce n’est pas ce qui a fait la différence. Ce qui est essentiel, c’est l’appel à BitmapFactory.decodeFileDescriptor au lieu de decodeStream ou decodeFile ):

private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
    bfOptions.inTempStorage=new byte[32 * 1024]; 

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

Je pense qu'il y a un problème avec la fonction native utilisée dans decodeStream / decodeFile. J'ai confirmé qu'une méthode native différente est appelée lors de l'utilisation de decodeFileDescriptor. De plus, ce que j’ai lu, c’est "que les images (bitmaps) ne sont pas allouées de manière standard en Java mais via des appels natifs; les allocations sont faites en dehors du tas virtuel, mais sont a compté contre elle! "

Je pense que le meilleur moyen d'éviter le OutOfMemoryError est de le confronter et de le comprendre.

J'ai créé une application pour provoquer intentionnellement OutOfMemoryError et surveillez l'utilisation de la mémoire.

Après avoir effectué de nombreuses expériences avec cette appli, j'ai les conclusions suivantes:

Je vais parler des versions du SDK avant Honey Comb.

  1. Les images bitmap sont stockées dans le tas natif, mais les ordures sont automatiquement collectées. Il est inutile d'appeler recycle ().

  2. Si {taille de segment de machine virtuelle} + {mémoire de segment de mémoire native allouée} > = {limite de taille de segment de mémoire virtuelle pour le périphérique} et que vous essayez de créer un bitmap, le MOO sera lancé.

    AVIS: La taille de VM HEAP est comptée plutôt que la mémoire allouée par VM.

  3. La taille de la mémoire virtuelle ne sera jamais réduite, même si la mémoire de la machine virtuelle allouée est réduite.

  4. Vous devez donc maintenir la mémoire de pointe maximale de la machine virtuelle au maximum pour éviter que la taille de la machine virtuelle ne devienne trop importante pour économiser la mémoire disponible pour les bitmaps.

  5. Appeler manuellement System.gc () n'a pas de sens, le système l'appellera d'abord avant d'essayer d'augmenter la taille du segment de mémoire.

  6. La taille de segment de mémoire native ne sera jamais réduite non plus, mais elle ne compte pas pour le MOO, vous n'avez donc pas à vous en soucier.

Ensuite, parlons du SDK Starts from Honey Comb.

  1. Le bitmap est stocké dans la pile de la VM, la mémoire native n'est pas comptée pour le MOO.

  2. La condition pour le MOO est beaucoup plus simple: {Taille du segment de mémoire virtuelle} > = {Limite de taille de segment de mémoire virtuelle pour le périphérique}.

  3. Ainsi, vous disposez de plus de mémoire disponible pour créer des images bitmap avec la même limite de taille de segment de mémoire, mais le MOO est moins susceptible d'être émis.

Voici certaines de mes observations sur le ramassage des ordures ménagères et les fuites de mémoire.

Vous pouvez le voir vous-même dans l'application. Si une activité a exécuté une tâche asynchrone qui était toujours en cours d'exécution après la destruction de l'activité, l'activité ne sera pas récupérée avant la fin de la tâche asynchrone.

En effet, AsyncTask étant une instance d'une classe interne anonyme, elle contient une référence de l'activité.

L'appel de AsyncTask.cancel (true) n'arrêtera pas l'exécution si la tâche est bloquée dans une opération IO dans le thread d'arrière-plan.

Les rappels sont également des classes internes anonymes. Par conséquent, si une instance statique de votre projet les contient et ne les libère pas, de la mémoire serait perdue.

Si vous planifiez une tâche répétitive ou retardée, par exemple un minuteur, et que vous n'appelez pas cancel () et purge () dans onPause (), la mémoire risque de fuir.

J'ai récemment rencontré beaucoup de questions sur les exceptions de MOO et la mise en cache. Le guide du développeur contient un très bon article sur ce sujet, mais certains a tendance à échouer dans sa mise en œuvre de manière appropriée.

À cause de cela, j'ai écrit un exemple d'application qui illustre la mise en cache dans un environnement Android. Cette mise en œuvre n'a pas encore reçu de MOO.

Recherchez un lien vers le code source à la fin de cette réponse.

Configuration requise:

  • API Android 2.1 ou supérieure (je ne pouvais tout simplement pas obtenir la mémoire disponible pour une application dans l'API 1.6 - c'est le seul élément de code qui ne fonctionne pas dans l'API 1.6)
  • Package de support Android

Capture d'écran

Caractéristiques:

  • Conserve le cache en cas de changement d'orientation , à l'aide d'un singleton
  • Utilisez un huitième de la mémoire d'application attribuée au cache (modifiez-le si vous le souhaitez)
  • Les grandes images bitmap sont mises à l'échelle (vous pouvez définir le nombre maximal de pixels que vous souhaitez autoriser)
  • Contrôle qu'il existe une connexion Internet avant de télécharger les bitmaps
  • s'assure que vous n'instanciez que une tâche par ligne
  • Si vous lâchez le ListView , il ne téléchargera tout simplement pas les bitmaps entre

Ceci n'inclut pas:

  • Mise en cache du disque. Cela devrait être facile à implémenter de toute façon - il suffit de pointer vers une tâche différente qui récupère les images bitmap du disque

Exemple de code:

Les images en cours de téléchargement sont des images (75x75) de Flickr. Cependant, indiquez les URL de l'image que vous souhaitez traiter, et l'application la réduira si elle dépasse le maximum. Dans cette application, les URL sont simplement dans un tableau String .

Le LruCache a une bonne façon de traiter les bitmaps. Cependant, dans cette application, j'ai inséré une instance d'un LruCache dans une autre classe de cache que j'ai créée afin d'améliorer la faisabilité de l'application.

Le truc essentiel de

Cache.java (la méthode loadBitmap () est la plus importante):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

Vous ne devriez avoir besoin de rien modifier dans le fichier Cache.java, sauf si vous souhaitez implémenter la mise en cache du disque.

Le contenu critique de

MainActivity.java:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView () est appelé très souvent. Ce n’est normalement pas une bonne idée de télécharger des images là-bas si nous n’avons pas mis en place de vérification qui nous assure que nous ne commencerons pas une quantité infinie de threads par ligne. Cache.java vérifie si le rowObject.mBitmapUrl est déjà dans une tâche et si c'est le cas, il n'en démarrera pas une autre. Par conséquent, il est fort probable que nous ne dépassons pas la restriction de la file d'attente de travail du pool AsyncTask .

Téléchargement:

Vous pouvez télécharger le code source à partir de https://www.dropbox.com/s /pvr9zyl811tfeem/ListViewImageCache.zip .

Derniers mots:

Je teste cela depuis quelques semaines maintenant, je n'ai pas encore eu une seule exception de MOO. Je l’ai testé sur l’émulateur, sur mon Nexus One et sur mon Nexus S. J'ai également testé des URL d’image contenant des images de qualité HD. Le seul goulot d’étranglement est que le téléchargement prend plus de temps.

Il n’ya qu’un seul scénario où je peux imaginer que le MOO apparaîtra, c’est-à-dire que si nous téléchargeons de très grandes images, et avant qu’elles ne soient redimensionnées et mises en cache, occuperont simultanément plus de mémoire et entraîneront une perte de mémoire. OOM. Mais ce n'est pas

J'ai procédé comme suit pour prendre l’image et la redimensionner à la volée. J'espère que cela aide

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    

Il semble que ce soit un problème très long, avec beaucoup d'explications différentes. J'ai suivi les conseils des deux réponses les plus courantes présentées ici, mais aucune d'entre elles n'a résolu mes problèmes de machine virtuelle, affirmant qu'elle ne pouvait pas se permettre les octets nécessaires pour exécuter la partie décodage du processus. Après quelques recherches, j'ai appris que le vrai problème, c'est le processus de décodage qui éloigne le tas NATIVE .

Voir ici: Le MOO de BitmapFactory me rend folle

Cela m’amène à un autre sujet de discussion où j’ai trouvé deux solutions supplémentaires à ce problème. L’une consiste à appeler System.gc (); manuellement après l’affichage de votre image. Mais cela oblige réellement votre application à utiliser PLUS de mémoire afin de réduire le tas natif. La meilleure solution à partir de la version 2.0 (Donut) consiste à utiliser l'option BitmapFactory "inPurgeable". J'ai donc simplement ajouté o2.inPurgeable = true; juste après o2.inSampleSize = scale; .

Plus d'informations sur ce sujet ici: est la limite de la mémoire perdue seulement 6M?

Maintenant, après avoir dit tout cela, je suis également un cancre complet avec Java et Android. Donc, si vous pensez que c'est un moyen terrible de résoudre ce problème, vous avez probablement raison. ;-) Mais cela a fonctionné à merveille pour moi et il m'est devenu impossible d'exécuter la VM à partir du cache de tas maintenant. Le seul inconvénient que je puisse trouver est que vous supprimez votre image dessinée en cache. Ce qui signifie que si vous revenez à DROITE à cette image, vous la redessinerez à chaque fois. Dans le cas où mon application fonctionne, ce n'est pas vraiment un problème. Votre kilométrage peut varier.

malheureusement si aucune des solutions ci-dessus ne fonctionne, ajoutez cette option à votre fichier Manifest . Dans la balise application

 <application
         android:largeHeap="true"

Utilisez ceci bitmap.recycle (); Ceci aide sans problème de qualité d'image.

J'ai une solution beaucoup plus efficace qui ne nécessite aucune mise à l'échelle. Décodez simplement votre bitmap une seule fois, puis mettez-la en cache dans une carte contre son nom. Ensuite, récupérez simplement le bitmap en fonction du nom et définissez-le dans ImageView. Il n'y a plus rien à faire.

Cela fonctionnera car les données binaires réelles du bitmap décodé ne sont pas stockées dans le segment de mémoire dalvik. Il est stocké à l'extérieur. Ainsi, chaque fois que vous décodez un bitmap, il alloue de la mémoire en dehors du tas de la VM, qui n'est jamais récupérée par le GC.

Pour vous aider à mieux comprendre cela, imaginez que vous ayez conservé votre image dans le dossier pouvant être dessiné. Vous obtenez simplement l'image en faisant un getResources (). GetDrwable (R.drawable.). Cela ne décodera PAS votre image à chaque fois, mais réutilisera une instance déjà décodée à chaque fois que vous l'appelez. Donc, essentiellement, il est mis en cache.

Maintenant que votre image est quelque part dans un fichier (ou peut même provenir d'un serveur externe), il est DE VOTRE responsabilité de mettre en cache l'instance bitmap décodée afin qu'elle soit réutilisée là où elle est nécessaire.

J'espère que cela vous aidera.

J'ai résolu le même problème de la manière suivante.

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);

Il y a deux problèmes ici ....

  • La mémoire bitmap n'est pas dans le segment de mémoire de la machine virtuelle, mais dans le segment de mémoire natif - voir Le MOO de BitmapFactory me rend folle
  • La récupération de place pour le segment de mémoire natif est plus paresseuse que celle de la machine virtuelle. Vous devez donc être assez agressif pour effectuer bitmap.recycle et bitmap = null chaque fois que vous passez à travers une activité onPause ou onDestroy

Cela a fonctionné pour moi!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}

Aucune des réponses ci-dessus n'a fonctionné pour moi, mais je suis parvenu à une solution de contournement horriblement moche qui a résolu le problème. J'ai ajouté une très petite image 1x1 pixel à mon projet en tant que ressource et je l'ai chargé dans mon ImageView avant d'appeler le garbage collection. Je pense qu'il se peut que ImageView ne publie pas le bitmap, de sorte que GC ne l'a jamais repris. C'est moche, mais cela semble fonctionner pour le moment.

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.

Excellentes réponses ici, mais je voulais une classe pleinement utilisable pour résoudre ce problème .. alors j'en ai fait une.

Voici ma classe BitmapHelper qui est la preuve OutOfMemoryError: -)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

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

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}

Cela fonctionne pour moi.

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

et ceci est sur C # monodroid. vous pouvez facilement changer le chemin de l'image. Ce qui est important ici, ce sont les options à définir.

Cela semble être l’endroit approprié pour partager ma classe d’utilitaires pour le chargement et le traitement d’images avec la communauté. Nous vous invitons à l’utiliser et à le modifier librement.

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://stackoverflow.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
     * @param width
     * @param height
     * @param config
     * @return
     */
    public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
        long pixels=width*height;
        switch(config){
        case ALPHA_8: // 1 byte per pixel
            return pixels;
        case ARGB_4444: // 2 bytes per pixel, but depreciated
            return pixels*2;
        case ARGB_8888: // 4 bytes per pixel
            return pixels*4;
        case RGB_565: // 2 bytes per pixel
            return pixels*2;
        default:
            return pixels;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}

Dans l'une de mes applications, je dois prendre une photo depuis Appareil photo / Galerie . Si l'utilisateur clique sur l'image de l'appareil photo (2MP, 5MP ou 8MP), la taille de l'image varie de Ko à MB . Si la taille de l'image est inférieure (ou inférieure à 1 à 2 Mo) au-dessus du code, mais si j'ai une image de taille supérieure à 4 Mo ou 5 Mo, MOO entre dans le cadre: (

alors j'ai travaillé pour résoudre ce problème & amp; Enfin, j'ai apporté les améliorations ci-dessous au code de Fedor (All Credit to Fedor pour avoir fait une si belle solution):)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.inInputShareable = true; // Which kind of reference will be used to
                                    // recover the Bitmap data after being
                                    // clear, when it will be used in the
                                    // future

    BitmapFactory.decodeFile(fPath, opts);

    // The new size we want to scale to
    final int REQUIRED_SIZE = 70;

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // 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.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

J'espère que cela aidera les copains qui font face au même problème!

pour plus d'informations, consultez this a>

Je viens juste de connaître ce problème il y a quelques minutes. Je l'ai résolu en faisant un meilleur travail de gestion de mon adaptateur Listview. Je pensais que c’était un problème avec les centaines d’images 50 x 50 pixels que j’utilisais. En fait, j’essayais de gonfler ma vue personnalisée à chaque affichage de la ligne. Tout simplement en testant si la ligne avait été gonflée, j'ai éliminé cette erreur et j'utilise des centaines de bitmaps. Ceci est en fait pour un Spinner, mais l'adaptateur de base fonctionne de la même manière pour un ListView. Ce correctif simple a également considérablement amélioré les performances de l'adaptateur.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...

J'ai passé toute la journée à tester ces solutions et la seule chose qui a fonctionné pour moi, ce sont les approches ci-dessus pour obtenir l'image et appeler manuellement le GC, ce qui, à mon avis, n'est pas censé être nécessaire, mais c'est la seule solution. Ce qui a fonctionné lorsque je soumettais mon application à des tests de charge importante, en passant d'une activité à l'autre. Mon application contient une liste d'images miniatures dans une liste (dans l'activité A) et, lorsque vous cliquez sur l'une de ces images, vous accédez à une autre activité (indiquant l'activité B) qui affiche une image principale pour cet élément. Lorsque je basculais entre les deux activités, j'obtenais éventuellement l'erreur de MOO et l'application se fermait de force.

Quand j'arriverais à mi-chemin de la liste, cela planterait.

Maintenant, lorsque j'implémente ce qui suit dans l'activité B, je peux parcourir toute la liste sans problème et continuer, encore et encore ... et c'est rapide comme bonjour.

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}

Ce problème ne se produit que dans les émulateurs Android. J'ai également rencontré ce problème dans un émulateur, mais lorsque j'ai archivé un périphérique, cela a bien fonctionné.

Alors, veuillez enregistrer un appareil. Il peut être exécuté sur le périphérique.

Mes 2 cents: j'ai résolu mes erreurs de MOO avec des bitmaps par:

a) redimensionner mes images par un facteur de 2

b) en utilisant la bibliothèque Picasso dans mon adaptateur personnalisé pour un ListView, avec un appel unique dans getView comme ceci: < code> Picasso.with (contexte) .load (R.id.myImage) .into (R.id.myImageView);

utilisez ce code pour chaque image sélectionnée dans SdCard ou dessiner pour convertir un objet bitmap.

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

utilisez votre chemin d'image pour ImageData_Path.get (img_pos) .getPath () .

Toutes les solutions ici nécessitent la définition d'un IMAGE_MAX_SIZE. Cela limite les appareils dotés d’un matériel plus puissant et si la taille de l’image est trop basse, elle aura une apparence laide sur l’écran HD.

Je suis sorti avec une solution qui fonctionne avec mon Samsung Galaxy S3 et plusieurs autres appareils, y compris ceux moins puissants, avec une meilleure qualité d'image lorsqu'un appareil plus puissant est utilisé.

L’essentiel est de calculer la mémoire maximale allouée pour l’application sur un appareil particulier, puis de définir l’échelle la plus basse possible sans dépasser cette mémoire. Voici le code:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

Je règle la mémoire maximale utilisée par ce bitmap sur 25% de la mémoire maximale allouée. Vous devrez peut-être l'adapter à vos besoins et vous assurer que ce bitmap est nettoyé et qu'il ne reste pas en mémoire lorsque vous avez terminé. En l'utilisant. J'utilise généralement ce code pour effectuer la rotation des images (bitmap source et de destination); mon application doit donc charger 2 bitmaps en mémoire en même temps, et 25% me donnent un bon tampon sans manquer de mémoire lors de la rotation des images.

J'espère que cela aidera quelqu'un là-bas ..

Un tel OutofMemoryException ne peut pas être totalement résolu en appelant System.gc () , etc.

.

En vous référant au Cycle de vie d'une activité

Les états d'activité sont déterminés par le système d'exploitation lui-même, en fonction de l'utilisation de la mémoire pour chaque processus et de la priorité de chaque processus.

Vous pouvez considérer la taille et la résolution de chacune des images bitmap utilisées. Je recommande de réduire la taille, de ré-échantillonner à une résolution inférieure, de se référer à la conception des galeries (une petite image PNG et une image originale.)

En règle générale, la taille de segment de mémoire d'un appareil Android ne représente que 16 Mo (varie selon l'appareil / le système d'exploitation, voir l'article Taille du segment de mémoire ), si vous êtes Lorsque vous chargez les images et qu’elles dépassent la taille de 16 Mo, vous obtiendrez une exception de mémoire insuffisante. Au lieu d’utiliser Bitmap pour, le chargement d’images à partir de la carte SD, des ressources ou même du réseau, essayez d’utiliser getImageUri , le chargement de bitmap nécessite plus de mémoire, ou vous pouvez définir bitmap sur null si votre travail est effectué avec cette bitmap.

Ce code aidera à charger une grande image bitmap depuis Drawable

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top