Forcing Portrait mode OOM bitmap resize

880 views Asked by At

Whenever I force portrait mode in onCreate

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

I get an error

What does Portrait mode have to do with OOM VM budget on image scale?

private void scaleFrom(BmpWrap image, Bitmap bmp)
    {
      if (image.bmp != null && image.bmp != bmp) {
        image.bmp.recycle();
      }

      if (mDisplayScale > 0.99999 && mDisplayScale < 1.00001) {
        image.bmp = bmp;
        return;
      }
      int dstWidth = (int)(bmp.getWidth() * mDisplayScale);
      int dstHeight = (int)(bmp.getHeight() * mDisplayScale);
      image.bmp = Bitmap.createScaledBitmap(bmp, dstWidth, dstHeight, true);
    }

    private void resizeBitmaps()
    {

      scaleFrom(mBackground, mBackgroundOrig);
      for (int i = 0; i < mBOrig.length; i++) {
        scaleFrom(mB[i], mBOrig[i]);
      }
      for (int i = 0; i < mBlind.length; i++) {
        scaleFrom(mBlind[i], mBlindOrig[i]);
      }
      for (int i = 0; i < mFrozen.length; i++) {
        scaleFrom(mFrozen[i], mFrozenOrig[i]);
      }
      for (int i = 0; i < mTargeted.length; i++) {
        scaleFrom(mTargeted[i], mTargetedOrig[i]);
      }
      scaleFrom(mBlink, mBlinkOrig);
      scaleFrom(mWon, mWonOrig);
      scaleFrom(mLost, mLostOrig);

      mImagesReady = true;
    }

java.lang.OutOfMemoryError: bitmap size exceeds VM budget
at android.graphics.Bitmap.nativeCreate(Native Method)
at android.graphics.Bitmap.createBitmap(Bitmap.java:498)
at android.graphics.Bitmap.createBitmap(Bitmap.java:465)
at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:370)
at com.company.app.View$hread.scaleFrom(View.java:313)
at com.company.app.View$hread.resizeBitmaps(View.java:337)
at com.company.app.View$hread.setSurfaceSize(View.java:480)
at com.company.app.View.surfaceChanged(View.java:905)
at android.view.SurfaceView.updateWindow(SurfaceView.java:538)
at android.view.SurfaceView.dispatchDraw(SurfaceView.java:339)
at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
at android.view.ViewGroup.drawChild(ViewGroup.java:1638)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
at android.view.View.draw(View.java:6745)
at android.widget.FrameLayout.draw(FrameLayout.java:352)
at android.view.ViewGroup.drawChild(ViewGroup.java:1640)
at android.view.ViewGroup.dispatchDraw(ViewGroup.java:1367)
at android.view.View.draw(View.java:6745)
at android.widget.FrameLayout.draw(FrameLayout.java:352)
at com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:1891)
at android.view.ViewRoot.draw(ViewRoot.java:1416)
at android.view.ViewRoot.performTraversals(ViewRoot.java:1172)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1736)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:143)
at android.app.ActivityThread.main(ActivityThread.java:4701)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)
2

There are 2 answers

11
Yahel On

Whenever you do an orientation change, the app is reloaded entirely except for static properties. (read here : http://developer.android.com/reference/android/app/Activity.html)

This means that all the bitmap you allocated if they were not static will be reloaded. Since the memory allowed for one app is 16 MB(24 on some devices i hear) and that bitmap are stored in memory as raw bitmap(read not compressed) this create a spike in the memory usage that can lead to OOM

Add to that the fact that the memory used for bitmaps are allocated outside of your heap memory but counted as if part of it so that you can't actually trace that problem even using ddms or MAT.

Be sure to recycle all your bitmaps in your onDestroy method so that they can be garbage collected.

The one thing that finally got me out of this one are these three lines of code(hack) :

System.gc();
System.runFinalization();
System.gc();

Beware though, it impacts performance(about 500 to 750 ms) so it is not suitable for a game with FPS concern but for an app it is perfectly reasonable.

Put them at the very beginning of your createScaledBitmap call.

It worked for me

Edit :

Depending on what you are doing with the bitmap in question you can ask android to open it downsampled, taking less memory. I wrote this function while dealing with this problem. It tries to open the bitmap as big as possible :

    private Bitmap getDownsampledBitmapFromFile(String fileName, int sampleSize) {

        //Try to free up some memory
        System.gc();
        System.runFinalization();
        System.gc();

        BitmapFactory.Options options=new BitmapFactory.Options();//reset object            
        byte[] tempBuffer=new byte[8000]; 
        options.inTempStorage = tempBuffer;
        options.inSampleSize=sampleSize;

        Bitmap downsampledBitmap = null;

        try {
            downsampledBitmap = BitmapFactory.decodeFile(fileNameToUpload, options);
        } catch (OutOfMemoryError e) {
            sampleSize ++;
        }

        return(downsampledBitmap);

    }
0
Torid On

The OOM exception is because you are running out of Native heap by recreating bitmaps on the orientation change. See my post at BitmapFactory OOM driving me nuts for background.

One way around this is to (as Yahel says) recycle your bitmaps in onDestroy. Although the bitmap data are in the Native heap, and we found that the code of the form below (in onDestroy) is more effective in getting the heap back.

mBitmap.recycle();
mBitmap = null;