web-dev-qa-db-fra.com

Erreur de mémoire insuffisante ImageView

Je suis nouveau dans la programmation Android et j'ai une erreur qui dit que mon application est à court de mémoire, par exemple j'ai copié à partir d'un livre et cela fonctionne avec une résolution de petites images, mais quand je Ajout de quelques images avec une résolution plus élevée Une erreur de mémoire insuffisante apparaît, peut-être que je fais quelque chose de mal ou que je ne sais tout simplement pas tout ce que je dois encore travailler avec des images, si quelqu'un sait ce que je dois changer pour que cette erreur n'apparaisse pas encore une fois, merci de l'aide.

Le code source:

public class ImageViewsActivity extends Activity {
//the images to display
Integer[] imageIDs={
        R.drawable.pic1,
        R.drawable.pic2,
        R.drawable.pic3,
        R.drawable.pic4,
        R.drawable.pic5
};  
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    final ImageView iv=(ImageView) findViewById(R.id.image1);

    Gallery gallery=(Gallery) findViewById(R.id.gallery);
    gallery.setAdapter(new ImageAdapter(this));
  gallery.setOnItemClickListener(new OnItemClickListener(){
        public void onItemClick(AdapterView<?> parent, View v, int position, long id){
            Toast.makeText(getBaseContext(), "pic"+(position+1)+" selected", Toast.LENGTH_SHORT).show();

            //display the image selected
            try{iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
              iv.setImageResource(imageIDs[position]);}catch(OutOfMemoryError e){
                     iv.setImageBitmap(null);
                }
        }
    });


}

public class ImageAdapter extends BaseAdapter{
    private Context context;
    private int itemBackground;

    public ImageAdapter(Context c){
        context=c;
        //setting the style
        TypedArray a = obtainStyledAttributes(R.styleable.Gallery1);
        itemBackground = a.getResourceId(R.styleable.Gallery1_Android_galleryItemBackground, 0);
        a.recycle();
    }

    //returns the number of images
    public int getCount() {
        // TODO Auto-generated method stub
        return imageIDs.length;
    }

    //returns the ID of an item
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    //returns the ID of an item
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    //returns an ImageView view
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        ImageView iv= new ImageView(context);
        iv.setImageResource(imageIDs[position]);
        iv.setScaleType(ImageView.ScaleType.FIT_XY);
        iv.setLayoutParams(new Gallery.LayoutParams(150,120));
        iv.setBackgroundResource(itemBackground);

        return iv;
    }
}}

ERREUR ICI:

04-18 10:38:31.661: D/dalvikvm(10152): Debugger has detached; object registry had 442 entries
04-18 10:38:31.661: D/AndroidRuntime(10152): Shutting down VM
04-18 10:38:31.661: W/dalvikvm(10152): threadid=1: thread exiting with uncaught exception (group=0x4001d820)
04-18 10:38:31.691: E/AndroidRuntime(10152): FATAL EXCEPTION: main
04-18 10:38:31.691: E/AndroidRuntime(10152): Java.lang.OutOfMemoryError: bitmap size exceeds VM budget
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.graphics.Bitmap.nativeCreate(Native Method)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.graphics.Bitmap.createBitmap(Bitmap.Java:499)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.graphics.Bitmap.createBitmap(Bitmap.Java:466)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.graphics.Bitmap.createScaledBitmap(Bitmap.Java:371)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.graphics.BitmapFactory.finishDecode(BitmapFactory.Java:539)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.graphics.BitmapFactory.decodeStream(BitmapFactory.Java:508)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.Java:365)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.graphics.drawable.Drawable.createFromResourceStream(Drawable.Java:728)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.content.res.Resources.loadDrawable(Resources.Java:1740)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.content.res.Resources.getDrawable(Resources.Java:612)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.widget.ImageView.resolveUri(ImageView.Java:520)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.widget.ImageView.setImageResource(ImageView.Java:305)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at image.view.GalleryView$ImageAdapter.getView(GalleryView.Java:95)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.widget.Gallery.makeAndAddView(Gallery.Java:776)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.widget.Gallery.fillToGalleryLeft(Gallery.Java:695)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.widget.Gallery.trackMotionScroll(Gallery.Java:406)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.widget.Gallery$FlingRunnable.run(Gallery.Java:1397)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.os.Handler.handleCallback(Handler.Java:618)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.os.Handler.dispatchMessage(Handler.Java:123)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.os.Looper.loop(Looper.Java:154)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Android.app.ActivityThread.main(ActivityThread.Java:4668)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Java.lang.reflect.Method.invokeNative(Native Method)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at Java.lang.reflect.Method.invoke(Method.Java:552)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:917)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:674)
04-18 10:38:31.691: E/AndroidRuntime(10152):    at dalvik.system.NativeStart.main(Native Method)
31
Max

Pour ajouter la réponse de Ken, qui est un solide morceau de code, je pensais que je le renverserais après l'avoir configuré:

    if(imageView != null) {
        ((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();
    }
    imageView = (ImageView) view.findViewById(R.id.imageView);
    imageView.setImageResource(resID);

REMARQUE: cela ne fonctionnera pas si vous essayez de permuter une image que vous avez déjà recyclée. Vous obtiendrez quelque chose comme ça dans LOGCAT

Canvas: essayer d'utiliser une bitmap recyclée

Donc, ce que je fais maintenant si je n'ai pas à charger un tas d'images différentes de manière asynchrone, je mets simplement cela dans onDestroy lorsque je traite des fragments et de grandes images d'arrière-plan:

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

    imageView.setImageDrawable(null);
}
35
whyoz

Utilisation

((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();

Avant de changer de nouvelle image !!

28
Ken

Pour ceux qui utilisent la bibliothèque de chargement d'images Glide , qui rencontrent toujours ces problèmes OutOfMemory Exception, Vous pouvez faire beaucoup de choses pour que Glide utilise moins de mémoire et espérons résoudre votre problème. En voici quelques uns:

  • N'utilisez pas Android:scaleType="fitXY" À l'intérieur de votre ImageView. Donc, si vous êtes ImageView ressemble à ceci:

    <ImageView Android:id="@Android:id/icon"
           Android:layout_width="@dimen/width"
           Android:layout_height="@dimen/height"
           Android:adjustViewBounds="true" 
           Android:scaleType="fitXY" 
          <!-- DON'T USE "fitXY"! -->
    />
    

    Modifiez le ImageView pour utiliser un autre Android:scaleType, De préférence: fitCenter ou centerCrop.

  • N'utilisez pas wrap_content Dans votre ImageView, utilisez plutôt match_parent Ou spécifiez width/height en utilisant explicitement une taille dans dp. Si vous vraiment insistez pour utiliser wrap_content Dans votre ImageView, définissez au moins un Android:maxHeight/Android:maxWidth.
  • Désactivez les animations avec: dontAnimate() sur votre demande Glide.with()....
  • Si vous chargez beaucoup d'images potentiellement volumineuses (comme vous le feriez dans une liste/grille), spécifiez une charge thumbnail(float sizeMultiplier) dans votre demande. Ex:

    Glide.with(context)
       .load(imageUri)
       .thumbnail(0.5f)
       .dontAnimate()
       .into(iconImageView);
    
  • Réduisez temporairement l'empreinte mémoire de Glide pendant certaines phases de votre application en utilisant: Glide.get(context).setMemoryCategory(MemoryCategory.LOW).

  • Ne mettez en cache qu'en mémoire si vous en avez besoin, vous pouvez le désactiver avec: skipMemoryCache(true) sur votre Glide.with()... requête. Cela mettra toujours en cache les images sur le disque, ce que vous voudrez probablement car vous renoncez au cache en mémoire.
  • Si vous chargez un Drawable à partir de vos ressources locales, assurez-vous que l'image que vous essayez de charger N'EST PAS SUPER ÉNORME. De nombreux outils de compression d'images sont disponibles en ligne. Ces outils réduiront la taille de vos images tout en conservant leur qualité d'apparence.
  • Si vous chargez à partir de ressources locales, utilisez .diskCacheStrategy(DiskCacheStrategy.NONE).
  • Accrochez-vous à la fonction de rappel onTrimMemory(int level) que Android fournit pour découper le cache Glide si nécessaire. Ex.

    @Override
    public void onTrimMemory(int level)
    {
        super.onTrimMemory(level);
        Glide.get(this).trimMemory(level);
    }
    
  • Si vous affichez des images dans un RecyclerView, vous pouvez explicitement effacer Glide lorsque les vues sont recyclées, comme ceci:

    @Override
    public void onViewRecycled(MyAdapter.MyViewHolder holder)
    {
        super.onViewRecycled(holder);
        Glide.clear(holder.imageView);
    }
    
  • Si cela se produit encore, même après avoir "tout essayé", le problème peut être votre application ( GASP!), Et Glide est juste la seule chose qui le pousse dans la zone OutOfMemory Exception ... Assurez-vous donc que vous n'avez aucune fuite de mémoire dans votre application. Android Studio Fournit des outils pour identifier les problèmes de consommation de mémoire dans votre application.
  • Enfin, consultez la page de problème sur le GitHub de Glide , pour des problèmes similaires qui peuvent donner un aperçu de la résolution de vos problèmes. Le repo est très bien géré et ils sont très utiles.
16
Sakiboy

Les images sont de toutes formes et tailles. Dans de nombreux cas, ils sont plus grands que requis pour une interface utilisateur d'application (UI) typique. Par exemple, l'application Galerie système affiche des photos prises à l'aide de l'appareil photo de votre appareil Android Android) qui sont généralement d'une résolution beaucoup plus élevée que la densité d'écran de votre appareil.

Étant donné que vous travaillez avec une mémoire limitée, vous ne souhaitez idéalement charger qu'une version de résolution inférieure en mémoire. La version de résolution inférieure doit correspondre à la taille du composant d'interface utilisateur qui l'affiche. Une image avec une résolution plus élevée n'offre aucun avantage visible, mais occupe toujours une mémoire précieuse et entraîne des frais supplémentaires de performance en raison d'une mise à l'échelle supplémentaire à la volée.

Source: chargement efficace de grandes images bitmap

Sur la base des informations ci-dessus, je vous recommanderais au lieu de définir l'image comme ceci:

setImageResource(resId);

pour le définir comme ceci:

setScaledImage(yourImageView, resId);

et copiez et collez les méthodes ci-dessous:

    private void setScaledImage(ImageView imageView, final int resId) {
        final ImageView iv = imageView;
        ViewTreeObserver viewTreeObserver = iv.getViewTreeObserver();
        viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            public boolean onPreDraw() {
                iv.getViewTreeObserver().removeOnPreDrawListener(this);
                int imageViewHeight = iv.getMeasuredHeight();
                int imageViewWidth = iv.getMeasuredWidth();
                iv.setImageBitmap(
                        decodeSampledBitmapFromResource(getResources(),
                                resId, imageViewWidth, imageViewHeight));
                return true;
            }
        });
    }

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

    private 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;
    }
6
Ivo Stoyanov

Google a la bonne (parfaite) réponse:

https://developer.Android.com/training/displaying-bitmaps/load-bitmap.html

Un exemple comment je l'utilise en fragments:

private ImageView mImageView;
private View view;
private int viewWidth;
private int viewHeight;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    view = inflater.inflate(R.layout.fragment_episode_list, container, false);
    mImageView = (ImageView) view.findViewById(R.id.ImageView);

    ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
    if (viewTreeObserver.isAlive()) {
        viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                viewWidth = view.getMeasuredWidth();
                viewHeight = view.getMeasuredHeight();
                mImageView.setImageBitmap(Methods.decodeSampledBitmapFromResource(getResources(),
                            R.drawable.YourImageName, viewWidth, viewHeight));
            }
        });
    }

    return view;
}

Je mets ces méthodes Google dans ma classe "Méthodes" (pour toute autre méthode utile):

public class Methods {

    ...

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

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

}
2
user25

Vous pouvez le laisser aux bibliothèques tierces telles que Glide

//                imageView.setImageResource(imageId);
                Glide.with(this)  // Activity or Fragment
                        .load(imageId)
                        .into(imageView);

Voici comment l'ajouter à votre build.gradle:

compile group: 'com.github.bumptech.glide', name: 'glide', version: '3.7.0'

Picasso de Square le fait aussi Picasso charge les ressources dessinables depuis leur URI

2
JohnnyLambada

Notes supplémentaires à la réponse @Sakiboy. Bien que je sois probablement trop en retard pour ma réponse, mais voici ma solution, j'ai trouvé que cela fonctionne sans avoir besoin de faire beaucoup de changements de code.

  • utilisez Glide pour gérer toute la mise en cache.
  • Pour effacer plus de mémoire, vous devez supprimer manuellement tous les views et définir tout ImageView bitmap/drawable sur null et effacer tous les gestionnaires d'événements et écouteurs.
  • Définissez toutes les variables que vous avez dans votre activity ou fragment sur null.
  • Vous devez mettre votre logique dans onDestroy et vous devriez être prêt à partir.
  • L'étape facultative consiste à ajouter System.gc() à la fin de votre code.

Après avoir effacé toutes les choses que j'ai mentionnées plus tôt. Vous remarquerez que la mémoire diminue à chaque fois qu'un fragment/activité est détruit.

0
SolidSnake