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)
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);
}
Utilisation
((BitmapDrawable)imageView.getDrawable()).getBitmap().recycle();
Avant de changer de nouvelle image !!
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
.
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
.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)
.
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.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..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);
}
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.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;
}
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);
}
}
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
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.
Glide
pour gérer toute la mise en cache.views
et définir tout ImageView
bitmap/drawable sur null
et effacer tous les gestionnaires d'événements et écouteurs.activity
ou fragment
sur null
.onDestroy
et vous devriez être prêt à partir.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.