Sweeper
Sweeper

Reputation: 271050

I am just loading 22 drawables, why OutOfMemoryError?

I am writing an Android app that helps little kids learn maths. Users can select some question options and answer the questions. If he/she answers all of them correctly, he/she can get a prize. And there is a different prize for every question option. And there are 22 different question options. I found 22 images online and put them in my drawable folder. Then I wrote a class full of maps, called QuestionOptionMaps. Here it is, hope you know what I want to do here:

package com.smartkidslovemaths.util;

import com.smartkidslovemaths.QuestionOptions;
import com.smartkidslovemaths.R;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;

public class QuestionOptionMaps {

    private QuestionOptionMaps () {}

    public static ArrayList<QuestionOptions> getOptionsList() {
        return optionsList;
    }

    public static HashMap<QuestionOptions, Integer> getOptionsDrawableMap() {
        return optionsDrawableMap;
    }

    public static HashMap<QuestionOptions, String> getOptionsKeysMap() {
        return optionsKeysMap;
    }

    public static HashMap<QuestionOptions, Integer> getOptionsTimerMap() {
        return optionsTimerMap;
    }

    private static ArrayList<QuestionOptions> optionsList;
    private static HashMap<QuestionOptions, Integer> optionsDrawableMap;
    private static HashMap<QuestionOptions, String> optionsKeysMap;
    private static HashMap<QuestionOptions, Integer> optionsTimerMap;

    static {
        optionsList = new ArrayList<> ();
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADDITION, 1, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADDITION, 2, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADDITION, 3, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADDITION, 1, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADDITION, 2, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADDITION, 3, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.SUBTRACTION, 1, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.SUBTRACTION, 2, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.SUBTRACTION, 3, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.SUBTRACTION, 1, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.SUBTRACTION, 2, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.SUBTRACTION, 3, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADD_AND_SUB, 1, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADD_AND_SUB, 2, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADD_AND_SUB, 3, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADD_AND_SUB, 1, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADD_AND_SUB, 2, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.ADD_AND_SUB, 3, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.MULTIPLICATION, 1, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.MULTIPLICATION, 2, false));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.MULTIPLICATION, 1, true));
        optionsList.add (new QuestionOptions (QuestionOptions.OperationType.MULTIPLICATION, 2, true));

        ArrayList<String> prefKeyArray = new ArrayList<> ();
        prefKeyArray.add ("p110");
        prefKeyArray.add ("p120");
        prefKeyArray.add ("p130");
        prefKeyArray.add ("p111");
        prefKeyArray.add ("p121");
        prefKeyArray.add ("p131");
        prefKeyArray.add ("p210");
        prefKeyArray.add ("p220");
        prefKeyArray.add ("p230");
        prefKeyArray.add ("p211");
        prefKeyArray.add ("p221");
        prefKeyArray.add ("p231");
        prefKeyArray.add ("p310");
        prefKeyArray.add ("p320");
        prefKeyArray.add ("p330");
        prefKeyArray.add ("p311");
        prefKeyArray.add ("p321");
        prefKeyArray.add ("p331");
        prefKeyArray.add ("p410");
        prefKeyArray.add ("p420");
        prefKeyArray.add ("p411");
        prefKeyArray.add ("p421");
        optionsKeysMap = getHashMapFromCollections (optionsList, prefKeyArray);

        ArrayList<Integer> idArray = new ArrayList<> ();
        idArray.add (R.drawable.p110);
        idArray.add (R.drawable.p120);
        idArray.add (R.drawable.p130);
        idArray.add (R.drawable.p111);
        idArray.add (R.drawable.p121);
        idArray.add (R.drawable.p131);
        idArray.add (R.drawable.p210);
        idArray.add (R.drawable.p220);
        idArray.add (R.drawable.p230);
        idArray.add (R.drawable.p211);
        idArray.add (R.drawable.p221);
        idArray.add (R.drawable.p231);
        idArray.add (R.drawable.p310);
        idArray.add (R.drawable.p320);
        idArray.add (R.drawable.p330);
        idArray.add (R.drawable.p311);
        idArray.add (R.drawable.p321);
        idArray.add (R.drawable.p331);
        idArray.add (R.drawable.p410);
        idArray.add (R.drawable.p420);
        idArray.add (R.drawable.p411);
        idArray.add (R.drawable.p421);
        optionsDrawableMap = getHashMapFromCollections (optionsList, idArray);

        //TODO initialize the collections
    }

    private static <K, V> HashMap<K, V> getHashMapFromCollections (Collection<K> keys, Collection<V> values) {
        if (keys.size () != values.size ())
            throw new AssertionError ();
        HashMap<K, V> map = new HashMap<> ();
        K[] keyArray = (K[])keys.toArray ();
        V[] valueArray = (V[])values.toArray ();
        for (int i = 0 ; i < keys.size () ; i++) {
            map.put (keyArray[i], valueArray[i]);
        }
        return map;
    }
}

Most of it is just initializing the maps. I didn't initialize the optionsTimer map because that is a todo. Now I created a PrizeActivity which displays all the prizes that the user got. It basically shows all the prizes and the amount that you have. Because there are a lot of prizes, I decided to dynamically add views to a ScrollView. Here is the layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin"
                tools:context="com.smartkidslovemaths.PrizeActivity">
    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:id="@+id/trophy_content"
            android:orientation="horizontal">

        </LinearLayout>
    </HorizontalScrollView>

</RelativeLayout>

And here is how I add the views:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate (savedInstanceState);
    setContentView (R.layout.activity_prize);
    for (QuestionOptions option : QuestionOptionMaps.getOptionsList ()) {
        displayAPrize (option);
    }
}

private void displayAPrize (QuestionOptions options) {
    Resources res = getResources ();
    int parentMargin = (int)res.getDimension (R.dimen.prize_display_margin);
    LinearLayout.LayoutParams parentParams =
            new LinearLayout.LayoutParams (ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT);
    parentParams.setMargins (parentMargin, parentMargin, parentMargin, parentMargin);

    LinearLayout.LayoutParams imageParams =
            new LinearLayout.LayoutParams (
                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT
            );

    LinearLayout parent = new LinearLayout (this);
    parent.setLayoutParams (parentParams);
    parent.setOrientation (LinearLayout.VERTICAL);


    ImageView image = new ImageView (this);
    image.setLayoutParams (imageParams);
    int imageId = QuestionOptionMaps.getOptionsDrawableMap ().get (options);
    image.setImageResource (imageId);
    parent.addView (image);

    TextView text = new TextView (this);
    text.setLayoutParams (imageParams);
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences (this);
    String key = QuestionOptionMaps.getOptionsKeysMap ().get (options);
    int prizeCount = prefs.getInt (key, 0);
    text.setText ("x" + prizeCount);
    parent.addView (text);

    ((LinearLayout)findViewById (R.id.trophy_content)).addView (parent);
}

And when I run the app, it crashed with an OutOfMemoryError! Here is the call stack:

java.lang.OutOfMemoryError
        at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
        at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:503)
        at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:356)
        at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:816)
        at android.content.res.Resources.loadDrawable(Resources.java:2117)
        at android.content.res.Resources.getDrawable(Resources.java:702)
        at android.widget.ImageView.resolveUri(ImageView.java:636)
        at android.widget.ImageView.setImageResource(ImageView.java:365)
        at com.smartkidslovemaths.PrizeActivity.displayAPrize(PrizeActivity.java:46)
        at com.smartkidslovemaths.PrizeActivity.onCreate(PrizeActivity.java:22)
        at android.app.Activity.performCreate(Activity.java:5133)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2230)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2316)
        at android.app.ActivityThread.access$600(ActivityThread.java:150)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1298)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:213)
        at android.app.ActivityThread.main(ActivityThread.java:5225)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:525)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:741)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:557)
        at dalvik.system.NativeStart.main(Native Method)

Line 46 is this line:

image.setImageResource (imageId);

I really don't understand why this happens. Other apps have lots more images, why they don't crash? is there any way to fix this?

Upvotes: 0

Views: 86

Answers (2)

Gunaseelan
Gunaseelan

Reputation: 15515

Add android:largeHeap="true" in your application tag in manifest file.

And try again.

But this way is not recommended. Take a look at following posts also.

  1. Displaying Bitmaps Efficiently
  2. Strange out of memory issue while loading an image to a Bitmap object

Upvotes: 3

Rajesh Jadav
Rajesh Jadav

Reputation: 12861

I think your drawables'resolution is higher. Either you have to reduce their resolution or you can use following alternate solution

<application
    ....
       android:largeHeap="true"> 

Actually android:largeHeap is the instrument for increasing your allocated memory to app.

There is no clear definition of the need to use this flag. If you need more memory - Android provides you with a tool to increase it. But necessity of using, you define yourself.

From Android Docs:

Whether your application's processes should be created with a large Dalvik heap. This applies to all processes created for the application. It only applies to the first application loaded into a process; if you're using a shared user ID to allow multiple applications to use a process, they all must use this option consistently or they will have unpredictable results.

Most apps should not need this and should instead focus on reducing their overall memory usage for improved performance. Enabling this also does not guarantee a fixed increase in available memory, because some devices are constrained by their total available memory.

To query the available memory size at runtime, use the methods getMemoryClass() or getLargeMemoryClass().

DrawBack:

The use of largeHeap is not recommended in all cases, please use it very cautiously, it might slow other running application, and also impact your app's reactiveness, since the garbage collector with be solicited more often. For more information check this speech from google i/o Link

I hope it will help you.

Upvotes: 1

Related Questions