Pergunta

Eu tenho uma visão de lista com um par de botões de imagem em cada linha. Quando você clique na linha lista, ele inicia uma nova atividade. Eu tive de construir meus próprios guias devido a um problema com o layout da câmera. A atividade que é lançado para o resultado é um mapa. Se eu clicar no meu botão para iniciar a visualização da imagem (carregar uma imagem a partir do cartão SD) o aplicativo retorna da parte de trás atividade para a atividade listview para o manipulador de resultado para relançar a minha nova atividade que nada mais é do que um widget de imagem.

A pré-visualização da imagem na exibição de lista está sendo feito com o cursor e ListAdapter. Isto torna muito simples, mas eu não sei como eu posso colocar uma imagem redimensionada (Ie Tamanho menor bit não pixel como o src para a imagem em tempo real. Então, eu só redimensionada a imagem que saiu a câmera do telefone.

O problema é que eu recebo um erro de falta de memória quando ele tenta voltar e re-lançamento da 2ª actividade.

  • Existe uma maneira eu posso construir o adaptador lista facilmente linha por linha, onde eu possa redimensionar on the fly ( mordeu sábio )?

Este seria preferível como eu também preciso fazer algumas alterações para as propriedades dos widgets / elementos em cada linha como eu sou incapaz de selecionar uma linha com o ecrã táctil por causa da questão de foco. ( Posso usar bola de rolo. )

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

Assim que eu desativei a imagem na exibição de lista funcionou muito bem novamente.

FYI: Esta é a forma como eu estava fazendo isso:

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.imagefilename é 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 

Eu 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
Foi útil?

Solução

classe A href="http://developer.android.com/training/index.html"> Formação " Exibindo mapas de bits eficiente ", oferece algumas grandes informações para compreender e lidar com o java.lang.OutOfMemoryError: bitmap size exceeds VM budget exceção ao carregar bitmaps.


Leia Bitmap Dimensões e Tipo

A classe BitmapFactory fornece vários métodos de descodificação (decodeByteArray(), decodeFile(), decodeResource(), etc.) para a criação de um Bitmap a partir de várias fontes. Escolha o método de decodificação apropriada mais baseado em sua fonte de dados de imagem. Estes métodos tentar alocar memória para o bitmap construído e, portanto, pode facilmente resultar em uma exceção OutOfMemory. Cada tipo de método de decodificação tem assinaturas adicionais que permitem especificar decodificação opções através da classe BitmapFactory.Options. Definir a propriedade inJustDecodeBounds para true enquanto decodificação evita a alocação de memória, retornando null para o objeto bitmap mas a definição outWidth, outHeight e outMimeType. Esta técnica permite que você leia as dimensões eo tipo de dados de 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 exceções java.lang.OutOfMemory, verificar as dimensões de um bitmap antes de decodificá-lo, a menos que você absolutamente confiar na fonte para lhe fornecer dados de imagem previsivelmente porte que se encaixa confortavelmente dentro da memória disponível.


Carregar uma versão reduzida na memória

Agora que as dimensões da imagem são conhecidos, eles podem ser usados ??para decidir se a imagem completa deve ser carregado na memória ou se uma versão subsampled devem ser carregados em seu lugar. Aqui estão alguns fatores a considerar:

  • uso de memória estimado de carregar a imagem completa na memória.
  • A quantidade de memória que está disposto a comprometer-se a colocar esta imagem dadas quaisquer outros requisitos de memória de sua aplicação.
  • Dimensões da ImageView alvo ou componente de interface que a imagem está a ser carregado na.
  • O 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 ele acabará por ser exibido em um pixel 128x96 em miniatura em uma ImageView.

Para dizer a decodificador para subamostra a imagem, carregar uma versão menor na memória, conjunto inSampleSize para true em seu objeto BitmapFactory.Options. Por exemplo, uma imagem com resolução 2048x1536 que é descodificado com um inSampleSize de 4 produz um mapa de bits de cerca de 512x384. Carregando este em usos de memória 0.75MB ao invés de 12MB para a imagem completa (assumindo uma configuração de bitmap de ARGB_8888). Aqui está um método para calcular um valor tamanho da amostra que é uma potência de dois baseado em uma largura alvo e altura:

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 : A potência de dois valor é calculado porque o decodificador usa um valor final por arredondamento para o poder mais próximo dos dois, de acordo com o inSampleSize documentação.

Para utilizar este método, primeiro decodificação com o conjunto inJustDecodeBounds para true, passar as opções através de e, em seguida, decodificação novamente utilizando o valor inSampleSize e conjunto inJustDecodeBounds novo 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);
}

Este método faz com que seja fácil de carregar um bitmap de tamanho arbitrariamente grande em um ImageView que exibe uma miniatura de 100x100 pixel, como mostrado no seguinte exemplo de código:

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

Você pode seguir um processo semelhante ao bitmaps decodificação de outras fontes, substituindo o método BitmapFactory.decode* apropriada conforme necessário.

Outras dicas

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

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

Esta opção inSampleSize reduz o consumo de memória.

Aqui está um método completo. Primeiro ele lê o tamanho da imagem sem decodificar o conteúdo propriamente dito. Em seguida, ele encontra o melhor valor inSampleSize, deve ser 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;
}

Eu fiz uma pequena melhora ao código de Fedor. Ele basicamente faz o mesmo, mas sem o (na minha opinião) feio enquanto loop e que sempre resulta em uma potência de dois. Kudos para Fedor para fazer a solução original, eu estava preso até que eu encontrei o dele, e então eu era capaz de fazer este:)

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

Eu venho de experiência iOS e eu estava frustrado ao descobrir um problema com algo tão básico como o carregamento e mostrando uma imagem. Afinal, todo mundo que está tendo esse problema está tentando exibir imagens de tamanho razoável. De qualquer forma, aqui estão as duas mudanças que fixa o meu problema (e feitas meu aplicativo muito sensível).

1) Toda vez que você faz BitmapFactory.decodeXYZ(), certifique-se de passar em um BitmapFactory.Options com o conjunto inPurgeable para true (e de preferência com inInputShareable também definido para true).

2) NUNCA use Bitmap.createBitmap(width, height, Config.ARGB_8888). Quero dizer NUNCA! Eu nunca tive essa coisa não levantar erro de memória após alguns passes. Nenhuma quantidade de recycle(), System.gc(), qualquer que seja ajudado. É sempre levantada exceção. A única outra maneira que realmente funciona é ter uma imagem fictícia em suas drawables (ou outro bitmap que você decodificado usando o passo 1 acima), rescale que para o que quiser, em seguida, manipular o bitmap resultante (como passá-lo para uma lona 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 a força bruta método de criar, então, pelo menos Config.ARGB_4444 passagem.

Este é quase garantida para poupar horas se não dias. Tudo o que falar sobre dimensionamento da imagem, etc. realmente não funciona (a menos que você considere ficando tamanho errado ou imagem degradada uma solução).

É um conhecido bug , não é por causa da grande arquivos. Desde caches Android os Drawables, ele vai para fora da memória depois de usar poucas imagens. Mas eu encontrei uma maneira alternativa para ele, saltando do sistema de cache padrão android.

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

Eu tive esse mesmo problema e resolveu-lo, evitando as funções BitmapFactory.decodeStream ou decodeFile e BitmapFactory.decodeFileDescriptor vez usado

aparência decodeFileDescriptor como ele chama diferentes métodos nativos do que a decodeStream / decodeFile.

De qualquer forma, o que funcionou foi isso (note que eu adicionei algumas opções como alguns tinham acima, mas não é isso que fez a diferença. O que é crítico é 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 utilizada na decodeStream / decodeFile. Eu confirmaram que um método nativo diferente é chamado quando usando decodeFileDescriptor. Também o que eu li é "que as imagens (bitmaps) não são atribuídos de forma Java padrão, mas através de chamadas nativas; as alocações são feitas fora da pilha virtual, mas são contado contra ela! "

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

Eu fiz uma aplicativo para causar intencionalmente OutOfMemoryError, e uso de memória do monitor .

Depois que eu fiz um monte de experimentos com este App, eu tenho as seguintes conclusões:

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

  1. Bitmap é armazenado na pilha nativa, mas ele vai ficar lixo coletado automaticamente, chamando reciclagem () é desnecessária.

  2. Se {VM tamanho da pilha} + {alocado memória heap nativa}> = {VM montão limite de tamanho para o dispositivo}, e você está tentando criar bitmap, OOM será lançada.

    AVISO:. VM MONTÃO TAMANHO é contado em vez de VM memória alocada

  3. tamanho VM Heap nunca vai encolher após adulta, mesmo se a memória VM alocada é shrinked.

  4. Então, você tem que manter o pico de memória VM o mais baixo possível para manter VM Heap Tamanho de crescer muito grande para economizar memória disponível para bitmaps.

  5. manualmente chamada System.gc () é sem sentido, o sistema irá chamá-lo primeiro antes de tentar aumentar o tamanho da pilha.

  6. Native Heap Size nunca vai encolher muito, mas não é contado para OOM, por isso não precisa se preocupar com isso.

Então, vamos falar sobre o SDK Inicia de Honey Comb.

  1. Bitmap é armazenado em VM pilha, a memória nativa não é contado para OOM.

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

  3. Assim você tem mais memória disponível para criar bitmap com o mesmo limite de tamanho da pilha, OOM é menos provável de ser lançada.

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

Você pode ver-se na App. Se uma atividade executada uma AsyncTask que ainda estava funcionando após a atividade foi destruída, a atividade não vai ficar lixo coletado até o acabamento AsyncTask.

Isto porque AsyncTask é uma instância de uma classe interna anônima, que contém uma referência da actividade.

Chamando AsyncTask.cancel (true) não vai parar a execução se a tarefa está bloqueada em uma operação IO na discussão de fundo.

Callbacks são classes internas anônimas também, por isso, se uma instância estática em seu projeto prende-los e não liberá-los, a memória seria vazado.

Se você agendou uma tarefa repetir ou retardada, por exemplo, um temporizador, e você não chame cancelar () e expurgo () in onPause (), a memória seria vazado.

Eu vi um monte de perguntas sobre exceções OOM e caching recentemente. O guia do desenvolvedor tem um artigo muito bom sobre isso, mas alguns tende a falhar na implementação de uma forma adequada.

Devido a isso eu escrevi um aplicativo de exemplo que demonstra o cache em um ambiente Android. Esta implementação ainda não tenha obtido uma OOM.

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

Requisitos:

  • API Android 2.1 ou superior (eu simplesmente não conseguia obter a memória disponível para um aplicativo no API 1.6 - que é o único pedaço de código que não funciona na API 1.6)
  • Android suporte pacote

Captura de tela

características:

  • Mantém o cache se houver uma mudança de orientação , usando um singleton
  • Use um oitavo da memória de aplicativo atribuído à cache (modificar se quiser)
  • bitmaps grandes recebe escalado (você pode definir o máximo de pixels que você deseja permitir)
  • Controles que há uma conexão com a internet disponível antes de baixar os mapas de bits
  • garante que você está apenas instanciar uma tarefa por linha
  • Se você está jogando o ListView afastado, ele simplesmente não vai baixar os bitmaps entre

Isto não inclui:

    caching
  • Disk. Isso deve ser fácil de implementar qualquer maneira - basta apontar para uma tarefa diferente que agarra os bitmaps a partir do disco

Exemplo de código:

As imagens que estão sendo baixados são imagens (75x75) de Flickr. No entanto, colocar qualquer urls imagem que você quer ser processado, eo aplicativo irá escalá-lo para baixo se ele excede o máximo. Nesta aplicação as urls são simplesmente em uma matriz String.

O LruCache tem uma boa maneira de lidar com bitmaps. No entanto, nesta aplicação eu coloquei uma instância de um LruCache dentro de outra classe de cache que eu criei a fim de obter a aplicação mais viável.

material crítico de Cache.java (o método loadBitmap() é 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 deve precisar de editar qualquer coisa no arquivo Cache.java a menos que você deseja 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 muitas vezes. Ele normalmente não é uma boa idéia para download de imagens lá se não implementaram um cheque que assegurar-nos de que não vamos começar uma quantidade infinita de fios por linha. cheques Cache.java se o rowObject.mBitmapUrl já está em uma tarefa e se for, ele não vai começar outra. Portanto, nós provavelmente não estão excedendo a restrição fila de trabalho do pool AsyncTask.

Download:

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


Últimas palavras:

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

Há apenas um cenário possível, onde eu posso imaginar que o OOM irá aparecer, e que é se baixar muitos realmente grandes imagens, e, antes de serem escalados e colocados em cache, irá simultaneamente ocupam mais memória e causar um OOM. Mas isso não é even uma situação ideal de qualquer maneira e ele provavelmente não será possível resolver de uma forma mais viável.

Informar erros nos comentários! : -)

Eu fiz o seguinte para levar a imagem e redimensioná-lo na mosca. Espero que isso ajude

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    

Parece que este é um problema muito longa duração, com um monte de explicações diferentes. I levou conselho dos dois mais comuns apresentados respostas aqui, mas nem um deles resolvido meus problemas da VM alegando que não podiam pagar os bytes para realizar a decodificação parte do processo. Após alguma escavação Aprendi que o verdadeiro problema aqui é o processo de decodificação de tirar a NATIVO heap.

Veja aqui: BitmapFactory OOM dirigindo-me louco

Que levam-me a outro segmento de discussão onde eu encontrei mais algumas soluções para este problema. Uma delas é callSystem.gc(); manualmente após a sua imagem é exibida. Mas que na verdade faz com que seu uso aplicativo mais memória, em um esforço para reduzir a pilha nativa. A melhor solução a partir da libertação de 2,0 (rosca) é usar a opção BitmapFactory "inPurgeable". Então, eu simplesmente adicionado o2.inPurgeable=true; logo após o2.inSampleSize=scale;.

Mais sobre o assunto aqui: é o limite da pilha de memória única 6M?

Agora, tendo dito tudo isso, eu sou um burro completo com Java e Android também. Então, se você acha que isso é uma péssima maneira de resolver este problema, você provavelmente está certo. ;-) Mas isso tem trabalhado maravilhas para mim, e eu achava impossível executar a VM fora do cache de pilha agora. A única desvantagem que eu posso encontrar é que você está destruindo sua imagem tirada em cache. Que significa que se você voltar para a direita para essa imagem, você está redesenhando-lo cada vez. No caso de como meu aplicativo funciona, isso não é realmente um problema. Sua milhagem pode variar.

infelizmente Se nenhum dos acima funciona, então Adicione isto a sua manifesto arquivo. Dentro aplicativo tag

 <application
         android:largeHeap="true"

Use este bitmap.recycle(); Isso ajuda sem qualquer problema de qualidade de imagem.

Eu tenho uma solução muito mais eficaz do que não precisa de escala de qualquer espécie. Simplesmente decodificar seu bitmap apenas uma vez e, em seguida, armazená-lo em um mapa contra o seu nome. Em seguida, basta recuperar o bitmap com o nome e defini-lo no ImageView. Não há nada mais que precisa ser feito.

Este vai funcionar porque os dados binários real do bitmap decodificado não é armazenada dentro do Dalvik VM heap. Ele é armazenado externamente. Assim, cada vez que você decodificar um bitmap, ele aloca fora memória de VM pilha que nunca é recuperado pelo GC

Para ajudá-lo a avaliar melhor isso, imagine que você tem mantido imagem ur na pasta drawable. Você só obter a imagem fazendo uma getResources (). GetDrwable (R.drawable.). Este não irá decodificar sua imagem cada vez mas re-utilizar um toda instância já decodificado você chamá-lo. Assim, em essência, ele é armazenado em cache.

Agora, já que sua imagem está em um algum lugar do arquivo (ou pode até mesmo ser proveniente de um servidor externo), é de sua responsabilidade para armazenar em cache a instância bitmap decodificado para ser reutilizado em qualquer lugar é necessário.

Espero que isso ajude.

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

Há duas questões aqui ....

  • memória Bitmap não é na pilha VM mas sim na pilha nativa - veja BitmapFactory OOM me deixando louco
  • A coleta de lixo para o heap nativa é mais preguiçoso do que a pilha VM - então você precisa ser bastante agressivo sobre fazer bitmap.recycle e bitmap = null cada vez que você passar por uma atividade onPause ou onDestroy

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

Nenhuma das respostas acima funcionou para mim, mas eu vim com uma solução terrivelmente feia que resolveu o problema. Eu adicionei uma imagem muito pequena, de 1x1 pixel para o meu projeto como um recurso, e carregá-la no meu ImageView antes de chamar para coleta de lixo. Eu acho que pode ser que o ImageView não estava liberando o Bitmap, então GC não pegou. É feio, mas parece estar a trabalhar para agora.

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.

grandes respostas aqui, mas eu queria uma classe totalmente utilizável para resolver este problema .. então eu fiz um.

Aqui está a minha classe BitmapHelper que é OutOfMemoryError prova: -)

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

}

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 este é em C # MonoDroid. você pode facilmente mudar o caminho da imagem. o importante aqui são as opções a ser definido.

Este parece ser o local apropriado para partilhar a minha classe de utilitário para carregar e processar imagens com a comunidade, você está convidado a 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;
    }
}

Em um dos meu aplicativo eu preciso tirar uma foto a partir Camera/Gallery. Se imagem clique do usuário da câmera (pode ser 2MP, 5MP ou 8MP), tamanho da imagem varia de kBs para MBs. Se o tamanho da imagem é menos (ou até 1-2MB) acima código funcionando bem, mas se eu tiver uma imagem de tamanho acima de 4 MB ou 5MB depois OOM vem em quadro: (

então, tenho trabalhado para resolver este problema e, finalmente, eu fiz a seguir melhoria do Fedor (todo o crédito para Fedor para fazer tal solução agradável) Código:)

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 vai ajudar os amigos enfrentando o mesmo problema!

Para mais consulte este

Eu apenas corri para este problema de alguns minutos atrás. Eu resolvi-lo fazendo um trabalho melhor em gerir o meu adaptador de listview. Eu pensei que era um problema com as centenas de 50x50px imagens que eu estava usando, Acontece que eu estava tentando inflar a minha exibição personalizada a cada vez estava sendo mostrado a linha. Simplesmente, testando para ver se a linha tinha sido inflado I eliminado esse erro, e eu estou usando centenas de bitmaps. Esta é realmente para um Spinner, mas o adaptador de base funciona tudo a mesma coisa para um ListView. Esta simples correção também melhorou muito 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);
    }
...

Eu passei o dia inteiro testar essas soluções e a única coisa que funcionou para mim é o acima abordagens para obter a imagem e manualmente chamar o GC, que eu sei que não é suposto ser necessário, mas é a única coisa que funcionou quando eu coloco meu aplicativo sob carga pesada testar a comutação entre as atividades. Meu aplicativo tem uma lista de imagens em miniatura numa lista em (digamos atividade A) e quando você clica em uma dessas imagens que você leva para outra atividade (digamos atividade B), que mostra uma imagem principal para esse item. Quando eu alternar entre as duas atividades, eu acabaria por obter o erro OOM eo app forçaria perto.

Quando eu iria ficar a meio caminho para baixo a listview que deixaria de funcionar.

Agora, quando eu implementar o seguinte em atividade B, posso passar por todo o listview com nenhum problema e continue indo e indo e indo ... e sua abundância rápido.

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}

Este problema só acontece em emuladores Android. Eu também enfrentou essa questão em um emulador, mas quando eu fiz em um dispositivo, em seguida, ele funcionou bem.

Então, por favor, verifique em um dispositivo. Pode ser executado em dispositivo.

Meus 2 centavos: i resolvido meus erros OOM com bitmaps por:

a) escala minhas imagens por um fator de 2

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

usar estes códigos para cada imagem no seleto sdcard ou drewable ao objeto convertido 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;

usar o seu trajeto instend de ImageData_Path.get (img_pos) .getPath () .

Todas as soluções aqui requerem a criação de um IMAGE_MAX_SIZE. Isso limita os dispositivos com hardware mais poderoso e se o tamanho da imagem é muito baixa que olha feio na tela do HD.

eu saí com 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.

A essência do que é para calcular o máximo de memória alocada para o aplicativo em um dispositivo particular, em seguida, definir a escala para ser mais baixo 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;
}

Eu defini o máximo de memória usada por este bitmap a ser 25% da memória máxima atribuída, você pode precisar de ajustar isso para as suas necessidades e garantir que este bitmap é limpo e não ficar na memória quando você terminou usando isso. Normalmente eu uso este código para executar a rotação da imagem (fonte e bitmap destino) para as minhas necessidades de aplicativos para carregar 2 bitmaps em memória ao mesmo tempo, e 25% dá-me um bom tampão sem ficar sem memória ao realizar a rotação da imagem.

Espero que isso ajude alguém lá fora ..

Tal OutofMemoryException não pode ser totalmente resolvido chamando o System.gc() e assim por diante.

Ao se referir ao Atividade do Ciclo de Vida

Os Estados Atividade são determinados pelo próprio sujeito ao uso de memória para cada processo ea prioridade de cada processo OS.

Você pode considerar o tamanho ea resolução de cada uma das imagens bitmap usadas. Eu recomendo para reduzir o tamanho, resample baixar a resolução, consulte o projeto de galerias (uma pequena PNG imagem, e uma imagem original.)

tamanho Geralmente android montão dispositivo é apenas 16MB (varia de dispositivo / OS ver post Heap Tamanhos ), se você estiver carregar as imagens e cruza o tamanho de 16 MB, ele vai jogar fora de exceção de memória, em vez de usar o Bitmap para, carregar imagens a partir do cartão SD ou de recursos ou mesmo de tentativa de rede para usando getImageUri , Carregando bitmap requerem mais memória, ou você pode definir bitmap como nulo se o seu trabalho feito com que bitmap.

Este código vai ajudar a carregar bitmap grande 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;
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top