Problème de sortie de mémoire lors du chargement d'une image sur un objet Bitmap

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

la prévisualisation de l'image sur la liste se fait avec le curseur et ListAdapter . Cela rend cela assez simple, mais je ne suis pas sûr comment je peux mettre une image redimensionnée (c.-à-d.. Plus petite taille de bit pas pixel comme le src pour le bouton image à la volée. Alors j'ai juste redimensionné l'image qui est descendu de l'appareil photo du téléphone.

le problème est que je reçois une erreur de mémoire quand il tente de revenir en arrière et de relancer la 2e activité.

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

cela serait préférable car j'ai aussi besoin d'apporter quelques changements aux propriétés des widgets/éléments dans chaque rangée car je ne suis pas capable de sélectionner une rangée avec Écran tactile à cause du problème de mise au point. ( je peux utiliser des patins à billes. )

  • je sais que je peux faire un redimensionnement hors bande et enregistrer de mon image, 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 sur la vue Liste il a fonctionné très bien à nouveau.

POUR INFO: C'est comme ça que 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 un 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 aussi 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
1133
demandé sur Chrispix 2009-01-25 14:23:37

30 réponses

le formation Android classe," affichage Bitmaps efficacement ", offre de grandes informations pour comprendre et traiter l'exception java.lang.OutOfMemoryError: bitmap size exceeds VM budget lors du chargement Bitmaps.


Lire Dimensions Bitmap et Type

la classe BitmapFactory fournit plusieurs méthodes de décodage( decodeByteArray() , decodeFile() , decodeResource() , etc.) pour la création d'un Bitmap à partir de diverses sources. Choisissez la méthode de décodage la plus appropriée en fonction de votre source de données d'image. Ces méthodes tentent d'allouer de la mémoire pour le bitmap construit et peuvent donc facilement aboutir à une exception OutOfMemory . Chaque type de méthode de décodage a des signatures supplémentaires qui vous permettent de spécifier des options de décodage via la classe BitmapFactory.Options . Paramétrer la propriété inJustDecodeBounds à true tout en décodant évite l'allocation de mémoire, en retournant null pour le bitmap objet mais paramètre 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'attribution 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, à moins que vous ne fassiez absolument confiance à la source pour vous fournir des données d'image de taille prévisible qui s'inscrivent confortablement 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 à considérer:

  • estimation de l'utilisation de la mémoire pour charger l'image complète en mémoire.
  • La quantité de mémoire que vous êtes prêt à s'engager à chargement de cette image étant donné toutes les autres exigences de mémoire de votre application.
  • Dimensions de L'ImageView cible ou du composant UI dans lequel l'image doit être chargée.
  • Taille de L'écran et densité de l'appareil courant.

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

à dites au décodeur de sous-échantillonner l'image, en chargeant une version plus petite dans la mémoire, mettez inSampleSize à true dans votre objet BitmapFactory.Options . Par exemple, une image de résolution 2048x1536 qui est décodée avec un inSampleSize de 4 produit une image bitmap d'environ 512x384. Le chargement en mémoire utilise 0,75 Mo plutôt que 12 Mo pour l'image complète (en supposant une configuration bitmap de ARGB_8888 ). Voici une méthode pour calculer la taille de l'échantillon qui est une puissance de deux, basé sur un cible de la largeur et de la hauteur:

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

Note : une puissance de deux valeurs est calculée parce que le décodeur utilise une valeur finale en arrondissant vers le bas à la puissance la plus proche de deux, conformément à la inSampleSize de la documentation.

pour utiliser cette méthode, décoder d'abord avec inJustDecodeBounds réglé sur true , passer les options et ensuite décoder à nouveau en utilisant la nouvelle valeur inSampleSize et inJustDecodeBounds mis à 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 permet de charger facilement un bitmap de taille arbitrairement grande dans un ImageView qui affiche une vignette de 100x100 pixels, comme indiqué dans le code exemple suivant:

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

vous pouvez suivre un processus similaire pour décoder des bitmaps d'autres sources, en remplaçant la méthode appropriée BitmapFactory.decode* si nécessaire.

572
répondu Sazid 2015-09-11 00:58:18

pour corriger l'erreur OutOfMemory, vous devriez faire quelque chose comme ceci:

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. Puis il trouve la meilleure valeur inSampleSize , il devrait être une puissance de 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;
}
865
répondu Fedor 2015-04-28 21:54:24

j'ai apporté une petite amélioration au code de Fedor. Il fait essentiellement la même chose, mais sans le (à mon avis) laid tout boucle et il résulte toujours en une puissance de deux. Félicitations à Fedor pour avoir fait la solution originale, j'étais coincé jusqu'à ce que je trouve le sien, et puis j'ai pu faire celui-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;
}
356
répondu Thomas Vervest 2014-03-20 06:18:29

je viens de iOS experience et j'ai été frustré de découvrir un problème avec quelque chose d'aussi fondamental que le chargement et la présentation d'une image. Après tout, tous ceux qui ont ce problème essaient d'Afficher des images de taille raisonnable. Quoi qu'il en soit, voici les deux changements qui ont corrigé mon problème (et rendu mon application très réactive).

1) chaque fois que vous faites BitmapFactory.decodeXYZ() , assurez-vous de passer dans un BitmapFactory.Options avec inPurgeable mis à true (et de préférence avec inInputShareable aussi mis à true ).

2) Ne jamais utiliser Bitmap.createBitmap(width, height, Config.ARGB_8888) . Je veux dire que JAMAIS! Je n'ai jamais eu cette chose de ne pas augmenter l'erreur de mémoire après quelques passes. Pas de montant de recycle() , System.gc() , peu importe ce qui a aidé. Il a toujours soulevé l'exception. La seule autre façon qui fonctionne réellement est d'avoir une image factice dans vos tirages (ou un autre Bitmap que vous avez décodé en utilisant l'étape 1 ci-dessus), rééchelonnez cela à ce que vous voulez, puis manipulez le Bitmap résultant (tel que le passer à un Toile pour plus de fun). Donc, ce que vous devriez utiliser à la place est: Bitmap.createScaledBitmap(srcBitmap, width, height, false) . Si, pour une raison quelconque, vous devez utiliser la méthode brute force create, alors au moins passer Config.ARGB_4444 .

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

222
répondu Ephraim 2016-06-02 12:03:47

c'est un bogue connu , ce n'est pas à cause de gros fichiers. Depuis Android Cache Les Tirables, il va sortir de mémoire après avoir utilisé quelques images. Mais j'ai trouvé un autre moyen pour ça, en sautant le système de cache par défaut d'android.

Solution : Déplacez les images dans le dossier "assets" 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);
}
85
répondu Anto Binish Kaspar 2014-09-15 10:05:32

j'avais ce même problème et je l'ai résolu en évitant le BitmapFactory.decodeStream ou decodeFile fonctions et utilisé à la place BitmapFactory.decodeFileDescriptor

decodeFileDescriptor ressemble à des méthodes natives différentes que le decodeStream/decodeFile.

de toute façon, ce qui a fonctionné était ceci (notez que j'ai ajouté quelques options comme certains avaient ci-dessus, mais ce n'est pas ce qui a fait la différence. Ce qui est critique, c'est l'appel au 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. Aussi ce que j'ai Lu est "que les Images (Bitmaps) ne sont pas allouées de manière Java standard mais via des appels natifs; les attributions sont faites en dehors du tas virtuel, mais sont compté contre elle! "

70
répondu Fraggle 2015-01-31 08:33:36

je pense que la meilleure façon d'éviter le OutOfMemoryError est de l'affronter et de le comprendre.

j'ai fait un app pour causer intentionnellement OutOfMemoryError , et surveiller l'utilisation de la mémoire.

après avoir fait beaucoup d'expériences avec cette application, j'ai eu les conclusions suivantes:

je vais parler des versions SDK avant Honey Comb.

  1. Bitmap est stocké dans tas natif, mais il des ordures collectées automatiquement, appel à recycler() est inutile.

  2. si {VM heap size} + {allocated native heap memory} >= {VM heap size limit for the device}, et que vous essayez de créer bitmap, OOM sera lancé.

    avis: La Taille du tas VM est comptée plutôt que la mémoire VM allouée.

  3. VM taille de Segment de mémoire ne sera jamais rétrécir après avoir grandi, même si l' la mémoire VM allouée est réduite.

  4. ainsi vous devez garder la mémoire VM de pointe aussi bas que possible pour garder la taille de tas de VM de devenir trop grand pour sauver la mémoire disponible pour Bitmaps.

  5. système D'appel manuel.gc () n'a pas de sens, le système l'appellera en premier avant d'essayer d'augmenter la taille du tas.

  6. la taille du tas natif ne se rétrécira jamais aussi, mais ce n'est pas j'ai compté pour OOM, donc pas besoin de s'en inquiéter.

alors, parlons de SDK à partir de Honey Comb.

  1. Bitmap est stocké dans VM tas, Natif de la mémoire n'est pas compté pour OOM.

  2. la condition pour OOM est beaucoup plus simple: {VM taille tas} >= {VM limite de taille tas pour l'appareil}.

  3. donc vous avez plus mémoire disponible pour créer bitmap avec la même limite de taille de tas, OOM est moins susceptible d'être lancé.

voici quelques-unes de mes observations sur la collecte des ordures et la fuite de mémoire.

Vous pouvez le voir vous-même dans l'Application. Si une activité a exécuté un AsyncTask qui était encore en cours d'exécution après que l'activité a été détruite, l'activité ne sera pas ramassée jusqu'à la fin de l'AsyncTask.

c'est parce que AsyncTask est une instance d'une classe interne anonyme, elle contient une référence de l'activité.

Appel 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 background.

les Callbacks sont des classes internes anonymes aussi, donc si une instance statique dans votre projet les retient et ne les libère pas, la mémoire fuit.

si vous avez programmé une tâche répétitive ou retardée, par exemple un Minuterie, et vous n'appelez pas annuler() et la purge() dans onPause(), mémoire de "fuites".

65
répondu coocood 2013-01-09 12:45:12

j'ai vu beaucoup de questions sur les exceptions OOM et la mise en cache dernièrement. Le developer guide a un très bon article à ce sujet, mais certains ont tendance à échouer sur la mise en œuvre d'une manière appropriée.

pour cette raison, j'ai écrit un exemple d'application qui démontre la mise en cache dans un environnement Android. Cette mise en œuvre n'a pas encore reçu D'OOM.

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

Requirements:

  • API Android 2.1 ou plus (Je ne pouvais tout simplement pas obtenir la mémoire disponible pour une application dans L'API 1.6 - qui est le seul morceau de code qui ne fonctionne pas dans L'API 1.6)
  • le soutien Android package

Screenshot

Dispose:

  • conserve le cache s'il y a un changement d'orientation , en utilisant un singleton
  • utiliser un huitième de la mémoire d'application assignée au cache (modifier si vous voulez)
  • Large bitmaps gets scaled (vous pouvez définir les pixels maximum que vous voulez autoriser)
  • contrôle qu'Il ya une connexion internet disponible avant télécharger les images
  • S'assure que vous instantiez seulement une tâche par rangée
  • Si vous êtes jetant le ListView , il n'est tout simplement pas télécharger les images entre

ne comprend pas:

  • cache Disque. Cela devrait être facile à mettre en œuvre de toute façon - il suffit de pointer vers une tâche différente qui saisit le bitmaps du disque

Exemple de code:

les images qui sont téléchargées sont des images (75x75) de Flickr. Cependant, mettez toutes les urls d'image que vous voulez être traitées, et l'application va réduire son échelle si elle dépasse le maximum. Dans cette application, les urls sont simplement dans un tableau String .

le LruCache a une bonne façon de traiter bitmaps. Toutefois, dans ce application j'ai mis une instance d'un LruCache dans une autre classe de cache que j'ai créée pour rendre l'application plus faisable.

Cache.le matériel critique de 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 pas avoir besoin d'éditer quoi que ce soit dans le Cache.java file à moins que vous ne vouliez implémenter la mise en cache disque.

activité principale.java l'essentiel des trucs:

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 une 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 lancera pas une autre. Par conséquent, il est fort probable que nous n'excédions pas la limite de la file d'attente de la réserve AsyncTask .

télécharger:

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


derniers mots:

j'ai testé cela depuis quelques semaines maintenant, je n'ai pas eu une seule exception OOM encore. J'ai testé ceci sur l'émulateur, sur mon Nexus One et sur mon Nexus S. j'ai testé des urls d'image qui contiennent des images de qualité HD. Le seul goulot d'étranglement, c'est qu'il faut plus de temps pour télécharger.

il n'y a qu'un seul scénario possible où je peux imaginer que L'OOM apparaîtra, et c'est-à-dire si nous téléchargeons beaucoup, de très grandes images, et avant qu'elles soient mises à l'échelle et mises en cache, prendra simultanément plus de mémoire et causer une OOM. Mais ce n'est même pas une situation idéale de toute façon et il est très probable qu'il ne sera pas possible de la résoudre d'une manière plus réaliste.

signaler des erreurs dans les commentaires! :- )

59
répondu Wroclai 2014-08-01 20:25:26

je n'ai suivantes pour prendre l'image et la redimensionner à la volée. Espérons que cette aide

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
38
répondu Chrispix 2015-04-24 20:31:37

malheureusement si aucun des travaux ci-dessus, puis Ajouter cela à votre manifeste fichier. À l'intérieur de application tag

 <application
         android:largeHeap="true"
32
répondu Himanshu Mori 2016-01-22 12:18:30

il semble qu'il s'agisse d'un problème de très longue durée, avec beaucoup d'explications différentes. J'ai suivi les conseils des deux réponses les plus courantes présentées ici, mais ni l'une ni l'autre n'a résolu mes problèmes de la VM affirmant qu'elle ne pouvait pas se permettre les octets pour effectuer la décodage partie du processus. Après quelques recherches, j'ai appris que le vrai problème ici est le processus de décodage qui éloigne du tas natif .

Voir ici: BitmapFactory OOM me rend fou

qui m'amène à un autre sujet de discussion où j'ai trouvé un couple d'autres solutions à ce problème. L'un est d'appeler System.gc(); manuellement après que votre image est affichée. Mais cela rend en fait votre application utilise plus de mémoire, dans un effort pour réduire le tas natif. La meilleure solution à partir de la version 2.0 (Donut) est d'utiliser L'option BitmapFactory "inpurable". Alors j'ai simplement ajouté o2.inPurgeable=true; juste après o2.inSampleSize=scale; .

plus sur ce sujet ici: est la limite de la mémoire tas seulement 6M?

maintenant, ayant dit tout cela, je suis un dunce complet avec Java et Android aussi. Donc, si vous pensez que c'est une terrible façon de résoudre ce problème, vous avez probablement raison. ;-) Mais cela a fonctionné à merveille pour moi, et j'ai trouvé qu'il est impossible d'exécuter la machine virtuelle de segment de mémoire cache maintenant. Le seul inconvénient que je puisse trouver est que vous êtes en train de détruire votre image. Ce qui veut dire que si vous retournez à cette image, vous la redessinez à chaque fois. Dans le cas de la façon dont ma demande fonctionne, ce n'est pas vraiment un problème. Votre kilométrage peut varier.

31
répondu RayHaque 2017-05-23 10:31:38

utilisez ce bitmap.recycle(); cela aide sans aucun problème de qualité d'image.

29
répondu Arsalan 2015-04-21 19:29:20

j'ai une solution beaucoup plus efficace qui n'a pas besoin d'échelle d'aucune sorte. Il suffit de décoder votre bitmap une seule fois, puis de le mettre en cache dans une carte par rapport à son nom. Ensuite, récupérez simplement le bitmap par rapport au nom et mettez-le dans L'ImageView. Il n'y a rien de plus qui doit être fait.

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

pour vous aider à mieux apprécier cela, imaginez que vous avez gardé votre image dans le dossier à dessiner. Vous obtenez juste 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'appellerez. Donc, en substance, il est mis en cache.

maintenant puisque votre image est dans un fichier quelque part (ou peut-être même venir d'un serveur externe), il est de votre responsabilité de mettre en cache l'instance bitmap décodée pour la réutiliser là où elle est nécessaire.

Espérons que cette aide.

25
répondu Parth Mehta 2011-03-02 18:17:45

j'ai résolu la même question de la façon 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);
25
répondu Prerna 2015-04-24 20:34:42

il y a deux questions ici....

  • Image de la mémoire n'est pas dans la machine virtuelle tas, mais plutôt dans le tas natif - voir BitmapFactory OOM me rend fou
  • collecte des ordures pour le tas natif est plus fainéant que le tas VM - donc vous devez être assez agressif à faire bitmap.recycle et bitmap = null chaque fois que vous passez par onPause ou onDestroy d'une activité
24
répondu Torid 2017-05-23 12:26:34

ça a marché 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());
    }
}
24
répondu Luke Taylor 2015-04-24 20:32:58

bonnes réponses ici, mais je voulais une classe pleinement utilisable pour résoudre ce problème.. j'ai donc fait un.

Voici mon BitmapHelper class qui est OutOfMemoryError preuve: -)

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

}
19
répondu Pascal 2013-02-13 06:43:01

aucune des réponses ci-dessus n'a fonctionné pour moi, mais j'ai trouvé une solution affreusement laide qui a résolu le problème. J'ai ajouté une très petite image 1x1 pixel à mon projet en tant que ressource, et l'ai chargée dans mon ImageView avant d'appeler dans la collecte des ordures. Je pense que C'est peut-être parce que ImageView ne publiait pas le Bitmap, donc le GC ne l'a jamais récupéré. C'est moche, mais il semble fonctionner pour l'instant.

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.
18
répondu Mike 2011-12-07 12:32:34

ça me va.

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 c'est sur C# monodroid. vous pouvez facilement changer le chemin de l'image. ce qui importe ici, ce sont les options à définir.

18
répondu Dobermaxx99 2012-10-02 10:33:28

cela semble être l'endroit approprié pour partager ma classe utilitaire pour le chargement et le traitement des images avec la communauté, vous êtes invités à l'utiliser et à la 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;
    }
}
14
répondu Emil Davtyan 2012-11-02 23:41:44

dans une de mes applications, je dois prendre une photo de Camera/Gallery . Si l'utilisateur clique sur L'image de la caméra (peut être 2MP, 5MP ou 8MP), la taille de l'image varie de kB s à MB s. Si la taille de l'image est inférieure(ou jusqu'à 1-2MB) au-dessus du code de travail bien, mais si j'ai une image de taille supérieure à 4MB ou 5MB alors OOM vient dans la section: (

alors j'ai travaillé pour résoudre ce problème et finalement j'ai fait l'amélioration ci-dessous de Fedor(tout le crédit à Fedor pour faire une telle solution) code :)

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 confrontés au même problème!

pour plus s'il vous plaît se référer à ce

14
répondu Rupesh Yadav 2013-02-06 14:53:59

j'ai rencontré ce numéro il y a quelques minutes. Je l'ai résolu en gérant mieux mon adaptateur listview. J'ai pensé que c'était un problème avec les centaines d'images 50x50px que j'utilisais, il s'avère que j'essayais de gonfler ma vue personnalisée chaque fois que la rangée était montrée. Simplement en testant pour voir si la rangée avait été gonflée j'ai éliminé cette erreur, et j'utilise des centaines de bitmaps. Il s'agit en fait d'un Spinner, mais l'adaptateur de base fonctionne tout de même pour un ListView. Ce la fixation simple a aussi grandement 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);
    }
...
13
répondu BajaBob 2012-02-24 19:08:16

ce problème se produit uniquement dans les émulateurs Android. J'ai également fait face à ce problème dans un émulateur, mais quand j'ai vérifié dans un appareil puis il a fonctionné très bien.

alors veuillez enregistrer un appareil. Il peut être exécuté dans le dispositif.

13
répondu hackjutsu 2017-06-10 00:53:29

j'ai passé la journée entière à tester ces solutions et la seule chose qui a fonctionné pour moi est les approches ci-dessus pour obtenir l'image et appeler manuellement le GC, je sais que ce n'est pas censé être nécessaire, mais c'est la seule chose qui a fonctionné quand je mets mon application sous une lourde charge essai commutation entre les activités. Mon application dispose d'une liste de vignettes dans une listview (permet de dire que l'activité A) et lorsque vous cliquez sur une de ces images qu'il vous faut pour une autre activité (permet de dire l'activité B) qui montre une image principale pour cet article. Quand je changeais de côté et d'autre entre les deux activités, je finissais par obtenir l'erreur OOM et l'application se refermait.

quand j'étais à mi-chemin dans la liste, il s'écrasait.

Maintenant, lorsque je mets en œuvre ce qui suit dans l'activité B, je peux passer en revue tout le listview sans problème et continuer et continuer...et ses beaucoup rapide.

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
12
répondu Jesse 2012-03-27 19:33:09

mes 2 cents: j'ai résolu mes erreurs OOM avec bitmaps par:

a) mise à l'échelle de mes images par un facteur de 2

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

11
répondu matsoftware 2013-07-14 09:08:14

utilisez ce code pour chaque image dans select from SdCard ou drewable 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 instend de ImageData_Path.get (img_pos).getPath () .

11
répondu Gaurav Pansheriya 2015-04-24 20:36:02

un tel OutofMemoryException ne peut être totalement résolu en appelant le System.gc() et ainsi de suite .

par référence au cycle de vie de l'activité 151980920 "

les États D'activité sont déterminés par L'OS lui-même en fonction de l'utilisation de la mémoire pour chaque processus et de la priorité de chaque processus.

vous pouvez prendre en compte la taille et la résolution de chacune des images bitmap utilisées. Je recommander de réduire la taille ,rééchantillonner à une plus faible résolution , se référer à la conception des galeries (une petite image PNG , et une image originale.)

10
répondu Raju Gujarati 2016-09-27 09:32:45

généralement android périphérique taille tas est seulement 16MB (varie de l'appareil/OS voir post tailles tas ), si vous chargez les images et il traverse la taille de 16MB , il va jeter de l'exception mémoire, au lieu d'utiliser le Bitmap pour , le chargement des images à partir de la carte SD ou des ressources ou même à partir du réseau essayer d'utiliser getImageUri , chargement bitmap nécessitent plus de mémoire , ou vous pouvez définir bitmap à null si votre travail fait avec ce bitmap.

10
répondu Rohit Sharma 2018-03-09 11:42:42

ce code aidera à charger une grande image bitmap à partir de

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;
    }
}
10
répondu vineet 2018-09-10 09:26:33

toutes les solutions nécessitent ici de définir une IMAGE_MAX_SIZE. Cela limite les appareils avec du matériel plus puissant et si la taille de l'image est trop basse, elle semble laide sur L'écran HD.

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

L'essentiel, c'est de calculer la quantité maximale de mémoire allouée pour l'application sur un appareil particulier, puis régler l'échelle pour être le plus bas 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;
}

j'ai réglé la mémoire maximale utilisée par ce bitmap à 25% de la mémoire maximale allouée, vous devrez peut-être l'ajuster à vos besoins et vous assurer que ce bitmap est nettoyé et ne reste pas en mémoire lorsque vous aurez fini de l'utiliser. En général, j'utilise ce code pour effectuer la rotation d'image (source et destination bitmap) donc mon application doit charger 2 bitmaps dans mémoire en même temps, et 25% me donne un bon tampon sans manquer de mémoire lors de l'exécution de rotation d'image.

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

9
répondu Bruce 2013-08-31 03:04:47