Problema estranho de falta de memória ao carregar uma imagem em um objeto Bitmap

1289

Eu tenho uma exibição de lista com alguns botões de imagem em cada linha. Quando você clica na linha da lista, ela lança uma nova atividade. Eu tive que criar minhas próprias guias por causa de um problema com o layout da câmera. A atividade iniciada para o resultado é um mapa. Se eu clicar no meu botão para iniciar a visualização da imagem (carregar uma imagem do cartão SD), o aplicativo retornará da atividade de volta à listviewatividade para o manipulador de resultados para reiniciar minha nova atividade, que nada mais é do que um widget de imagem.

A visualização da imagem na exibição de lista está sendo feita com o cursor e ListAdapter. Isso torna bastante simples, mas não tenho certeza de como posso colocar uma imagem redimensionada (ou seja, tamanho de bit menor e não pixel como o srcbotão de imagem em tempo real. Acabei de redimensionar a imagem que saiu da câmera do telefone.

O problema é que recebo um erro de falta de memória quando ele tenta voltar e reiniciar a 2ª atividade.

  • Existe uma maneira de criar o adaptador de lista facilmente linha por linha, onde eu possa redimensionar rapidamente (um pouco )?

Isso seria preferível, pois também preciso fazer algumas alterações nas propriedades dos widgets / elementos em cada linha, pois não consigo selecionar uma linha com a tela de toque por causa do problema de foco. ( Eu posso usar bola de rolo. )

  • Eu sei que posso redimensionar fora da banda e salvar a minha imagem, mas não é exatamente isso que eu quero fazer, mas algum código de exemplo para isso seria bom.

Assim que desabilitei a imagem na exibição de lista, ela funcionou bem novamente.

FYI: Era assim que eu fazia:

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

Onde R.id.imagefilenameestá um ButtonImage.

Aqui está o meu 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 

Também tenho um novo erro ao exibir uma imagem:

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
Chrispix
fonte
8
Resolvi isso evitando o Bitmap.decodeStream ou decodeFile e usando o método BitmapFactory.decodeFileDescriptor.
Fraggle
1
Também enfrentei um problema semelhante há algumas semanas e resolvi-o diminuindo as imagens até o ponto ideal. Eu escrevi uma abordagem completa no meu blog codingjunkiesforum.wordpress.com/2014/06/12/… e enviei um exemplo de projeto completo com código propenso OOM vs código de prova OOM em http://github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat
5
A resposta aceita para esta pergunta está sendo discutida no meta
rene
4
Isso acontece quando você não ler os guias de desenvolvedor Android
Pedro Varela
2
Isso acontece devido à arquitetura do Android ruim. Ele deve redimensionar as próprias imagens, como o ios e a UWP faz isso. Eu não tenho que fazer isso sozinho. Os desenvolvedores do Android se acostumam com esse inferno e pensam que funciona da maneira que deveria.
Acesso negado

Respostas:

651

A classe de treinamento do Android , " Exibindo bitmaps com eficiência ", oferece ótimas informações para entender e lidar com a exceção java.lang.OutOfMemoryError: bitmap size exceeds VM budgetao carregar bitmaps.


Leia dimensões e tipo de bitmap

A BitmapFactoryclasse fornece vários métodos de descodificação ( decodeByteArray(), decodeFile(), decodeResource(), etc.) para a criação de uma Bitmapde várias fontes. Escolha o método de decodificação mais apropriado com base na fonte de dados da imagem. Esses métodos tentam alocar memória para o bitmap construído e, portanto, podem facilmente resultar em uma OutOfMemoryexceção. Cada tipo de método de decodificação possui assinaturas adicionais que permitem especificar opções de decodificação por meio da BitmapFactory.Optionsclasse. Definir a inJustDecodeBoundspropriedade como truedecodificação evita a alocação de memória, retornando nullpara o objeto bitmap, mas definindo outWidth, outHeighte outMimeType. Essa técnica permite que você leia as dimensões e o tipo dos dados da imagem antes da construção (e alocação de memória) do 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;

Para evitar java.lang.OutOfMemoryexceções, verifique as dimensões de um bitmap antes de decodificá-lo, a menos que você confie totalmente na fonte para fornecer dados de imagem de tamanho previsível que cabem confortavelmente na memória disponível.


Carregar uma versão reduzida na memória

Agora que as dimensões da imagem são conhecidas, elas podem ser usadas para decidir se a imagem completa deve ser carregada na memória ou se uma versão com subamostra deve ser carregada. Aqui estão alguns fatores a serem considerados:

  • Uso estimado da memória para carregar a imagem completa na memória.
  • A quantidade de memória que você deseja comprometer para carregar esta imagem, considerando quaisquer outros requisitos de memória do seu aplicativo.
  • Dimensões do componente ImageView ou UI de destino no qual a imagem deve ser carregada.
  • Tamanho da tela e densidade do dispositivo atual.

Por exemplo, não vale a pena carregar uma imagem de 1024x768 pixels na memória se ela for exibida em uma miniatura de 128x96 pixels em uma ImageView.

Para dizer a decodificador para subamostra a imagem, carregar uma versão menor na memória, conjunto inSampleSizepara trueem seu BitmapFactory.Optionsobjeto. Por exemplo, uma imagem com resolução 2048x1536 decodificada com um inSampleSizede 4 produz um bitmap de aproximadamente 512x384. Carregar isso na memória usa 0,75 MB em vez de 12MB para a imagem completa (assumindo uma configuração de bitmap de ARGB_8888). Aqui está um método para calcular um valor de tamanho de amostra que é uma potência de dois com base na largura e altura do destino:

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

Nota : Uma potência de dois valores é calculada porque o decodificador usa um valor final arredondando para a potência mais próxima de dois, conforme a inSampleSizedocumentação.

Para usar esse método, decodifique primeiro com inJustDecodeBoundsset true, passe as opções e decodifique novamente usando o novo inSampleSizevalor e inJustDecodeBoundsconfigure para 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);
}

Esse método facilita o carregamento de um bitmap de tamanho arbitrariamente grande em um ImageViewque exibe uma miniatura de 100 x 100 pixels, conforme mostrado no código de exemplo a seguir:

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

Você pode seguir um processo semelhante para decodificar bitmaps de outras fontes, substituindo o BitmapFactory.decode*método apropriado conforme necessário.

Sazid
fonte
21
Esta resposta está sendo discutida em meta
rene
9
Esta resposta (exceto as informações obtidas através do link) não oferece muita solução quanto a resposta. As partes importantes do link devem ser mescladas à pergunta.
FallenAngel
7
Essa resposta, como a pergunta e as outras respostas, é o Community Wiki, portanto, isso pode ser corrigido pela edição, algo que não requer intervenção do moderador.
Martijn Pieters
O link atual para o conteúdo e o suporte Kotlin pode ser encontrado em: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr
891

Para corrigir o erro OutOfMemory, você deve fazer algo assim:

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

Esta inSampleSizeopção reduz o consumo de memória.

Aqui está um método completo. Primeiro, ele lê o tamanho da imagem sem decodificar o conteúdo. Então ele encontra o melhor inSampleSizevalor, deve ter uma potência de 2 e, finalmente, a imagem é decodificada.

// 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;
}
Fedor
fonte
31
Note-se que 10 pode não ser o melhor valor para inSampleSize entanto, a documentação sugere o uso de potências de 2.
Mirko N.
70
Estou enfrentando o mesmo problema que o Chrispix, mas não acho que a solução aqui realmente resolva o problema, mas evite-o. Alterar o tamanho da amostra reduz a quantidade de memória usada (ao custo da qualidade da imagem, o que provavelmente é aceitável para uma visualização da imagem), mas isso não impedirá a exceção se um fluxo de imagem grande o suficiente for decodificado, ou se houver vários fluxos de imagem. decodificado. Se eu encontrar uma solução melhor (e talvez não exista), postarei uma resposta aqui.
Flynn81
4
Você só precisa de um tamanho apropriado para corresponder à tela na densidade de pixels, para aumentar o zoom, e assim você pode tirar uma amostra da imagem em uma densidade mais alta.
Stealthcopter
4
REQUIRED_SIZE é o novo tamanho para o qual você deseja dimensionar.
Fedor
8
essa solução me ajudou, mas a qualidade da imagem é terrível. Estou usando um viewfilpper para exibir as imagens, alguma sugestão?
user1106888
373

Fiz uma pequena melhoria no código do Fedor. Basicamente, faz o mesmo, mas sem o (na minha opinião) feio while loop e sempre resulta em uma potência de dois. Parabéns ao Fedor por fazer a solução original, fiquei preso até encontrar a dele e depois consegui fazer essa :)

 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;
}
Thomas Vervest
fonte
40
Sim, você está certo enquanto não é tão bonito. Eu apenas tentei deixar claro para todos. Obrigado pelo seu código.
Fedor
10
@ Thomas Vervest - Há um grande problema com esse código. ^ não eleva 2 a uma potência, xors 2 com o resultado. Você quer Math.pow (2.0, ...). Caso contrário, isso parece bom.
DougW
6
Ooh, isso é muito bom! Meu mal, eu vou corrigi-lo imediatamente, obrigado pela resposta!
Thomas Vervest 26/10/10
8
Você está criando dois novos FileInputStreams, um para cada chamada ao BitmapFactory.decodeStream(). Você não precisa salvar uma referência para cada um deles para que eles possam ser fechados em um finallybloco?
matsev
1
@Babibu A documentação não indica que o fluxo está fechado para você, portanto, presumo que ele ainda deva estar fechado. Uma discussão interessante e relacionada pode ser encontrada aqui . Observe o comentário de Adrian Smith, que se relaciona diretamente ao nosso debate.
Thomas Vervest
233

Sou da experiência do iOS e fiquei frustrado ao descobrir um problema com algo tão básico quanto carregar e mostrar uma imagem. Afinal, todo mundo que está tendo esse problema está tentando exibir imagens de tamanho razoável. Enfim, aqui estão as duas alterações que corrigiram meu problema (e tornaram meu aplicativo muito responsivo).

1) Toda vez que fizer isso BitmapFactory.decodeXYZ(), passe um a BitmapFactory.Optionscom inPurgeabledefinido como true(e de preferência com inInputShareabletambém definido como true).

2) NUNCA use Bitmap.createBitmap(width, height, Config.ARGB_8888). Quero dizer NUNCA! Eu nunca tive essa coisa não aumentar o erro de memória depois de algumas passagens. Nenhuma quantidade de recycle(), System.gc(), qualquer que seja ajudado. Sempre levantava exceção. A outra maneira que realmente funciona é ter uma imagem fictícia em seus drawables (ou outro Bitmap que você decodificou usando a etapa 1 acima), redimensioná-la para o que quiser e manipular o Bitmap resultante (como passá-lo para um Canvas para mais diversão). Então, o que você deve usar em vez disso é: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Se, por qualquer motivo, você DEVE usar o método de criação de força bruta, passe pelo menos Config.ARGB_4444.

É quase garantido que você economize horas, se não dias. Tudo o que fala sobre dimensionar a imagem, etc., realmente não funciona (a menos que você considere obter um tamanho errado ou degradar a imagem como uma solução).

Ephraim
fonte
22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;e Bitmap.createScaledBitmap(srcBitmap, width, height, false);resolvi meu problema com exceção de falta de memória no android 4.0.0. Obrigado companheiro!
Jan-Terje Sørensen
5
Na chamada Bitmap.createScaledBitmap (), você provavelmente deve usar true como o parâmetro flag. Caso contrário, a qualidade da imagem não será suave ao aumentar a escala. Verifique este thread stackoverflow.com/questions/2895065/…
rOrlig
11
Esse é realmente um conselho fabuloso. Gostaria de poder lhe dar um +1 extra por levar o Google à tarefa por esse bug dink incrivelmente excêntrico. Quero dizer ... se não é um bug, a documentação realmente precisa ter alguns sinais de néon piscando seriamente dizendo "ISSO É COMO VOCÊ PROCESSA FOTOS", porque eu estou lutando com isso há 2 anos e agora encontrei este post. Ótima descoberta.
Yevgeny Simkin
Escalar suas imagens definitivamente ajuda, mas este é um passo importante e o que acabou por resolver esse problema para mim. O problema de apenas escalar suas imagens é se você tiver muitas delas ou se as imagens de origem forem muito grandes, então você ainda poderá enfrentar o mesmo problema. +1 a você Efraim.
Dave
10
A partir do Lollipop, BitmapFactory.Options.inPurgeablee BitmapFactory.Options.inInputShareableestão obsoletos developer.android.com/reference/android/graphics/…
Denis Kniazhev
93

É um bug conhecido , não é por causa de arquivos grandes. Como o Android armazena em cache os Drawables, fica sem memória depois de usar poucas imagens. Mas eu encontrei uma maneira alternativa para isso, ignorando o sistema de cache padrão do Android.

Solução : Mova as imagens para a pasta "assets" e use a seguinte função para obter o 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);
}
Anto Binish Kaspar
fonte
79

Eu tive esse mesmo problema e o resolvi evitando as funções BitmapFactory.decodeStream ou decodeFile e, em vez disso, usei BitmapFactory.decodeFileDescriptor

decodeFileDescriptor parece que chama métodos nativos diferentes do decodeStream / decodeFile.

De qualquer forma, o que funcionou foi isso (observe que eu adicionei algumas opções como algumas acima, mas não foi isso que fez a diferença. O mais importante é a chamada para BitmapFactory.decodeFileDescriptor em vez 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;                        
}

Eu acho que há um problema com a função nativa usada em decodeStream / decodeFile. Confirmei que um método nativo diferente é chamado ao usar decodeFileDescriptor. Também o que eu li é "que as Imagens (Bitmaps) não são alocadas de maneira Java padrão, mas por meio de chamadas nativas; as alocações são feitas fora do heap virtual, mas são contadas contra ele! "

Fraggle
fonte
1
mesmo resultado de memória, na verdade, não importa qual método você está usando, depende do número de bytes que você está segurando para ler os dados que ficam sem memória.
PiyushMishra
72

Eu acho que a melhor maneira de evitar o OutOfMemoryError é enfrentá-lo e entendê-lo.

Eu fiz um aplicativo para causar intencionalmenteOutOfMemoryError e monitorar o uso de memória.

Depois de fazer muitas experiências com este aplicativo, tenho as seguintes conclusões:

Vou falar sobre as versões do SDK antes da Honey Comb primeiro.

  1. O bitmap é armazenado no heap nativo, mas será coletado o lixo automaticamente, sendo desnecessário chamar recycle ().

  2. Se {tamanho da pilha da VM} + {memória de pilha nativa alocada}> = = {limite de tamanho da pilha da VM para o dispositivo} e você estiver tentando criar bitmap, o OOM será lançado.

    AVISO: O TAMANHO HEAP DA VM é contado em vez da MEMÓRIA ALOCADA DA VM.

  3. O tamanho da pilha da VM nunca diminui após o crescimento, mesmo que a memória da VM alocada seja reduzida.

  4. Portanto, você deve manter o pico da memória da VM o mais baixo possível para impedir que o tamanho da pilha da VM cresça muito demais para economizar memória disponível para os bitmaps.

  5. Chamar manualmente System.gc () não faz sentido, o sistema o chamará primeiro antes de tentar aumentar o tamanho do heap.

  6. O tamanho nativo da pilha nunca diminuirá também, mas não é contabilizado para o OOM, portanto, não é necessário se preocupar com isso.

Então, vamos falar sobre o SDK Starts do Honey Comb.

  1. O bitmap é armazenado no heap da VM, a memória nativa não é contada para o OOM.

  2. A condição para o OOM é muito mais simples: {tamanho da pilha da VM}> = = {limite de tamanho da pilha da VM para o dispositivo}.

  3. Para que você tenha mais memória disponível para criar bitmap com o mesmo limite de tamanho de heap, é menos provável que o OOM seja lançado.

Aqui estão algumas das minhas observações sobre coleta de lixo e vazamento de memória.

Você pode vê-lo no aplicativo. Se uma Atividade executou um AsyncTask que ainda estava em execução após a Atividade ter sido destruída, a Atividade não será coletada como lixo até que o AsyncTask seja concluído.

Isso ocorre porque o AsyncTask é uma instância de uma classe interna anônima e contém uma referência da atividade.

Chamar AsyncTask.cancel (true) não interromperá a execução se a tarefa estiver bloqueada em uma operação de E / S no encadeamento em segundo plano.

Os retornos de chamada também são classes internas anônimas; portanto, se uma instância estática do seu projeto os mantiver e não os liberar, a memória vazará.

Se você agendou uma tarefa repetida ou atrasada, por exemplo, um Timer e não chamou cancel () e purge () em onPause (), a memória vazaria.

coocood
fonte
O AsyncTask não precisa necessariamente ser "uma instância de uma classe interna anônima" e o mesmo vale para os retornos de chamada. Você pode criar uma nova classe pública em seu próprio arquivo que estende o AsyncTask, ou mesmo private static classna mesma classe. Eles não vão ter qualquer referência à actividade (a menos que você lhes dá um claro)
Simon Forsberg
65

Ultimamente, tenho visto muitas perguntas sobre exceções de OOM e cache. O guia do desenvolvedor tem um artigo realmente bom sobre isso, mas alguns tendem a falhar ao implementá-lo de maneira adequada.

Por isso, escrevi um exemplo de aplicativo que demonstra o cache em um ambiente Android. Esta implementação ainda não obteve um OOM.

Veja no final desta resposta um link para o código-fonte.

Requisitos:

  • API Android 2.1 ou superior (simplesmente não consegui obter a memória disponível para um aplicativo na API 1.6 - esse é o único trecho de código que não funciona na API 1.6)
  • Pacote de suporte para Android

Captura de tela

Recursos:

  • Mantém o cache se houver uma mudança de orientação , usando um singleton
  • Use um oitavo da memória do aplicativo atribuída ao cache (modifique se desejar)
  • Os bitmaps grandes são redimensionados (você pode definir o máximo de pixels que deseja permitir)
  • Controla se há uma conexão com a Internet disponível antes de baixar os bitmaps
  • Garante que você esteja instanciando apenas uma tarefa por linha
  • Se você está lançando a ListViewdistância, ele simplesmente não vai baixar os bitmaps entre

Isso não inclui:

  • Cache de disco. De qualquer maneira, isso deve ser fácil de implementar - basta apontar para uma tarefa diferente que captura os bitmaps do disco

Código de amostra:

As imagens que estão sendo baixadas são imagens (75x75) do Flickr. No entanto, coloque os URLs da imagem que você deseja processar e o aplicativo a reduzirá se exceder o máximo. Nesta aplicação, os URLs estão simplesmente em uma Stringmatriz.

O LruCachetem uma boa maneira de lidar com bitmaps. No entanto, nesta aplicação, coloquei uma instância de uma LruCacheoutra classe de cache que criei para tornar o aplicativo mais viável.

Coisas críticas do Cache.java (o loadBitmap()método é o mais 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();
        }
    } 
}

Você não precisa editar nada no arquivo Cache.java, a menos que queira implementar o cache de disco.

Material crítico 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()é chamado com muita frequência. Normalmente, não é uma boa ideia fazer o download de imagens lá se não tivermos implementado uma verificação que garanta que não iniciaremos uma quantidade infinita de threads por linha. O Cache.java verifica se o rowObject.mBitmapUrljá está em uma tarefa e, se estiver, não iniciará outra. Portanto, provavelmente não estamos excedendo a restrição da fila de trabalho doAsyncTask pool.

Baixar:

Você pode fazer o download do código-fonte em https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip .


Últimas palavras:

Eu testei isso por algumas semanas agora, ainda não recebi uma única exceção do OOM. Eu testei isso no emulador, no meu Nexus One e no meu Nexus S. Testei URLs de imagem que contêm imagens com qualidade HD. O único gargalo é que leva mais tempo para fazer o download.

Há apenas um cenário possível em que posso imaginar que o OOM aparecerá, ou seja, se fizermos o download de muitas imagens realmente grandes e, antes que elas sejam dimensionadas e colocadas no cache, ocupem simultaneamente mais memória e causam um OOM. Mas isso não é nem mesmo uma situação ideal e provavelmente não será possível resolver de uma maneira mais viável.

Relatar erros nos comentários! :-)

Wroclai
fonte
43

Fiz o seguinte para pegar a imagem e redimensioná-la rapidamente. Espero que isto ajude

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
Chrispix
fonte
26
Essa abordagem dimensiona o bitmap. Mas isso não resolve o problema OutOfMemory porque o bitmap completo está sendo decodificado de qualquer maneira.
Fedor
5
Vou ver se consigo ver meu código antigo, mas acho que resolveu meus problemas de falta de memória. Vou checar meu código antigo.
chrispix
2
Neste exemplo, pelo menos, parece que você não está mantendo a referência ao bitmap completo, economizando memória.
NoBugs
Para mim, resolveu o problema de memória, mas reduziu a qualidade das imagens.
Pamela Sillah
35

Parece que este é um problema muito demorado, com muitas explicações diferentes. Eu segui o conselho das duas respostas apresentadas mais comuns aqui, mas nenhuma delas resolveu meus problemas da VM alegando que não podia pagar os bytes para executar a parte de decodificação do processo. Depois de algumas escavações, aprendi que o verdadeiro problema aqui é o processo de decodificação que tira o NATIVE heap .

Veja aqui: BitmapFactory OOM me deixando louco

Isso me levou a outro tópico de discussão em que encontrei mais algumas soluções para esse problema. Uma é ligar System.gc();manualmente após a exibição da imagem. Mas isso faz com que seu aplicativo use MAIS memória, em um esforço para reduzir a pilha nativa. A melhor solução a partir do lançamento do 2.0 (Donut) é usar a opção BitmapFactory "inPurgeable". Então eu simplesmente adicionei o2.inPurgeable=true;logo depois o2.inSampleSize=scale;.

Mais sobre esse tópico aqui: O limite de memória é de apenas 6M?

Agora, tendo dito tudo isso, também sou um idiota completo com Java e Android. Portanto, se você acha que essa é uma maneira terrível de resolver esse problema, provavelmente está certo. ;-) Mas isso fez maravilhas para mim e achei impossível executar a VM fora do cache de heap agora. A única desvantagem que posso encontrar é que você está destruindo sua imagem desenhada em cache. O que significa que, se você voltar DIRETAMENTE àquela imagem, estará redesenhando-a sempre. No caso de como meu aplicativo funciona, isso não é realmente um problema. Sua milhagem pode variar.

RayHaque
fonte
OOM fixo inPurgeable para mim.
Artem Russakovskii
35

infelizmente, se nenhuma das opções acima funcionar, adicione isso ao seu arquivo de manifesto . Etiqueta interna da aplicação

 <application
         android:largeHeap="true"
Himanshu Mori
fonte
1
Você pode explicar o que isso realmente faz? Simplesmente dizer às pessoas para adicionar isso não ajuda.
Rabino furtivo
1
Esta é uma solução muito ruim. Basicamente, você não está tentando corrigir o problema. Em vez disso, peça ao sistema Android para alocar mais espaço de heap para seu aplicativo. Isso terá implicações muito ruins no seu aplicativo, como o aplicativo que consome muita energia da bateria, pois o GC precisa percorrer um grande espaço de pilha para limpar a memória e também o desempenho do aplicativo será mais lento.
Prakash
2
então por que o android está nos permitindo adicionar esse android: largeHeap = "true" em nosso manifesto? Agora você está desafiando o Android.
Himanshu Mori
32

Use isto bitmap.recycle();Isso ajuda sem nenhum problema de qualidade de imagem.

Arsalan
fonte
9
De acordo com a API, não é necessário chamar recycle ().
Artem Russakovskii
28

Eu tenho uma solução muito mais eficaz que não precisa de escala de nenhum tipo. Simplesmente decodifique seu bitmap apenas uma vez e, em seguida, armazene-o em cache em um mapa com seu nome. Em seguida, recupere o bitmap com o nome e defina-o no ImageView. Não há mais nada a ser feito.

Isso funcionará porque os dados binários reais do bitmap decodificado não são armazenados no heap da dalvik VM. É armazenado externamente. Portanto, toda vez que você decodifica um bitmap, ele aloca memória fora do heap da VM, que nunca é recuperada pelo GC

Para ajudá-lo a apreciar melhor isso, imagine que você tenha mantido sua imagem na pasta de desenho. Você acabou de obter a imagem executando getResources (). GetDrwable (R.drawable.). Isso NÃO decodificará sua imagem toda vez, mas reutilizará uma instância já decodificada toda vez que você a chamar. Então, em essência, é armazenado em cache.

Agora, como sua imagem está em um arquivo em algum lugar (ou pode vir de um servidor externo), é SUA responsabilidade armazenar em cache a instância de bitmap decodificada para ser reutilizada em qualquer lugar que seja necessário.

Espero que isto ajude.

Parth Mehta
fonte
4
"e, em seguida, armazene-o em cache em um mapa com seu nome." Como exatamente você armazena em cache suas imagens?
27411 Vincent
3
Você realmente tentou isso? Mesmo que os dados de pixel não sejam realmente armazenados no heap Dalvik, seu tamanho na memória nativa é relatado à VM e contado na memória disponível.
ErikR
3
@Incent Acho que não é difícil armazená-los em um mapa. Eu sugeriria algo como o mapa HashMap <KEY, Bitmap>, onde a Key pode ser uma String da fonte ou qualquer coisa que faça sentido para você. Vamos supor que você tomar um caminho como chave, você armazená-lo como map.put (Path, Bitmap) e recebê-lo através map.get (Caminho)
Rafael T
3
você provavelmente desejaria usar o HashMap <String, SoftReference <Bitmap>> se estiver implementando um cache de imagem, caso contrário poderá ficar sem memória de qualquer maneira - também não acho que "ele aloque memória fora do heap da VM que nunca é recuperado pelo GC "é verdade, a memória é recuperada como eu entendo só pode haver um atraso, que é o que bitmap.recycle () é para, como uma dica para recuperar o mem cedo ...
Dori
28

Resolvi o mesmo problema da seguinte maneira.

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);
Prerna
fonte
Está tudo bem, mas eu estou usando múltiplos bitmap para círculo empate em OnCreate e chamada atividade 4-5 vezes tão como bitmap clara e como remover bitmap e criar novamente pela segunda vez quando a atividade 0nCreate ..
ckpatel
27

Há duas questões aqui....

  • A memória de bitmap não está no heap da VM, mas no heap nativo - consulte BitmapFactory OOM me deixando louco
  • A coleta de lixo para o heap nativo é mais preguiçosa que o heap da VM - portanto, você precisa ser bastante agressivo ao fazer bitmap.recycle e bitmap = null toda vez que passar pela onPause ou onDestroy de uma atividade
Torid
fonte
Ele está na pilha da VM desde o Android 2.3
superior.
27

Isso funcionou para mim!

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());
    }
}
Luke Taylor
fonte
20

Nenhuma das respostas acima funcionou para mim, mas eu criei uma solução horrivelmente feia que resolveu o problema. Adicionei uma imagem muito pequena, de 1 x 1 pixel ao meu projeto como recurso e carreguei-a no meu ImageView antes de chamar a coleta de lixo. Eu acho que o ImageView não estava lançando o Bitmap, então a GC nunca o pegou. É feio, mas parece estar funcionando por enquanto.

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.
Mike
fonte
parece que o imageView realmente não recicla o bitmap por si só. Ajudou-me, obrigado #
1100 Dmitry Zaytsev
@ Mike, você pode adicionar o código completo do imageloader ou pode me fornecer o link para carregar imagens de bitmap. Se eu usar reciclagem em bitmap todo o meu listview é exibido, mas todos os itens são mostrados em branco
TNR
@ Mike você pode dizer se eu faço imageView = null antes de chamar a coleta de lixo é bom ou não?
Youddh
@TNR Acho que o que você está perdendo aqui é que, bitmapno código acima, a imagem anterior já é exibida, é necessário reciclar, limpar todas as referências a ela, fazer com que a imageViewesqueça também, definindo uma pequena substituição gc(); e depois de tudo isso: carregue sua NOVA imagem bitmape exiba-a ...no código acima.
TWIStErRob 01/08/14
Isto está errado. Você sempre deve limpar o conteúdo do imageView ANTES de reciclar o bitmap (em vez de enquanto ele é realmente exibido e em uso).
FindOut_Quran
20

Ótimas respostas aqui, mas eu queria uma classe totalmente utilizável para resolver esse problema.

Aqui está minha classe BitmapHelper que é a prova 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;
    }

}
Pascal
fonte
Para quem usa isso: Eu consertei um bug: "int scaledBitmapHeight = scaledBitmap.getWidth ();" está errado (obviamente. Substituí-o por "int scaledBitmapHeight = scaledBitmap.getHeight ();")
Pascal
19

Isso funciona para mim.

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

e isso é em C # monodroid. você pode facilmente mudar o caminho da imagem. O importante aqui são as opções a serem definidas.

Dobermaxx99
fonte
16

Este parece ser o local apropriado para compartilhar minha classe de utilitário para carregar e processar imagens com a comunidade; você pode usá-lo e modificá-lo livremente.

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;
    }
}
Emil Davtyan
fonte
16

Em um dos meus aplicativos, preciso tirar uma foto Camera/Gallery. Se o usuário clicar na imagem da câmera (pode ser 2MP, 5MP ou 8MP), o tamanho da imagem varia dekB s para MBs. Se o tamanho da imagem for menor (ou até 1-2 MB) acima do código funcionando bem, mas se eu tiver uma imagem de tamanho acima de 4 MB ou 5 MB, o OOMquadro será fornecido :(

então trabalhei para resolver esse problema e finalmente fiz a melhoria abaixo no código do Fedor (todo o crédito ao Fedor por fazer uma solução tão boa) :)

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;

}

Espero que isso ajude os amigos que enfrentam o mesmo problema!

para mais informações, consulte este

Rupesh Yadav
fonte
14

Acabei de encontrar este problema há alguns minutos. Eu o resolvi fazendo um trabalho melhor no gerenciamento do meu adaptador listview. Eu pensei que era um problema com as centenas de imagens de 50x50px que eu estava usando. Acontece que eu estava tentando aumentar minha exibição personalizada toda vez que a linha estava sendo exibida. Simplesmente testando para ver se a linha havia sido inflada, eliminei esse erro e estou usando centenas de bitmaps. Na verdade, isso é para um Spinner, mas o adaptador básico funciona da mesma forma para um ListView. Essa correção simples também melhorou bastante o desempenho do adaptador.

@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);
    }
...
BajaBob
fonte
3
Não posso agradecer o suficiente por isso! Eu estava perseguindo o problema errado antes de ver isso. Pergunta para você: porém, como cada uma das linhas da minha lista tem um nome e uma foto exclusivos, tive que usar uma matriz convertView para manter os valores de cada uma das linhas. Eu não conseguia ver como o uso de uma única variável permitiria que você o fizesse. Estou esquecendo de algo?
Peteh
13

Passei o dia inteiro testando essas soluções e a única coisa que funcionou para mim foram as abordagens acima para obter a imagem e chamar manualmente o GC, que eu sei que não é necessário, mas é a única coisa que funcionou quando coloco meu aplicativo em testes de carga pesada alternando entre atividades. Meu aplicativo tem uma lista de imagens em miniatura em uma exibição de lista em (digamos atividade A) e quando você clica em uma dessas imagens, ele o leva para outra atividade (digamos atividade B) que mostra uma imagem principal para esse item. Quando eu alternava entre as duas atividades, acabava recebendo o erro OOM e o aplicativo se fechava.

Quando eu chegava na metade da lista, ele travava.

Agora, quando eu implemento o seguinte na atividade B, posso percorrer toda a lista sem problemas e continuar indo e indo e indo ... e é muito rápido.

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
Jesse
fonte
Ame sua solução! Também passei horas resolvendo esse bug, tão irritante! Edit: Infelizmente, o problema ainda existe quando eu mudar a minha orientação da tela no modo paisagem ...
Xarialon
Isso finalmente me ajudou junto com: - BitmapFactory.Options options = new BitmapFactory.Options (); options.InPurgeable = true; options.InSampleSize = 2;
user3833732
13

Esse problema ocorre apenas em emuladores Android. Também enfrentei esse problema em um emulador, mas quando fiz o check-in de um dispositivo, ele funcionou bem.

Então, verifique o dispositivo. Pode ser executado no dispositivo.

hackjutsu
fonte
12

Meus 2 centavos: resolvi meus erros de OOM com bitmaps por:

a) dimensionar minhas imagens por um fator de 2

b) usando a biblioteca Picasso no meu adaptador personalizado para um ListView, com uma chamada em getView, desta forma:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

matsoftware
fonte
Fico feliz que você tenha mencionado Picasso, porque isso facilita muito o carregamento de imagens. Especialmente armazenados remotamente.
31713 Chris13
12

use esse código para todas as imagens selecionadas em SdCard ou drewable para converter objetos de 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;

use o caminho da imagem no final de ImageData_Path.get (img_pos) .getPath () .

Gaurav Pansheriya
fonte
12

Geralmente, o tamanho da pilha do dispositivo Android é de apenas 16 MB (varia de acordo com o tamanho do dispositivo / SO, consulte os tamanhos de pilha da postagem ). Se você estiver carregando as imagens e cruzar o tamanho de 16 MB, lançará uma exceção de falta de memória, em vez de usar o Bitmap para carregar imagens do cartão SD ou de recursos ou mesmo da rede tentam usar getImageUri , carregar o bitmap requer mais memória ou você pode definir o bitmap como nulo se o trabalho foi feito com esse bitmap.

Rohit Sharma
fonte
1
E se setImageURI ainda está recebendo exceção, consulte este stackoverflow.com/questions/15377186/…
Mahesh
11

Todas as soluções aqui exigem a configuração de um IMAGE_MAX_SIZE. Isso limita os dispositivos com hardware mais poderoso e, se o tamanho da imagem for muito baixo, ele ficará feio na tela HD.

Eu criei uma solução que funciona com o meu Samsung Galaxy S3 e vários outros dispositivos, incluindo os menos poderosos, com melhor qualidade de imagem quando um dispositivo mais poderoso é usado.

O essencial é calcular o máximo de memória alocada para o aplicativo em um dispositivo específico e, em seguida, defina a escala para a menor possível, sem exceder essa memória. Aqui está o código:

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

Defino a memória máxima usada por este bitmap como 25% da memória máxima alocada; talvez seja necessário ajustá-lo às suas necessidades e verifique se esse bitmap está limpo e não fica na memória quando você terminar de usá-lo. Normalmente, eu uso esse código para executar a rotação da imagem (bitmap de origem e destino) para que meu aplicativo precise carregar 2 bitmaps na memória ao mesmo tempo, e 25% fornece um bom buffer sem ficar sem memória ao executar a rotação da imagem.

Espero que isto seja útil a alguém..

Bruce
fonte
11

Isso OutofMemoryExceptionnão pode ser totalmente resolvido chamando o System.gc()e assim por diante.

Ao se referir ao Ciclo de Vida da Atividade

Os estados da atividade são determinados pelo próprio sistema operacional, sujeitos ao uso de memória para cada processo e à prioridade de cada processo.

Você pode considerar o tamanho e a resolução de cada uma das imagens de bitmap usadas. Recomendo reduzir o tamanho, redimensionar para uma resolução mais baixa, consultar o design das galerias (uma pequena imagem PNG e uma imagem original).

Raju Gujarati
fonte
11

Esse código ajudará a carregar bitmap grande a partir 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;
    }
}
vinhedo
fonte
Bom, acha que seria melhor usar um Loader em vez de Async Task?
31412 Chrispix
Que tal Bitmap.Config.ARGB_565? Se a alta qualidade não for crítica.
Hamzeh Soboh