Android Picasso ImageView - Out of Memory Exceptio

2019-02-15 12:11发布

问题:

I am new to Android development. I'm trying to download images via HTTP and storing them inside of ImageViews or Drawables. In this example, I am using ImageViews. Initially, I used an AsyncTask to download these images (roughly ~500KB each), however I decided to use Picasso since I've read it is more reliable.

In the code below, I have 20 ImageViews. Each of the image url's is an image that is roughly 400KB. However, after loading all of them, I've noticed my getUsedMem() is greater than 100MB. I'm not sure what is causing this memory leak.

Could you please assist me?

public class Example extends Activity {
    public long getUsedMem()
    {
        long freeSize = 0L;
        long totalSize = 0L;
        long usedSize = -1L;
        try {
            Runtime info = Runtime.getRuntime();
            freeSize = info.freeMemory();
            totalSize = info.totalMemory();
            usedSize = totalSize - freeSize;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return usedSize;
    }

    public void concatLogMessages(String msg)
    {
        TextView tv = (TextView) findViewById(R.id.textView1);
        CharSequence cs = tv.getText();

        tv.setText(cs + "\n" + 
                   "Image #: " + msg + "\n" +
                   "Used Mem: " + getUsedMem()
                   );
    }

    public void loadImage(final ImageView target, String url, final int num)    {
        Picasso.with(this).load(url).into(target, new EmptyCallback(){
            @Override
            public void onError() {
                Example.this.concatLogMessages("Picasso onError");
                super.onError();
            }

            @SuppressLint("NewApi") @Override
            public void onSuccess() {
                Example.this.concatLogMessages("Picasso onSuccess " + num);
                super.onSuccess();
            }
        });
    }

    @SuppressLint("NewApi")
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        drawables = new ArrayList<Drawable>();

        // Check Memory Before Everything
        concatLogMessages("0");

        // Imageview to show
        ImageView image1 = (ImageView) findViewById(R.id.imageView1);
        ImageView image2 = (ImageView) findViewById(R.id.imageView2);
        ImageView image3 = (ImageView) findViewById(R.id.imageView3);
        ImageView image4 = (ImageView) findViewById(R.id.imageView4);
        ImageView image5 = (ImageView) findViewById(R.id.imageView5);
        ImageView image6 = (ImageView) findViewById(R.id.imageView6);
        ImageView image7 = (ImageView) findViewById(R.id.imageView7);
        ImageView image8 = (ImageView) findViewById(R.id.imageView8);
        ImageView image9 = (ImageView) findViewById(R.id.imageView9);
        ImageView image10 = (ImageView) findViewById(R.id.imageView10);
        ImageView image11 = (ImageView) findViewById(R.id.imageView11);
        ImageView image12 = (ImageView) findViewById(R.id.imageView12);
        ImageView image13 = (ImageView) findViewById(R.id.imageView13);
        ImageView image14 = (ImageView) findViewById(R.id.imageView14);
        ImageView image15 = (ImageView) findViewById(R.id.imageView15);
        ImageView image16 = (ImageView) findViewById(R.id.imageView16);
        ImageView image17 = (ImageView) findViewById(R.id.imageView17);
        ImageView image18 = (ImageView) findViewById(R.id.imageView18);
        ImageView image19 = (ImageView) findViewById(R.id.imageView19);
        ImageView image20 = (ImageView) findViewById(R.id.imageView20);

        /* Picasso */
        ImageView target = (ImageView) findViewById(R.id.picassoImageView);

        loadImage(target, image_url1, 0);

        // Test 1-20 image views...
        loadImage(image1, R.String.image_url1, 1);
        loadImage(image2, R.String.image_url2, 2);
        loadImage(image3, R.String.image_url3, 3);
        loadImage(image4, R.String.image_url4, 4);
        loadImage(image5, R.String.image_url5, 5);   
        loadImage(image6, R.String.image_url6, 6);
        loadImage(image7, R.String.image_url7, 7);
        loadImage(image8, R.String.image_url8, 8);
        loadImage(image9, R.String.image_url9, 9);
        loadImage(image10, R.String.image_url10, 10);
        loadImage(image11, R.String.image_url11, 11);
        loadImage(image12, R.String.image_url12, 12);
        loadImage(image13, R.String.image_url13, 13);
        loadImage(image14, R.String.image_url14, 14);
        loadImage(image15, R.String.image_url15, 15);
        loadImage(image16, R.String.image_url16, 16);
        loadImage(image17, R.String.image_url17, 17);
        loadImage(image18, R.String.image_url18, 18);
        loadImage(image19, R.String.image_url19, 19);
        loadImage(image20, R.String.image_url20, 20);
    }
}

回答1:

I don't think it's a leak.

When Android "unwraps" your image (i.e. decodes it to bitmap), it will use 4 bytes per pixel. Count the number of pixels, multiply that by 4 and then by 20 (number of your images) and you'll probably get close to the 100mb figure. For instance, if your images have 1,000,000 pixel resolution, it would be 1,000,000 x 4 x 20 = 80mb.

You should not load all these images at once. Use some kind of LRU cache or similar (or alternatively use Universal Image Loader library which handles caching for you) and only load your bitmaps when you need them.

I highly recommend reading this article and following its advices very closely: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html



回答2:

I'm using this for a lot of images and it works fine

private class LoadImage extends AsyncTask<String, String, Bitmap> {

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        pDialog = new ProgressDialog((Main)context);
        pDialog.setMessage("Loading Image ....");
        pDialog.show();
    }
    protected Bitmap doInBackground(String... args) {
            Log.i("doInBack 1","length = 1 ");
            try {
                Bitmap positivo = BitmapFactory.decodeStream((InputStream)new URL(args[0]).getContent());
            } catch (Exception e) {
                e.printStackTrace();
            }

        return positivo;
    }
    protected void onPostExecute(Bitmap image) {
        if(image != null){
            //*bitmap is the bitmap u change each time
            bitmap = image;
            invalidate();
            pDialog.dismiss();
        }
    }

how to call this?, write this when u want change the image just with the url

new LoadImage().execute("https://yourimage.jpg");