1

I'm just trying to change the background image of the app, it has nothing in it yet and i just wanted to change the background of the main activity to an image. It displays correctly in the preview but when I come to run it either on an emulator or on my physical device I get this error.

Throwing OutOfMemoryError "Failed to allocate a 604786188 byte allocation with 4194208 free bytes and 230MB until OOM"

The image is less than 1.5MB so I have no clue why it would run out of memory or be trying to allocate that much.

all I did was open a blank project and change the background of the relative layout.

The strange thing is if I run the app on a nexus 4 emulator it works fine, but a nexus 5 even on the same version on android throws this error.

EDIT

No longer getting the above error instead the following error occurs on launch of the app regardless of the emulator used.

FATAL EXCEPTION: main
    Process: com.jacksteel.comp4, PID: 2164
    java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.jacksteel.comp4/com.jacksteel.comp4.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.Window.findViewById(int)' on a null object reference
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2236)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2390)
            at android.app.ActivityThread.access$800(ActivityThread.java:151)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5257)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.view.View android.view.Window.findViewById(int)' on a null object reference
            at android.app.Activity.findViewById(Activity.java:2072)
            at com.jacksteel.comp4.MainActivity.<init>(MainActivity.java:20)
            at java.lang.reflect.Constructor.newInstance(Native Method)
            at java.lang.Class.newInstance(Class.java:1606)
            at android.app.Instrumentation.newActivity(Instrumentation.java:1066)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2226)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2390)
            at android.app.ActivityThread.access$800(ActivityThread.java:151)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5257)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

EDIT 2 Source Code

 package com.jacksteel.comp4;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.RelativeLayout;


 public class MainActivity extends AppCompatActivity {
     private RelativeLayout Bg = (RelativeLayout) findViewById(R.id.MainBg);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),R.drawable.redboxes,Bg.getWidth(), Bg.getHeight());
        Resources res = getResources();
        BitmapDrawable backgroundDrawable = new BitmapDrawable(res, bitmap);
        Bg.setBackgroundDrawable(backgroundDrawable);
    }


     @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

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

EDIT 3 Update

Updated Source code

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final RelativeLayout Bg = (RelativeLayout) findViewById(R.id.MainBg);

    Bg.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            Bg.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), R.drawable.redboxes, Bg.getWidth(), Bg.getHeight());
            Resources res = getResources();
            BitmapDrawable backgroundDrawable = new BitmapDrawable(res, bitmap);
            Bg.setBackgroundDrawable(backgroundDrawable);
        }
    });

}

Updated Error

FATAL EXCEPTION: main
    Process: com.jacksteel.comp4, PID: 1872
    java.lang.OutOfMemoryError: Failed to allocate a 107347980 byte allocation with 1048576 free bytes and 63MB until OOM
            at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
            at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
            at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)
            at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)
            at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:467)
            at com.jacksteel.comp4.MainActivity.decodeSampledBitmapFromResource(MainActivity.java:93)
            at com.jacksteel.comp4.MainActivity$1.onGlobalLayout(MainActivity.java:27)
            at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:912)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1881)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1061)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5885)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:767)
            at android.view.Choreographer.doCallbacks(Choreographer.java:580)
            at android.view.Choreographer.doFrame(Choreographer.java:550)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:753)
            at android.os.Handler.handleCallback(Handler.java:739)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:135)
            at android.app.ActivityThread.main(ActivityThread.java:5257)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
mixel
  • 25,177
  • 13
  • 126
  • 165
Jack
  • 391
  • 5
  • 25
  • That's a really common problem. See [this answer](http://stackoverflow.com/a/10127787/4191629). I recommend using a image loading library, such as [Picasso](http://square.github.io/picasso/) or [Glide](https://github.com/bumptech/glide) that will take care of the resizing and memory caching for you. – maciekjanusz Sep 27 '15 at 22:03
  • "The image is less than 1.5MB" -- that is the compressed size on disk. That has little bearing on the amount of memory that is needed for the uncompressed image. What is the resolution of this image? And, in what resource directory (or directories) do you have versions of this image? – CommonsWare Sep 27 '15 at 22:16
  • Could you post complete source code of your layout and activity? – mixel Sep 28 '15 at 07:09
  • Did my answer help you? If yes - please mark it as accepted. – mixel Sep 28 '15 at 10:42
  • @mixel I have added the source code, still getting the error in edit 1 – Jack Sep 28 '15 at 16:16
  • @Jack That is because of ` private RelativeLayout Bg = (RelativeLayout) findViewById(R.id.MainBg);` is executed before `setContentView()`. – mixel Sep 28 '15 at 16:19
  • @Jack Assign `Bg = (RelativeLayout) findViewById(R.id.MainBg);` after `setContentView(R.layout.activity_main)`. And do not forget to get size of `Bg` with `Bg.getViewTreeObserver()...` as I wrote in my answer. – mixel Sep 28 '15 at 16:22
  • @mixel made those changes, see Edit 3 - back to the outOfMemoryError – Jack Sep 28 '15 at 17:16
  • @Jack What is resolution of your device screen and what size of your image in pixels? – mixel Sep 28 '15 at 17:22
  • @mixel device resolution is 1080x1920, Image size is 3456x4861, just tried it on a different emulator and it works on a resolution of 768x1280 – Jack Sep 28 '15 at 17:28
  • Have you solved your issue? I moved discussion to chat. – mixel Sep 29 '15 at 07:38
  • @mixel no, the error is still happening. I can't find anything to fix it – Jack Sep 29 '15 at 21:05
  • Let's continue in chat http://chat.stackoverflow.com/rooms/90817/discussion-between-jack-and-mixel – mixel Sep 29 '15 at 21:12

1 Answers1

4

UPDATE

You should put image that you will load with BitmapFactory.decodeResource() to drawable-nodpi directory to avoid scaling from density that used in drawable-<density> directory name to your device screen density.

In your case you putted image in drawable-hdpi directory and your device has xxhdpi screen so in BitmapFactory.decodeResource() with inSampleSize=2 image were 2x scaled up (from hdpi to xxhdpi) and 2x scaled down (by inSampleSize) remaining at original size and causing application crash by OutOfMemoryError.

ORIGINAL ANSWER

1.5MB is the size of compressed image. When you load it to ImageView or background of some other view then Bitmap is implicitly created. In Bitmap every pixel of image takes 4 bytes. So if your image has a resolution of 4096x4096 pixels then it takes 64 megabytes in memory though image could be filled with single color and takes only few kilobytes compressed to png or jpeg.

You should determine real size of your view and load scaled image:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
            Bitmap bitmap = ImageUtils.decodeSampledBitmapFromFile(backgroundImageFile, view.getWidth(), view.getHeight());
            view.setBackground(new BitmapDrawable(getResources(), bitmap));
        }
    });

There is my utility method to load scaled Bitmap from File:

public class ImageUtils {
    public static Bitmap decodeSampledBitmapFromFile(File file, int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(file.getAbsolutePath(), options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
    }

     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) {

            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.floor((float) height / (float) reqHeight);
            final int widthRatio = Math.floor((float) width / (float) reqWidth);

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

You can use other BitmapFactory methods to decode Bitmap from stream, resource and byte array.

mixel
  • 25,177
  • 13
  • 126
  • 165
  • using .setBackground gives this: `Call requires API level 16 (current min is 8): android.view.View#setBackground` and the app still crashes on launch – Jack Sep 27 '15 at 23:58
  • That is because it's new API available. You should use `View.setBackgroundDrawable()` instead. – mixel Sep 28 '15 at 07:08