VM running out of memory while getting images from

2019-06-01 16:42发布

问题:

The problem that I'm addressing is known to be in devices with 16 mb of heap memory, and I need to solve it. I have threads getting images from a server (Big Images) and having them cached then displayed. The problem is that I run out of memory. To explain a little further I'm posting my log cat as well as some code:

public class Test extends Activity implements OnClickListener {
ImageView paint, wheels, shadow;

Button b;
int j = 2;
int i = 1;
String pathToWheels, pathToPaint, pathToShadow;
String stringAngle;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    MemoryInfo dbm = new MemoryInfo();
    Debug.getMemoryInfo(dbm);
    b = (Button) findViewById(R.id.button1);
    b.setOnClickListener(this);



    wheels = (ImageView) findViewById(R.id.imageView3);
    paint = (ImageView) findViewById(R.id.imageView2);
    shadow = (ImageView) findViewById(R.id.imageView1);



    while (i < 13) {

        stringAngle = Integer.toString(i);
        String pathToWheels = url;
        String pathToPaint = url;
        String pathToShadow = url;

        if (Cache.getCacheFile(pathToWheels) == null) {
            ImageDownloader downloader1 = new ImageDownloader(wheels);
            downloader1
                    .execute(url);
        } else if (Cache.getCacheFile(pathToShadow) == null) {
            ImageDownloader downloader2 = new ImageDownloader(shadow);
            downloader2
                    .execute(url);


        } else if (Cache.getCacheFile(pathToPaint) == null) {

            ImageDownloader downloader = new ImageDownloader(paint);
            downloader
                    .execute(url);


        }

        i++;


    }

}



@Override
protected void onPause() {
    super.onPause();
    Toast.makeText(getApplicationContext(),"Pause",
            Toast.LENGTH_SHORT).show();

}

@Override
public void onClick(View v) {


    if (v.getId() == R.id.button1) {
        if (j == 13) 
        {
            j = 1;  
        }
        String s = Integer.toString(j);
        wheels.setImageBitmap(Cache
                .getCacheFile(url));
        paint.setImageBitmap(Cache
                .getCacheFile(url));
    shadow.setImageBitmap(Cache
                .getCacheFile(url));
        j++;



    }
}

}

From my cache class:

public static void saveCacheFile(String cacheUri, Bitmap image) {
    if(!isCacheWritable()) return;
    Log.i("CACHE S",cacheUri);
    cache.saveCacheFile(cacheUri, image);

From my CacheStore class:

public Bitmap getCacheFile(String cacheUri) {
    if(bitmapMap.containsKey(cacheUri)) return (Bitmap)bitmapMap.get(cacheUri);

    if(!cacheMap.containsKey(cacheUri)) return null;
    String fileLocalName = cacheMap.get(cacheUri);
    File fullCacheDir = new     File(Environment.getExternalStorageDirectory().toString(),cacheDir);
    File fileUri = new File(fullCacheDir.toString(), fileLocalName);
    if(!fileUri.exists()) return null;
    Log.i("CACHE", "File "+cacheUri+" has been found in the Cache");
    Bitmap bm = BitmapFactory.decodeFile(fileUri.toString());
    Bitmap.createScaledBitmap(bm, 480, 320, true);
    bitmapMap.put(cacheUri, bm);
    return bm;
}

The app crashes at this line: Bitmap bm = BitmapFactory.decodeFile(fileUri.toString()); where I'm running out of memory.

My logcat:

    07-06 23:40:20.720: ERROR/dalvikvm-heap(1844): 691200-byte external allocation too large for this process.
07-06 23:40:20.720: ERROR/GraphicsJNI(1844): VM won't let us allocate 691200 bytes
07-06 23:40:20.770: DEBUG/skia(1844): --- decoder->decode returned false
07-06 23:40:20.790: DEBUG/AndroidRuntime(1844): Shutting down VM
07-06 23:40:20.830: WARN/dalvikvm(1844): threadid=1: thread exiting with uncaught exception (group=0x4001d800)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844): FATAL EXCEPTION: main
07-06 23:40:20.890: ERROR/AndroidRuntime(1844): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:271)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:296)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at com.cache.CacheStore.getCacheFile(CacheStore.java:117)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at com.cache.Cache.getCacheFile(Cache.java:38)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at com.cache.Test.onClick(Test.java:118)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.view.View.performClick(View.java:2408)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.view.View$PerformClick.run(View.java:8816)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.os.Handler.handleCallback(Handler.java:587)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.os.Handler.dispatchMessage(Handler.java:92)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.os.Looper.loop(Looper.java:123)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at android.app.ActivityThread.main(ActivityThread.java:4627)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at java.lang.reflect.Method.invokeNative(Native Method)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at java.lang.reflect.Method.invoke(Method.java:521)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
07-06 23:40:20.890: ERROR/AndroidRuntime(1844):     at dalvik.system.NativeStart.main(Native Method)

Any help would be greatly appreciated, Thanks for reading.

回答1:

You need to re-sample and scale your bitmaps prior to inserting them into the cache, inside your imagedownloader.

In the past I've had to detect the file size of the image prior to downloading it from the web. If it's too large I just use a default image.

Additionally, I also lowered the resolution of the saved image by adjusting the BitmapFactory Sampling size, and adjusted the size of the final image in your app. (this saved a lot of space for the cache).

See How to know the size of a file before downloading it?

//somewhere inside image downloader class...
{...}

BitmapFactory.Options options = new BitmapFactory.Options();

if(enableSampling)
    options.inSampleSize = 2;

returnBitmap = BitmapFactory.decodeStream(is, null, options);   

if(returnBitmap != null)
{

    if(scaleImage)
        returnBitmap = Bitmap.createScaledBitmap(returnBitmap, width, height, true);

    // insert the image into the cache
    imageCache.put(url, new SoftReference<Bitmap>(returnBitmap));


    // Check the ImageView for nulls
    if(imageView != null && callback != null){
        ImageDisplayer displayer = new ImageDisplayer(imageView, returnBitmap, parentView, tag);
        callback.onImageReceived(displayer);
    }

}

{...}

So far this has worked for me, and the check for INSANE file sizes has saved the app from crashing on numerous occasions, not to mention it can just skip downloaing a massive file in the background.



回答2:

Check out BitmapFactory OOM driving me nuts