J'ai une vue en liste avec quelques boutons d'image sur chaque ligne. Lorsque vous cliquez sur la ligne de la liste, une nouvelle activité est lancée. J'ai dû créer mes propres onglets en raison d'un problème de disposition de la caméra. L'activité lancée pour le résultat est une carte. Si je clique sur mon bouton pour lancer l'aperçu de l'image (charger une image de la carte SD), l'application retourne de l'activité à l'activité listview
au gestionnaire de résultats pour relancer ma nouvelle activité, qui n'est rien d'autre qu'un widget image.
L'aperçu de l'image dans la vue en liste se fait avec le curseur et la touche ListAdapter
. Cela rend les choses assez simples, mais je ne sais pas comment mettre une image redimensionnée (c'est-à-dire une taille de bit plus petite que les pixels comme le src
du bouton d'image à la volée. Je viens donc de redimensionner l'image caméra de téléphone.
Le problème est que je reçois une erreur d'insuffisance de mémoire lorsqu'il essaie de revenir en arrière et de relancer la 2e activité.
Ce serait préférable car je dois également apporter quelques modifications aux propriétés des widgets/éléments de chaque ligne, car je ne parviens pas à sélectionner une ligne avec l'écran tactile en raison du problème de focus. (Je peux utiliser la bille roulante.)
Dès que j'ai désactivé l'image dans la liste, tout a bien fonctionné.
FYI: Voici comment je le faisais:
String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
Où 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
La classe Android Training , " Affichage efficace des bitmaps ", offre des informations précieuses pour comprendre et traiter l’exception Java.lang.OutOfMemoryError: bitmap size exceeds VM budget
lors du chargement de bitmaps.
La classe BitmapFactory
fournit plusieurs méthodes de décodage (decodeByteArray()
, decodeFile()
, decodeResource()
, etc.) pour créer un Bitmap
à partir de diverses sources. Choisissez la méthode de décodage la plus appropriée en fonction de la source de données de votre image. Ces méthodes tentent d'allouer de la mémoire pour le bitmap construit et peuvent par conséquent générer facilement une exception OutOfMemory
. Chaque type de méthode de décodage comporte des signatures supplémentaires vous permettant de spécifier des options de décodage via la classe BitmapFactory.Options
. Définir la propriété inJustDecodeBounds
sur true
lors du décodage évite l'allocation de mémoire, renvoyer null
pour l'objet bitmap mais en définissant outWidth
, outHeight
et outMimeType
. Cette technique vous permet de lire les dimensions et le type des données d'image avant la construction (et l'allocation de mémoire) du bitmap.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;
Pour éviter les exceptions Java.lang.OutOfMemory
, vérifiez les dimensions d'un bitmap avant de le décoder, à moins que vous n'ayez absolument confiance en la source pour vous fournir des données d'image de taille prévisible qui s'adaptent facilement à la mémoire disponible.
Maintenant que les dimensions de l'image sont connues, elles peuvent être utilisées pour décider si l'image complète doit être chargée en mémoire ou si une version sous-échantillonnée doit être chargée à la place. Voici quelques facteurs à prendre en compte:
Par exemple, il ne vaut pas la peine de charger en mémoire une image de 1024 x 768 pixels si elle est éventuellement affichée dans une vignette de 128 x 96 pixels dans un ImageView
.
Pour indiquer au décodeur de sous-échantillonner l'image, en chargeant une version plus petite en mémoire, définissez inSampleSize
sur true
dans votre objet BitmapFactory.Options
. Par exemple, une image de résolution 2048x1536 qui est décodée avec un inSampleSize
de 4 génère une image bitmap d’environ 512x384. Le chargement en mémoire utilise 0,75 Mo au lieu de 12 Mo pour l'image complète (en supposant une configuration bitmap de ARGB_8888
). Voici une méthode pour calculer une valeur de taille d’échantillon égale à une puissance de deux en fonction d’une largeur et d’une hauteur cibles:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
Remarque : une puissance de deux valeurs est calculée car le décodeur utilise une valeur finale en arrondissant à la puissance de deux la plus proche, conformément à la documentation de
inSampleSize
.
Pour utiliser cette méthode, commencez par décoder avec inJustDecodeBounds
défini sur true
, transmettez les options, puis décodez à nouveau à l'aide de la nouvelle valeur inSampleSize
et de inJustDecodeBounds
défini sur false
:
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
Cette méthode facilite le chargement d'un bitmap de taille arbitrairement grande dans un ImageView
qui affiche une vignette de 100 x 100 pixels, comme indiqué dans l'exemple de code suivant:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Vous pouvez suivre un processus similaire pour décoder des bitmaps provenant d’autres sources, en remplaçant la méthode appropriée BitmapFactory.decode*
au besoin.
Pour corriger l'erreur OutOfMemory, procédez comme suit:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);
Cette option inSampleSize
réduit la consommation de mémoire.
Voici une méthode complète. Tout d'abord, il lit la taille de l'image sans décoder le contenu lui-même. Ensuite, il trouve la meilleure valeur inSampleSize
, ce 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;
}
J'ai apporté une petite amélioration au code de Fedor. Il fait fondamentalement la même chose, mais sans (à mon avis) laide tout en boucle et il en résulte toujours une puissance de deux. Félicitations à Fedor pour avoir créé la solution originale, je suis resté bloqué jusqu'à ce que je trouve la sienne, puis j'ai pu faire celle-ci :)
private Bitmap decodeFile(File f){
Bitmap b = null;
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
BitmapFactory.decodeStream(fis, null, o);
fis.close();
int scale = 1;
if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
}
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
b = BitmapFactory.decodeStream(fis, null, o2);
fis.close();
return b;
}
Je viens de l'expérience iOS et j'étais frustré de découvrir un problème avec quelque chose d'aussi fondamental que le chargement et l'affichage d'une image. Après tout, tout le monde qui a ce problème essaie d’afficher des images de taille raisonnable. Quoi qu'il en soit, voici les deux modifications qui ont résolu mon problème (et ont rendu mon application très réactive).
1) Chaque fois que vous faites BitmapFactory.decodeXYZ()
, assurez-vous de passer un BitmapFactory.Options
avec inPurgeable
réglé sur true
(et de préférence avec inInputShareable
également réglé sur true
).
2) N'utilisez JAMAIS Bitmap.createBitmap(width, height, Config.ARGB_8888)
. Je veux dire JAMAIS! Je n'ai jamais eu cette chose pas soulevé d'erreur de mémoire après quelques passes. Aucune quantité de recycle()
, System.gc()
, peu importe. Il a toujours soulevé l'exception. Une autre façon de fonctionner consiste à insérer une image factice dans votre dessin (ou un autre bitmap que vous avez décodé à l’étape 1 ci-dessus), à redimensionner celle-ci, puis à manipuler le bitmap obtenu (par exemple, en le transmettant à un canevas). pour plus de plaisir). Donc, ce que vous devriez utiliser à la place est: Bitmap.createScaledBitmap(srcBitmap, width, height, false)
. Si pour quelque raison que ce soit, vous DEVEZ utiliser la méthode de création de force brute, passez au moins Config.ARGB_4444
.
Ceci est presque garanti pour vous faire économiser des heures sinon des jours. Tout ce qui parle de redimensionnement de l'image, etc. ne fonctionne pas vraiment (à moins que vous ne considériez une mauvaise taille ou une image dégradée comme solution).
C'est un bug conn , ce n'est pas à cause de gros fichiers. Étant donné que Android met en cache les fichiers Drawables, la mémoire est saturée après quelques images. Mais j’ai trouvé une autre solution, en sautant le système de cache par défaut 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);
}
J'avais ce même problème et je l'ai résolu en évitant les fonctions BitmapFactory.decodeStream ou decodeFile et en utilisant plutôt BitmapFactory.decodeFileDescriptor
decodeFileDescriptor
semble appeler des méthodes natives différentes de celles de decodeStream/decodeFile.
Quoi qu’il en soit, c’est ce qui a bien fonctionné (remarquez que j’ai ajouté certaines options, mais ce n’est pas ce qui a fait la différence. Ce qui est essentiel, c’est l’appel à BitmapFactory.decodeFileDescriptor au lieu de decodeStream ou decodeFile):
private void showImage(String path) {
Log.i("showImage","loading:"+path);
BitmapFactory.Options bfOptions=new BitmapFactory.Options();
bfOptions.inDither=false; //Disable Dithering mode
bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
bfOptions.inTempStorage=new byte[32 * 1024];
File file=new File(path);
FileInputStream fs=null;
try {
fs = new FileInputStream(file);
} catch (FileNotFoundException e) {
//TODO do something intelligent
e.printStackTrace();
}
try {
if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
} catch (IOException e) {
//TODO do something intelligent
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: Java.lang.OutOfMemoryError: bitmap size exceeds VM budget
im.setImageBitmap(bm);
//bm.recycle();
bm=null;
}
Je pense qu'il y a un problème avec la fonction native utilisée dans decodeStream/decodeFile. J'ai confirmé qu'une méthode native différente est appelée lors de l'utilisation de decodeFileDescriptor. De plus, ce que j’ai lu, c’est "que les images (bitmaps) ne sont pas allouées de manière standard Java mais par des appels natifs; les allocations sont faites en dehors du tas virtuel, mais sont a compté contre! "
Je pense que la meilleure façon d’éviter la OutOfMemoryError
est de l’affronter et de la 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 appli, j'ai les conclusions suivantes:
Je vais parler des versions du SDK avant Honey Comb.
Les images bitmap sont stockées dans le tas natif, mais les ordures sont automatiquement collectées. Il est inutile d'appeler recycle ().
Si {taille de segment de machine virtuelle} + {mémoire de segment de mémoire allouée}>> = {limite de taille de segment de mémoire virtuelle pour le périphérique}, et que vous essayez de créer un bitmap, vous créez une MOO.
AVIS: VM HEAP SIZE est compté plutôt que VM MÉMOIRE ALLOCÉE.
La taille de la mémoire virtuelle ne sera jamais réduite après la croissance, même si la mémoire allouée VM est réduite.
Il faut donc que la mémoire VM maximale ne dépasse pas le maximum possible pour que VM La taille de segment de mémoire ne devienne trop volumineuse pour économiser la mémoire disponible pour les bitmaps.
Appeler manuellement System.gc () n'a pas de sens, le système l'appellera d'abord avant d'essayer d'augmenter la taille du segment de mémoire.
La taille de segment de mémoire native ne diminuera jamais non plus, mais elle ne compte pas pour le MOO, vous n'avez donc pas à vous en soucier.
Ensuite, parlons du SDK Starts from Honey Comb.
Les images bitmap sont stockées dans le segment VM, la mémoire native n'est pas comptée pour le MOO.
La condition pour le MOO est beaucoup plus simple: {Taille de segment de mémoire virtuelle}>> = {Limite de taille de segment de mémoire virtuelle pour le périphérique}.
Par conséquent, vous disposez de plus de mémoire pour créer des images bitmap avec la même limite de taille de segment de mémoire, mais le MOO est moins susceptible d'être émis.
Voici certaines de mes observations sur le ramassage des ordures ménagères et la fuite de mémoire.
Vous pouvez le voir vous-même dans l'application. Si une activité a exécuté une tâche asynchrone qui était toujours en cours d'exécution après la destruction de l'activité, l'activité ne sera pas récupérée avant la fin de la tâche asynchrone.
En effet, AsyncTask est une instance d'une classe interne anonyme, elle contient une référence de Activity.
L'appel de AsyncTask.cancel (true) n'arrêtera pas l'exécution si la tâche est bloquée dans une opération IO dans un thread en arrière-plan.
Les rappels sont également des classes internes anonymes. Par conséquent, si une instance statique de votre projet les conserve et ne les libère pas, de la mémoire serait perdue.
Si vous avez planifié une tâche répétitive ou retardée, par exemple un minuteur, et que vous n'appelez pas cancel () et purge () dans onPause (), de la mémoire serait perdue.
J'ai vu beaucoup de questions sur les exceptions de MOO et la mise en cache récemment. Le guide du développeur contient un très bon article à ce sujet, mais certains ont tendance à échouer à le mettre en œuvre de manière appropriée.
À cause de cela, j'ai écrit un exemple d'application illustrant la mise en cache dans un environnement Android. Cette implémentation n'a pas encore reçu de MOO.
Regardez à la fin de cette réponse pour un lien vers le code source.
ListView
, il ne téléchargera tout simplement pas les bitmaps entreLes images en cours de téléchargement sont des images (75x75) de Flickr. Cependant, indiquez les URL de l'image que vous souhaitez traiter, et l'application la réduira si elle dépasse le maximum. Dans cette application, les URL sont simplement dans un tableau String
.
La LruCache
est un bon moyen de gérer les bitmaps. Cependant, dans cette application, je mets une instance de LruCache
dans une autre classe de cache que j'ai créée afin d'obtenir l'application plus réalisable.
Le contenu critique de Cache.Java (la méthode loadBitmap()
est la plus importante):
public Cache(int size, int maxWidth, int maxHeight) {
// Into the constructor you add the maximum pixels
// that you want to allow in order to not scale images.
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mBitmapCache = new LruCache<String, Bitmap>(size) {
protected int sizeOf(String key, Bitmap b) {
// Assuming that one pixel contains four bytes.
return b.getHeight() * b.getWidth() * 4;
}
};
mCurrentTasks = new ArrayList<String>();
}
/**
* Gets a bitmap from cache.
* If it is not in cache, this method will:
*
* 1: check if the bitmap url is currently being processed in the
* BitmapLoaderTask and cancel if it is already in a task (a control to see
* if it's inside the currentTasks list).
*
* 2: check if an internet connection is available and continue if so.
*
* 3: download the bitmap, scale the bitmap if necessary and put it into
* the memory cache.
*
* 4: Remove the bitmap url from the currentTasks list.
*
* 5: Notify the ListAdapter.
*
* @param mainActivity - Reference to activity object, in order to
* call notifyDataSetChanged() on the ListAdapter.
* @param imageKey - The bitmap url (will be the key).
* @param imageView - The ImageView that should get an
* available bitmap or a placeholder image.
* @param isScrolling - If set to true, we skip executing more tasks since
* the user probably has flinged away the view.
*/
public void loadBitmap(MainActivity mainActivity,
String imageKey, ImageView imageView,
boolean isScrolling) {
final Bitmap bitmap = getBitmapFromCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.ic_launcher);
if (!isScrolling && !mCurrentTasks.contains(imageKey) &&
mainActivity.internetIsAvailable()) {
BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
mainActivity.getAdapter());
task.execute();
}
}
}
Vous ne devriez pas avoir besoin de modifier quoi que ce soit dans le fichier Cache.Java, sauf si vous souhaitez implémenter la mise en cache du disque.
Les choses critiques de MainActivity.Java:
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (view.getId() == Android.R.id.list) {
// Set scrolling to true only if the user has flinged the
// ListView away, hence we skip downloading a series
// of unnecessary bitmaps that the user probably
// just want to skip anyways. If we scroll slowly it
// will still download bitmaps - that means
// that the application won't wait for the user
// to lift its finger off the screen in order to
// download.
if (scrollState == SCROLL_STATE_FLING) {
mIsScrolling = true;
} else {
mIsScrolling = false;
mListAdapter.notifyDataSetChanged();
}
}
}
// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
final ViewHolder holder;
if (row == null) {
LayoutInflater inflater = getLayoutInflater();
row = inflater.inflate(R.layout.main_listview_row, parent, false);
holder = new ViewHolder(row);
row.setTag(holder);
} else {
holder = (ViewHolder) row.getTag();
}
final Row rowObject = getItem(position);
// Look at the loadBitmap() method description...
holder.mTextView.setText(rowObject.mText);
mCache.loadBitmap(MainActivity.this,
rowObject.mBitmapUrl, holder.mImageView,
mIsScrolling);
return row;
}
getView()
est appelé très souvent. Ce n’est normalement pas une bonne idée de télécharger des images là-bas si nous n’avons pas mis en place de contrôle garantissant que nous ne démarrerons pas une quantité infinie de threads par ligne. Cache.Java vérifie si le rowObject.mBitmapUrl
est déjà dans une tâche et si c'est le cas, il n'en démarrera pas une autre. Par conséquent, il est fort probable que nous ne dépassons pas la restriction de la file d'attente de travail du pool AsyncTask
.
Vous pouvez télécharger le code source à partir de https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.Zip .
Je teste cela depuis quelques semaines maintenant, je n'ai pas encore eu une seule exception de MOO. Je l’ai testée sur l’émulateur, sur mon Nexus One et sur mon Nexus S. J'ai également testé des URL d’image contenant des images de qualité HD. Le seul goulot d'étranglement est que le téléchargement prend plus de temps.
Il n’ya qu’un seul scénario où je peux imaginer que le MOO apparaîtra, c’est-à-dire que si nous téléchargeons de très grandes images, et avant qu’elles ne soient redimensionnées et mises en cache, elles occuperont simultanément plus de mémoire et créeront un MOO. Mais ce n’est même pas une situation idéale et il ne sera probablement pas possible de le résoudre de manière plus réaliste.
Signaler des erreurs dans les commentaires! :-)
J'ai fait ce qui suit pour prendre l'image et la redimensionner à la volée. J'espère que cela t'aides
Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);
malheureusement si aucun des éléments ci-dessus ne fonctionne, ajoutez-le à votre fichier manifeste. Inside application tag
<application
Android:largeHeap="true"
Il semble que ce soit un problème très long, avec beaucoup d'explications différentes. J'ai suivi les conseils des deux réponses les plus courantes présentées ici, mais ni l'une ni l'autre de celles-ci n'a résolu mes problèmes de VM, affirmant qu'elle ne pouvait pas se permettre les octets pour effectuer la décodage une partie du processus. Après quelques recherches, j’ai appris que le vrai problème ici est le processus de décodage qui enlève le tas NATIF.
Voir ici: MOO BitmapFactory me rend fo
Cela m’amène à un autre sujet de discussion où j’ai trouvé deux solutions supplémentaires à ce problème. L'une consiste à appelerSystem.gc();
manuellement après l'affichage de votre image. Mais cela oblige réellement votre application à utiliser PLUS de mémoire afin de réduire le tas natif. La meilleure solution à partir de la version 2.0 (Donut) consiste à utiliser l'option BitmapFactory "inPurgeable". J'ai donc simplement ajouté o2.inPurgeable=true;
juste après o2.inSampleSize=scale;
.
Plus sur ce sujet ici: La limite de la mémoire est-elle seulement 6M?
Maintenant, après avoir dit tout cela, je suis un cancre complet avec Java et Android aussi. Donc, si vous pensez que c'est un moyen terrible de résoudre ce problème, vous avez probablement raison. ;-) Mais cela a fonctionné à merveille pour moi, et j’ai trouvé impossible d’exécuter le VM hors de la mémoire cache du tas maintenant. Le seul inconvénient que je puisse trouver est que vous supprimez votre image dessinée en cache. Ce qui signifie que si vous revenez à DROITE à cette image, vous la redessinerez à chaque fois. Dans le cas où mon application fonctionne, ce n'est pas vraiment un problème. Votre kilométrage peut varier.
Utilisez ceci bitmap.recycle();
Ceci aide sans aucun problème de qualité d'image.
J'ai résolu le même problème de la manière suivante.
Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
b.eraseColor(0xFFFFFFFF);
Rect r = new Rect(0, 0,320 , 424);
Canvas c = new Canvas(b);
Paint p = new Paint();
p.setColor(0xFFC0C0C0);
c.drawRect(r, p);
d = mContext.getResources().getDrawable(mImageIds[position]);
d.setBounds(r);
d.draw(c);
/*
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inTempStorage = new byte[128*1024];
b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
o2.inSampleSize=16;
o2.inPurgeable = true;
*/
} catch (Exception e) {
}
i.setImageBitmap(b);
J'ai une solution beaucoup plus efficace qui ne nécessite aucune mise à l'échelle. Décodez simplement votre bitmap une seule fois, puis mettez-la en cache dans une carte contre son nom. Ensuite, récupérez simplement le bitmap en fonction du nom et définissez-le dans ImageView. Il n'y a plus rien à faire.
Cela fonctionnera car les données binaires réelles du bitmap décodé ne sont pas stockées dans le tas dalvik VM. Il est stocké à l'extérieur. Ainsi, chaque fois que vous décodez un bitmap, il alloue de la mémoire en dehors du tas VM qui n'est jamais récupéré par GC.
Pour vous aider à mieux comprendre cela, imaginez que vous ayez conservé votre image dans le dossier pouvant être dessiné. 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'appelez. Donc, essentiellement, il est mis en cache.
Maintenant que votre image se trouve quelque part dans un fichier (ou peut même provenir d'un serveur externe), il est DE VOTRE responsabilité de mettre en cache l'instance bitmap décodée afin qu'elle soit réutilisée là où elle est nécessaire.
J'espère que cela t'aides.
Il y a deux problèmes ici ....
Cela a fonctionné pour moi!
public Bitmap readAssetsBitmap(String filename) throws IOException {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
if(bitmap == null) {
throw new IOException("File cannot be opened: It's value is null");
} else {
return bitmap;
}
} catch (IOException e) {
throw new IOException("File cannot be opened: " + e.getMessage());
}
}
Aucune des réponses ci-dessus n'a fonctionné pour moi, mais j'ai trouvé une solution de contournement horriblement moche qui a résolu le problème. J'ai ajouté une très petite image 1x1 pixel à mon projet en tant que ressource et je l'ai chargée dans mon ImageView avant d'appeler le garbage collection. Je pense qu'il se peut que ImageView ne publie pas le bitmap, de sorte que GC ne l'a jamais repris. C'est moche, mais ça semble fonctionner pour le moment.
if (bitmap != null)
{
bitmap.recycle();
bitmap = null;
}
if (imageView != null)
{
imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();
imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
Excellentes réponses ici, mais je voulais un classe pleinement utilisable pour résoudre ce problème .. alors j'en ai fait un.
Voici ma classe BitmapHelper qui est la preuve OutOfMemoryError :-)
import Java.io.File;
import Java.io.FileInputStream;
import Android.graphics.Bitmap;
import Android.graphics.Bitmap.Config;
import Android.graphics.BitmapFactory;
import Android.graphics.Canvas;
import Android.graphics.Matrix;
import Android.graphics.drawable.BitmapDrawable;
import Android.graphics.drawable.Drawable;
public class BitmapHelper
{
//decodes image and scales it to reduce memory consumption
public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
{
try
{
//Decode image size
BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
bitmapSizeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);
// load image using inSampleSize adapted to required image size
BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
bitmapDecodeOptions.inPurgeable = true;
bitmapDecodeOptions.inDither = !quickAndDirty;
bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;
Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);
// scale bitmap to mathc required size (and keep aspect ratio)
float srcWidth = (float) bitmapDecodeOptions.outWidth;
float srcHeight = (float) bitmapDecodeOptions.outHeight;
float dstWidth = (float) requiredWidth;
float dstHeight = (float) requiredHeight;
float srcAspectRatio = srcWidth / srcHeight;
float dstAspectRatio = dstWidth / dstHeight;
// recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
// (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
// Java.lang.RuntimeException: Canvas: trying to use a recycled bitmap Android.graphics.Bitmap@416ee7d8
// I do not excatly understand why, but this way it's OK
boolean recycleDecodedBitmap = false;
Bitmap scaledBitmap = decodedBitmap;
if (srcAspectRatio < dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
// will recycle recycleDecodedBitmap
recycleDecodedBitmap = true;
}
else if (srcAspectRatio > dstAspectRatio)
{
scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
recycleDecodedBitmap = true;
}
// crop image to match required image size
int scaledBitmapWidth = scaledBitmap.getWidth();
int scaledBitmapHeight = scaledBitmap.getHeight();
Bitmap croppedBitmap = scaledBitmap;
if (scaledBitmapWidth > requiredWidth)
{
int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
else if (scaledBitmapHeight > requiredHeight)
{
int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
scaledBitmap.recycle();
}
if (recycleDecodedBitmap)
{
decodedBitmap.recycle();
}
decodedBitmap = null;
scaledBitmap = null;
return croppedBitmap;
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
/**
* compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
*
* @param requiredWidth
* @param requiredHeight
* @param powerOf2
* weither we want a power of 2 sclae or not
* @return
*/
public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
{
int inSampleSize = 1;
// Raw height and width of image
final int srcHeight = options.outHeight;
final int srcWidth = options.outWidth;
if (powerOf2)
{
//Find the correct scale value. It should be the power of 2.
int tmpWidth = srcWidth, tmpHeight = srcHeight;
while (true)
{
if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
break;
tmpWidth /= 2;
tmpHeight /= 2;
inSampleSize *= 2;
}
}
else
{
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee
// a final image with both dimensions larger than or equal to the
// requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static Bitmap drawableToBitmap(Drawable drawable)
{
if (drawable instanceof BitmapDrawable)
{
return ((BitmapDrawable) drawable).getBitmap();
}
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
{
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// CREATE A MATRIX FOR THE MANIPULATION
Matrix matrix = new Matrix();
// RESIZE THE BIT MAP
matrix.postScale(scaleWidth, scaleHeight);
// RECREATE THE NEW BITMAP
Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
return resizedBitmap;
}
}
Cela fonctionne pour moi.
Bitmap myBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;
File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
et c'est sur C # monodroid. vous pouvez facilement changer le chemin de l'image. Ce qui est important ici, ce sont les options à définir.
Cela semble être l’endroit approprié pour partager ma classe d’utilitaires pour le chargement et le traitement d’images avec la communauté. Nous vous invitons à l’utiliser et à le modifier librement.
package com.emil;
import Java.io.IOException;
import Java.io.InputStream;
import Android.graphics.Bitmap;
import Android.graphics.BitmapFactory;
/**
* A class to load and process images of various sizes from input streams and file paths.
*
* @author Emil http://stackoverflow.com/users/220710/emil
*
*/
public class ImageProcessing {
public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return bm;
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
public static Dimensions getDimensions(InputStream stream) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeStream(stream,null,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using stream.");
}
}
public static Dimensions getDimensions(String imgPath) throws IOException{
BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
BitmapFactory.decodeFile(imgPath,options);
if(ImageProcessing.checkDecode(options)){
return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
}else{
throw new IOException("Image decoding failed, using file path.");
}
}
private static boolean checkDecode(BitmapFactory.Options options){
// Did decode work?
if( options.outWidth<0 || options.outHeight<0 ){
return false;
}else{
return true;
}
}
/**
* Creates a Bitmap that is of the minimum dimensions necessary
* @param bm
* @param min
* @return
*/
public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
int newWidth, newHeight;
switch(min.type){
case WIDTH:
if(bm.getWidth()>min.minWidth){
newWidth=min.minWidth;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case HEIGHT:
if(bm.getHeight()>min.minHeight){
newHeight=min.minHeight;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
break;
case BOTH: // minimize to the maximum dimension
case MAX:
if(bm.getHeight()>bm.getWidth()){
// Height needs to minimized
min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
if(bm.getHeight()>min.minDim){
newHeight=min.minDim;
newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}else{
// Width needs to be minimized
min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
if(bm.getWidth()>min.minDim){
newWidth=min.minDim;
newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
}else{
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
}
break;
default:
// No resize
newWidth=bm.getWidth();
newHeight=bm.getHeight();
}
return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
}
public static int getScaledWidth(int height, Bitmap bm){
return (int)(((double)bm.getWidth()/bm.getHeight())*height);
}
public static int getScaledHeight(int width, Bitmap bm){
return (int)(((double)bm.getHeight()/bm.getWidth())*width);
}
/**
* Get the proper sample size to meet minimization restraints
* @param dim
* @param min
* @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
* @return
*/
public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
switch(min.type){
case WIDTH:
return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
case HEIGHT:
return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
case BOTH:
int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
// Return the smaller of the two
if(widthMaxSampleSize<heightMaxSampleSize){
return widthMaxSampleSize;
}else{
return heightMaxSampleSize;
}
case MAX:
// Find the larger dimension and go bases on that
if(dim.width>dim.height){
return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
}else{
return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
}
}
return 1;
}
public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
int add=multipleOf2 ? 2 : 1;
int size=0;
while(min<(dim/(size+add))){
size+=add;
}
size = size==0 ? 1 : size;
return size;
}
public static class Dimensions {
int width;
int height;
public Dimensions(int width, int height) {
super();
this.width = width;
this.height = height;
}
@Override
public String toString() {
return width+" x "+height;
}
}
public static class Minimize {
public enum Type {
WIDTH,HEIGHT,BOTH,MAX
}
Integer minWidth;
Integer minHeight;
Integer minDim;
Type type;
public Minimize(int min, Type type) {
super();
this.type = type;
switch(type){
case WIDTH:
this.minWidth=min;
break;
case HEIGHT:
this.minHeight=min;
break;
case BOTH:
this.minWidth=min;
this.minHeight=min;
break;
case MAX:
this.minDim=min;
break;
}
}
public Minimize(int minWidth, int minHeight) {
super();
this.type=Type.BOTH;
this.minWidth = minWidth;
this.minHeight = minHeight;
}
}
/**
* Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
* @param width
* @param height
* @param config
* @return
*/
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
long pixels=width*height;
switch(config){
case ALPHA_8: // 1 byte per pixel
return pixels;
case ARGB_4444: // 2 bytes per pixel, but depreciated
return pixels*2;
case ARGB_8888: // 4 bytes per pixel
return pixels*4;
case RGB_565: // 2 bytes per pixel
return pixels*2;
default:
return pixels;
}
}
private static BitmapFactory.Options getOptionsForDimensions(){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;
return options;
}
private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = sampleSize;
options.inScaled = false;
options.inPreferredConfig = bitmapConfig;
return options;
}
}
Dans l'une de mes applications, je dois prendre une photo de Camera/Gallery
. Si l'utilisateur clique sur l'image de l'appareil photo (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 inférieure à 1-2 Mo) au-dessus du code, mais si j'ai une image de taille supérieure à 4 ou 5 Mo, alors OOM
entre dans le cadre :(
alors j'ai travaillé pour résoudre ce problème et finalement j'ai apporté l'amélioration ci-dessous au code de Fedor (Tout crédit à Fedor pour avoir fait une solution aussi agréable) :)
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 this
Je viens de connaître ce problème il y a quelques minutes. Je l'ai résolu en faisant un meilleur travail de gestion de mon adaptateur Listview. Je pensais que c’était un problème avec les centaines d’images 50 x 50 pixels que j’utilisais. En fait, j’essayais de gonfler ma vue personnalisée à chaque affichage de la ligne. Tout simplement en testant si la ligne avait été gonflée, j'ai éliminé cette erreur et j'utilise des centaines de bitmaps. Ceci est en fait pour un Spinner, mais l'adaptateur de base fonctionne de la même manière pour un ListView. Ce correctif simple a également considérablement amélioré les performances de l'adaptateur.
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
if(convertView == null){
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.spinner_row, null);
}
...
Ce problème survient uniquement dans les émulateurs Android. J'ai également rencontré ce problème dans un émulateur, mais lorsque j'ai archivé un périphérique, cela a bien fonctionné.
Alors s'il vous plaît vérifier dans un appareil. Il peut être exécuté dans l'appareil.
J'ai passé toute la journée à tester ces solutions et la seule chose qui a fonctionné pour moi, ce sont les approches ci-dessus pour obtenir l'image et appeler manuellement le GC, ce que je sais n'est pas censé être nécessaire, mais c'est la seule chose qui a fonctionné. lorsque je soumets mon application à des tests de charge importante, je passe d’une activité à l’autre. Mon application contient une liste d'images miniatures dans une liste (dans l'activité A) et lorsque vous cliquez sur l'une de ces images, vous accédez à une autre activité (indiquant l'activité B) qui affiche une image principale pour cet élément. Lorsque je basculais entre les deux activités, j'obtenais éventuellement l'erreur de MOO et l'application se fermait de force.
Quand je serais à mi-chemin dans la liste, il se planterait.
Maintenant, lorsque j'implémente ce qui suit dans l'activité B, je peux parcourir toute la liste sans aucun problème et continuer, encore et encore ... et c'est très rapide.
@Override
public void onDestroy()
{
Cleanup();
super.onDestroy();
}
private void Cleanup()
{
bitmap.recycle();
System.gc();
Runtime.getRuntime().gc();
}
utilisez ce code pour chaque image sélectionnée dans 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 of ImageData_Path.get (img_pos) .getPath ().
Mes 2 cents: j'ai résolu mes erreurs de MOO avec des bitmaps en:
a) redimensionner mes images par un facteur de 2
b) utilisation de la bibliothèque Picasso dans mon adaptateur personnalisé pour un ListView, avec un appel unique dans getView comme ceci: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);
En règle générale, Android la taille de segment de périphérique n’est que de 16 Mo (varie d’un périphérique/système d’exploitation à la publication suivante Taille du segment de mémoire ). manque d'exception de mémoire, au lieu d'utiliser Bitmap pour, le chargement d'images à partir d'une carte SD, de ressources ou même du réseau, essayez d'utiliser getImageUri , le chargement de bitmap nécessite plus de mémoire, ou vous pouvez définir bitmap sur null si votre travail est effectué avec cette bitmap.
Une telle OutofMemoryException
ne peut pas être totalement résolue en appelant la System.gc()
et ainsi de suite.
En se référant à la Cycle de vie de l'activité
Les états d'activité sont déterminés par le système d'exploitation lui-même, en fonction de l'utilisation de la mémoire pour chaque processus et de la priorité de chaque processus.
Vous pouvez considérer la taille et la résolution de chacune des images bitmap utilisées. Je recommande de réduire la taille, de ré-échantillonner à une résolution inférieure, de se référer à la conception des galeries (une petite image PNG et une image originale.)
Ce code aidera à charger de gros bitmap de drawable
public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {
Context context;
public BitmapUtilsTask(Context context) {
this.context = context;
}
/**
* Loads a bitmap from the specified url.
*
* @param url The location of the bitmap asset
* @return The bitmap, or null if it could not be loaded
* @throws IOException
* @throws MalformedURLException
*/
public Bitmap getBitmap() throws MalformedURLException, IOException {
// Get the source image's dimensions
int desiredWidth = 1000;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
int srcWidth = options.outWidth;
int srcHeight = options.outHeight;
// Only scale if the source is big enough. This code is just trying
// to fit a image into a certain width.
if (desiredWidth > srcWidth)
desiredWidth = srcWidth;
// Calculate the correct inSampleSize/scale value. This helps reduce
// memory use. It should be a power of 2
int inSampleSize = 1;
while (srcWidth / 2 > desiredWidth) {
srcWidth /= 2;
srcHeight /= 2;
inSampleSize *= 2;
}
// Decode with inSampleSize
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = inSampleSize;
options.inScaled = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
options.inPurgeable = true;
Bitmap sampledSrcBitmap;
sampledSrcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);
return sampledSrcBitmap;
}
/**
* The system calls this to perform work in a worker thread and delivers
* it the parameters given to AsyncTask.execute()
*/
@Override
protected Bitmap doInBackground(Object... item) {
try {
return getBitmap();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Toutes les solutions ici nécessitent la définition d'un IMAGE_MAX_SIZE. Cela limite les appareils dotés d’un matériel plus puissant et si la taille de l’image est trop basse, l’apparence est 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 lorsqu'un appareil plus puissant est utilisé.
L’essentiel est de calculer la mémoire maximale allouée pour l’application sur un appareil particulier, puis de définir l’échelle la plus basse possible sans dépasser cette mémoire. Voici le code:
public static Bitmap decodeFile(File f)
{
Bitmap b = null;
try
{
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(f);
try
{
BitmapFactory.decodeStream(fis, null, o);
}
finally
{
fis.close();
}
// In Samsung Galaxy S3, typically max memory is 64mb
// Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
// If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
// We try use 25% memory which equals to 16mb maximum for one bitmap
long maxMemory = Runtime.getRuntime().maxMemory();
int maxMemoryForImage = (int) (maxMemory / 100 * 25);
// Refer to
// http://developer.Android.com/training/displaying-bitmaps/cache-bitmap.html
// A full screen GridView filled with images on a device with
// 800x480 resolution would use around 1.5MB (800*480*4 bytes)
// When bitmap option's inSampleSize doubled, pixel height and
// weight both reduce in half
int scale = 1;
while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
scale *= 2;
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
fis = new FileInputStream(f);
try
{
b = BitmapFactory.decodeStream(fis, null, o2);
}
finally
{
fis.close();
}
}
catch (IOException e)
{
}
return b;
}
Je règle la mémoire maximale utilisée par ce bitmap sur 25% de la mémoire maximale allouée; vous devrez peut-être l'adapter à vos besoins et vous assurer que ce bitmap est nettoyé et qu'il ne reste pas en mémoire une fois que vous avez fini de l'utiliser. J'utilise généralement ce code pour effectuer la rotation des images (bitmap source et de destination); mon application doit donc charger 2 bitmaps en mémoire en même temps, et 25% me donnent un bon tampon sans manquer de mémoire lors de la rotation des images.
J'espère que cela aide quelqu'un là-bas ..
Il semble que les images que vous avez utilisées soient de très grande taille. Ainsi, certains appareils plus anciens tombent en panne à cause de la mémoire saturée. Dans les appareils plus anciens (Honey Comb ou ICS ou tout autre appareil modèle bas de gamme), essayez d'utiliser Android:largeHeap="true"
dans le fichier manifeste sous balise d'application ou réduisez la taille du bitmap en utilisant le code ci-dessous.
Bitmap bMap;
BitmapFactory.Options options = new BitmapFactory.Options();
options.InSampleSize = 8;
bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);
vous pouvez également donner 4 ou 12 ou 16 pour réduire la taille du bitmap
BitmapFactory.Options options = new Options();
options.inSampleSize = 32;
//img = BitmapFactory.decodeFile(imageids[position], options);
Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options);
Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true);
theImage.recycle();
theImage = null;
System.gc();
//ivlogdp.setImageBitmap(img);
Runtime.getRuntime().gc();
J'ai essayé l'approche de Thomas Vervest, mais elle renvoie une échelle de 1 pour la taille d'image 2592x1944 lorsque IMAGE_MAX_SIZE vaut 2048.
Cette version a fonctionné pour moi sur la base de tous les autres commentaires fournis par d'autres:
private 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 ();
}
int scale = 1;
for (int size = Math.max (o.outHeight, o.outWidth);
(size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);
// 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;
}
Pour réparer OutOfMemory, vous devriez faire quelque chose comme ça s'il vous plaît essayez ce code
public Bitmap loadBitmap(String URL, BitmapFactory.Options options) {
Bitmap bitmap = null;
InputStream in = null;
options.inSampleSize=4;
try {
in = OpenHttpConnection(URL);
Log.e("In====>", in+"");
bitmap = BitmapFactory.decodeStream(in, null, options);
Log.e("URL====>", bitmap+"");
in.close();
} catch (IOException e1) {
}
return bitmap;
}
et
try {
BitmapFactory.Options bmOptions;
bmOptions = new BitmapFactory.Options();
bmOptions.inSampleSize = 1;
if(studentImage != null){
galleryThumbnail= loadBitmap(IMAGE_URL+studentImage, bmOptions);
}
galleryThumbnail=getResizedBitmap(galleryThumbnail, imgEditStudentPhoto.getHeight(), imgEditStudentPhoto.getWidth());
Log.e("Image_Url==>",IMAGE_URL+studentImage+"");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Bonjour, merci de visiter le lien http://developer.Android.com/training/displaying-bitmaps/index.html
ou juste essayer de récupérer bitmap avec la fonction donnée
private Bitmap decodeBitmapFile (File f) {
Bitmap bitmap = 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 ();
}
int scale = 1;
for (int size = Math.max (o.outHeight, o.outWidth);
(size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);
// Decode with input-stram SampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options ();
o2.inSampleSize = scale;
fis = new FileInputStream (f);
try {
bitmap = BitmapFactory.decodeStream (fis, null, o2);
} finally {
fis.close ();
}
} catch (IOException e) {
}
return bitmap ;
}
utiliser ce concept cela vous aidera, Après que définir l'imagebitmap sur l'affichage de l'image
public static Bitmap convertBitmap(String path) {
Bitmap bitmap=null;
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) {
e.printStackTrace();
}
try {
if(fs!=null)
{
bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
}
} catch (IOException e) {
e.printStackTrace();
} finally{
if(fs!=null) {
try {
fs.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
Si vous souhaitez créer une petite image à partir d'une grande image de hauteur et de largeur telles que 60 et 60 et faire défiler rapidement la liste, utilisez ce concept.
public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,
int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth,
reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(path, options);
return bmp;
}
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
if (width > height) {
inSampleSize = Math.round((float) height / (float) reqHeight);
} else {
inSampleSize = Math.round((float) width / (float) reqWidth);
}
}
return inSampleSize;
}
J'espère que cela vous aidera beaucoup.
Vous pouvez prendre l'aide du site de développement Ici
Après avoir parcouru toutes les réponses, j'ai été surpris de constater que personne ne mentionnait l'API Glide pour la gestion des images. Grande bibliothèque et résume toute la complexité de la gestion des images bitmap. Vous pouvez charger et redimensionner des images rapidement avec cette bibliothèque et une seule ligne de code.
Glide.with(this).load(yourImageResource).into(imageview);
Vous pouvez obtenir le référentiel ici: https://github.com/bumptech/glide
Ajoutez les lignes suivantes à votre fichier manifest.xml:
<application
Android:hardwareAccelerated="false"
Android:largeHeap="true"
<activity>
</activity>
</application>
Meilleures pratiques pour éviter les fuites de mémoire ou le MOO pour bitmap
Si vous êtes paresseux comme moi. vous pouvez commencer à utiliser la bibliothèque Picasso pour charger des images. http://square.github.io/picasso/
Picasso.with(context).load(R.drawable.landing_screen).into(imageView1); Picasso.with(context).load("file:///Android_asset/DvpvklR.png").into(imageView2); Picasso.with(context).load(new File(...)).into(imageView3);
J'ai utilisé le descripteur de fichier Decode qui a fonctionné pour moi:
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(file);
FileDescriptor fd = fileInputStream.getFD();
Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612,
816);
imageView.setImageBitmap(imageBitmap);
} catch (Exception e) {
e.printStackTrace();
}finally {
if(fileInputStream != null){
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Code pour décoder le bitmap échantillonné à partir du descripteur de fichier:
/**
* Decode and sample down a bitmap from a file input stream to the requested width and height.
*
* @param fileDescriptor The file descriptor to read from
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return A bitmap sampled down from the original with the same aspect ratio and dimensions
* that are equal to or greater than the requested width and height
*/
public static Bitmap decodeSampledBitmapFromDescriptor(
FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
}
/**
* Calculate an inSampleSize for use in a {@link Android.graphics.BitmapFactory.Options} object when decoding
* bitmaps using the decode* methods from {@link Android.graphics.BitmapFactory}. This implementation calculates
* the closest inSampleSize that will result in the final decoded bitmap having a width and
* height equal to or larger than the requested width and height. This implementation does not
* ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
* results in a larger bitmap which isn't as useful for caching purposes.
*
* @param options An options object with out* params already populated (run through a decode*
* method with inJustDecodeBounds==true
* @param reqWidth The requested width of the resulting bitmap
* @param reqHeight The requested height of the resulting bitmap
* @return The value to be used for inSampleSize
*/
public static int calculateInSampleSize(BitmapFactory.Options options,
int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of height and width to requested height and width
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
// Choose the smallest ratio as inSampleSize value, this will guarantee a final image
// with both dimensions larger than or equal to the requested height and width.
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
// This offers some additional logic in case the image has a strange
// aspect ratio. For example, a panorama may have a much larger
// width than height. In these cases the total pixels might still
// end up being too large to fit comfortably in memory, so we should
// be more aggressive with sample down the image (=larger inSampleSize).
final float totalPixels = width * height;
// Anything more than 2x the requested pixels we'll sample down further
final float totalReqPixelsCap = reqWidth * reqHeight * 2;
while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
inSampleSize++;
}
}
return inSampleSize;
}
Cela donnera un bitmap approprié et réduira la consommation de mémoire
Java
Bitmap bm = null;
BitmapFactory.Options bmpOption = new BitmapFactory.Options();
bmpOption.inJustDecodeBounds = true;
FileInputStream fis = new FileInputStream(file);
BitmapFactory.decodeStream(fis, null, bmpOption);
fis.close();
int scale = 1;
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE) {
scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE /
(double) Math.max(bmpOption.outHeight, bmpOption.outWidth)) / Math.log(0.5)));
}
BitmapFactory.Options bmpOption2 = new BitmapFactory.Options();
bmpOption2.inSampleSize = scale;
fis = new FileInputStream(file);
bm = BitmapFactory.decodeStream(fis, null, bmpOption2);
fis.close();
Kotlin
val bm:Bitmap = null
val bmpOption = BitmapFactory.Options()
bmpOption.inJustDecodeBounds = true
val fis = FileInputStream(file)
BitmapFactory.decodeStream(fis, null, bmpOption)
fis.close()
val scale = 1
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE)
{
scale = Math.pow(2.0, Math.ceil((Math.log((IMAGE_MAX_SIZE / Math.max(bmpOption.outHeight, bmpOption.outWidth) as Double)) / Math.log(0.5))).toInt().toDouble()).toInt()
}
val bmpOption2 = BitmapFactory.Options()
bmpOption2.inSampleSize = scale
fis = FileInputStream(file)
bm = BitmapFactory.decodeStream(fis, null, bmpOption2)
fis.close()