galeria - carregar imagem imageview android




Estranho problema de falta de memória ao carregar uma imagem em um objeto Bitmap (20)

É um bug conhecido , não é por causa de arquivos grandes. Como o Android armazena os Drawables, ele 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);
}

Eu tenho uma visão de lista com um par de botões de imagem em cada linha. Quando você clica na linha da lista, ela inicia uma nova atividade. Eu tive que construir minhas próprias abas por causa de um problema com o layout da câmera. A atividade que é iniciada para o resultado é um mapa. Se eu clicar no botão para iniciar a visualização da imagem (carregar uma imagem do cartão SD), o aplicativo retorna da atividade de volta para a atividade listview para o manipulador de resultados para relançar 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 o ListAdapter . Isso faz com que seja bem 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 src para o botão de imagem. Simplesmente redimensionei a imagem que saiu da câmera do telefone.

A questão é que eu recebo um erro de falta de memória quando ele tenta voltar e relançar a segunda atividade.

  • Existe uma maneira que eu possa construir o adaptador de lista facilmente linha por linha, onde eu posso redimensionar em tempo real ( bit wise )?

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 sensível ao toque devido ao problema de foco. ( Eu posso usar bola de rolo. )

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

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

FYI: É assim que 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

A classe Android Training , " Exibindo bitmaps com eficiência ", oferece algumas ótimas informações para entender e lidar com a exceção java.lang.OutOfMemoryError: bitmap size exceeds VM budget ao carregar os Bitmaps.

Ler dimensões e tipo de bitmap

A classe BitmapFactory fornece vários métodos de decodificação ( decodeByteArray() , decodeFile() , decodeResource() , etc.) para criar um Bitmap de várias origens. Escolha o método de decodificação mais adequado com base na sua fonte de dados de imagem. Esses métodos tentam alocar memória para o bitmap construído e, portanto, podem facilmente resultar em uma exceção OutOfMemory . Cada tipo de método de decodificação possui assinaturas adicionais que permitem especificar opções de decodificação por meio da classe BitmapFactory.Options . Definir a propriedade inJustDecodeBounds como true ao decodificar evita a alocação de memória, retornando null para o objeto de bitmap, mas definindo outWidth , outHeight e outMimeType . Essa técnica permite ler as dimensões e o tipo dos dados da imagem antes da construção (e da 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 , 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 se ajustem confortavelmente à 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 subamostrada deve ser carregada. Aqui estão alguns fatores a serem considerados:

  • Estimativa de uso de memória ao carregar a imagem completa na memória.
  • A quantidade de memória que você deseja se comprometer a carregar essa imagem, considerando quaisquer outros requisitos de memória do seu aplicativo.
  • Dimensões do ImageView de destino ou componente de interface do usuário 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 um ImageView .

Para informar ao decodificador para subamostrar a imagem, carregando uma versão menor na memória, defina inSampleSize como true em seu objeto BitmapFactory.Options . Por exemplo, uma imagem com resolução 2048 inSampleSize 1536 decodificada com um inSampleSize de 4 produz um bitmap de aproximadamente 512 x 384. Carregar isso na memória usa 0.75MB 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 em uma largura e altura de 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 baixo até a potência mais próxima de dois, conforme a documentação do inSampleSize .

Para usar esse método, primeiro decodifique com inJustDecodeBounds definido como true , passe as opções e decodifique novamente usando o novo valor inJustDecodeBounds e inJustDecodeBounds definido como 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 ImageView que exibe uma miniatura de 100x100 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 método BitmapFactory.decode* apropriado conforme necessário.


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

 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 tenho visto muitas perguntas sobre exceções e armazenamento em cache do OOM ultimamente. O guia do desenvolvedor tem um artigo realmente bom sobre isso, mas alguns tendem a falhar em implementá-lo de maneira adequada.

Por causa disso eu escrevi um aplicativo de exemplo que demonstra o armazenamento em cache em um ambiente Android. Esta implementação ainda não obteve uma OOM.

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

Requisitos:

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

Características:

  • Retém o cache se houver uma mudança de orientação , usando um singleton
  • Use um oitavo da memória do aplicativo atribuído ao cache (modifique se quiser)
  • Os bitmaps grandes são dimensionados (você pode definir o máximo de pixels que deseja permitir)
  • Controla que há uma conexão com a Internet disponível antes de baixar os bitmaps
  • Certifica-se de que você está apenas instanciando uma tarefa por linha
  • Se você está arremessando o ListView , simplesmente não baixará os bitmaps entre

Isso não inclui:

  • Armazenamento em cache de disco Isso deve ser fácil de implementar de qualquer maneira - 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 de imagem que você deseja processar e o aplicativo será redimensionado se exceder o máximo. Nesta aplicação, os URLs são simplesmente em um array String .

O LruCache tem uma boa maneira de lidar com bitmaps. No entanto, neste aplicativo 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.

O material crítico de loadBitmap() 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 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 idéia baixar imagens lá, se não tivermos implementado um cheque que nos garanta que não iniciaremos uma quantidade infinita de threads por linha. Cache.java verifica se o rowObject.mBitmapUrljá está em uma tarefa e, se estiver, não iniciará outro. Portanto, é mais provável que não excedamos a restrição da fila de trabalho do AsyncTaskpool.

Download:

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, 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 imagens que contêm imagens com qualidade HD. O único gargalo é que leva mais tempo para baixar.

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

Reportar erros nos comentários! :-)


Eu venho de experiência iOS e fiquei frustrado ao descobrir um problema com algo tão básico como carregar e mostrar uma imagem. Afinal, todos que estão tendo esse problema estão tentando exibir imagens de tamanho razoável. De qualquer forma, aqui estão as duas alterações que corrigiram meu problema (e tornaram meu aplicativo muito responsivo).

1) Toda vez que você fizer BitmapFactory.decodeXYZ() , passe em BitmapFactory.Options com inPurgeable definido como true (e preferencialmente com inInputShareable também configurado como true ).

2) NUNCA use Bitmap.createBitmap(width, height, Config.ARGB_8888) . Eu quero dizer NUNCA! Eu nunca tive essa coisa não levantar erro de memória depois de alguns passes. Nenhuma quantidade de recycle() , System.gc() , o que ajudou. Sempre levantou exceção. A única outra maneira que realmente funciona é ter uma imagem fictícia em seus drawables (ou outro Bitmap que você decodificou usando o passo 1 acima), redimensionar isso para o que você quiser, então manipular o Bitmap resultante (como passá-lo para um Canvas para mais diversão). Então, o que você deve usar é: 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 .

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


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 opção inSampleSize reduz o consumo de memória.

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

Esse código ajudará a carregar um 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;
    }
}

Este parece ser o lugar apropriado para compartilhar 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://.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;
    }
}

Há duas questões aqui....

  • A memória Bitmap não está no heap da VM, mas sim no heap nativo - veja a OVM BitmapFactory me deixando louca
  • A coleta de lixo para o heap nativo é mais preguiçosa do que o heap VM - então você precisa ser bastante agressivo ao fazer bitmap.recycle e bitmap = null toda vez que passar por um onPause ou onDestroy de Activity

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.


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

a) dimensionando minhas imagens por um fator de 2

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


use este código para cada imagem na seleção de SdCard ou desenhável para converter objeto 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 seu caminho de imagem instend of ImageData_Path.get (img_pos) .getPath () .


Em uma das minhas aplicações eu preciso tirar uma foto de Camera/Gallery. Se o usuário clicar em imagem da câmera (pode ser 2MP, 5MP ou 8MP), o tamanho da imagem varia de kBs para MBs. Se o tamanho da imagem é menor (ou até 1-2MB) acima do código funcionando bem, mas se eu tiver imagem de tamanho acima de 4MB ou 5MB, então OOMvem no quadro :(

então eu trabalhei para resolver esse problema e finalmente eu fiz a melhoria abaixo para Fedor (todo o crédito para Fedor para fazer uma solução tão boa) 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 ajude os amigos que enfrentam o mesmo problema!

para mais por favor consulte this


Esse problema ocorre apenas em emuladores do Android. Eu também enfrentei esse problema em um emulador, mas quando eu chequei em um dispositivo, então funcionou bem.

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


Eu tenho uma solução muito mais eficaz que não precisa de qualquer tipo de escala. Simplesmente decodifique seu bitmap apenas uma vez e, em seguida, armazene-o em um mapa em relação ao seu nome. Em seguida, basta recuperar o bitmap em relação ao nome e defini-lo no ImageView. Não há mais nada que precise ser feito.

Isso funcionará porque os dados binários reais do bitmap decodificado não são armazenados no heap da VM dalvik. É 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ê manteve sua imagem na pasta drawable. Você acabou de obter a imagem fazendo um getResources (). GetDrwable (R.drawable.). Isto 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 até ser proveniente de um servidor externo), é sua responsabilidade armazenar em cache a instância de bitmap decodificada para ser reutilizada em qualquer local onde seja necessária.

Espero que isto ajude.


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 é 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 [email protected]
            // 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;
    }

}

Parece que este é um problema muito longo, com muitas explicações diferentes. Tomei o conselho das duas respostas apresentadas mais comuns aqui, mas nenhuma delas resolveu meus problemas com a VM, alegando que ela não podia permitir que os bytes executassem a parte de decodificação do processo. Depois de algumas escavações, aprendi que o problema real aqui é o processo de decodificação que tira o heap NATIVE .

Veja aqui: BitmapFactory OOM me deixando louco

Isso me levou a outra discussão onde encontrei mais algumas soluções para esse problema. Uma é ligar System.gc();manualmente depois que sua imagem é exibida. Mas isso realmente faz seu aplicativo usar mais memória, em um esforço para reduzir o heap nativo. 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 apóso2.inSampleSize=scale; .

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

Agora, tendo dito tudo isso, eu sou um completo burro com Java e Android também. Então, se você acha que essa é uma maneira terrível de resolver esse problema, provavelmente está certo. ;-) Mas isso tem funcionado maravilhas para mim, e eu 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 para a imagem, estará redesenhando cada vez. No caso de como meu aplicativo funciona, isso não é realmente um problema. Sua milhagem pode variar.


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, o que 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 visualização de lista em (digamos, atividade A) e quando você clica em uma dessas imagens, leva você a 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 forçava o fechamento.

Quando eu chegava na metade do listview, ele caía.

Agora, quando eu implementar o seguinte na atividade B, eu posso passar por 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();  
}

Tamanho geralmente android montão dispositivo é apenas 16MB (varia de dispositivo / OS ver post tamanhos de heap ), se você estiver carregando 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, carregamento imagens do cartão SD ou de recursos ou até mesmo da rede tentam usar o getImageUri , o carregamento do bitmap requer mais memória, ou você pode definir o bitmap para null se o seu trabalho for feito com esse bitmap.


Todas as soluções aqui exigem configuração de IMAGE_MAX_SIZE. Isso limita os dispositivos com hardware mais potente e, se o tamanho da imagem for muito baixo, ficará 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 potente é usado.

A essência disso é calcular a memória máxima alocada para o aplicativo em um determinado dispositivo e, em seguida, definir a escala como a mais baixa 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 defino a memória máxima usada por este bitmap para ser 25% da memória máxima alocada, talvez seja necessário ajustar isso às suas necessidades e certifique-se de que esse bitmap esteja limpo e não fique na memória quando terminar de usá-lo. Normalmente, eu uso esse código para executar rotação de imagem (bitmap de origem e destino) para que meu aplicativo carregue 2 bitmaps na memória ao mesmo tempo e 25% me dê um bom buffer sem ficar sem memória ao executar a rotação de imagem.

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





android-bitmap