0

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.access600ドル(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?

asked Aug 28, 2015 at 3:36
6
  • It also matters how large the images are. Commented Aug 28, 2015 at 3:39
  • @Karakuri They are 1.9 MB in total Commented Aug 28, 2015 at 3:42
  • once again, the size of the image files is of no importance. their dimensions only matters, as it define the size of the bitmap in memory Commented Aug 28, 2015 at 3:46
  • Oh they are about 800px long and wide. I thought images with a too low resolution would look blurry on a phone's screen because the pixels are dense. @njzk2 Commented Aug 28, 2015 at 3:49
  • @Sweeper just go through this link you will get an idea to solve it.. developer.android.com/training/displaying-bitmaps/… Commented Aug 28, 2015 at 4:07

2 Answers 2

3

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
halfer
20.2k20 gold badges110 silver badges207 bronze badges
answered Aug 28, 2015 at 3:41
Sign up to request clarification or add additional context in comments.

3 Comments

This is certainly not a good solution as it doesn't address why you are having the problem, so you may run into the same problem again even with a larger heap.
@Karakuri it doesn't address why you are having the problem - the exception itself said OutOfMemoryError. I think even kids can understand what this word tells. And Opp asked as is there any way to fix this?. So I provided possible solution and also I have explained why it is not recommended. Now I am wired how would you say this is certainly not a good solution.
@Gunaseelan because if there is a problem with his code in the way he handles bitmaps, then that problem still exists, and he can run into the same error later even with a larger heap. It may take longer, but it can still happen because his actual problem isn't fixed. His app is filling up memory and not allowing any (or enough) of it to be reclaimed; you're just giving him more memory to fill up.
1

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.

answered Aug 28, 2015 at 3:52

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.