我刚加载 22 个可绘制对象,为什么会出现 OutOfMemoryError?

I am just loading 22 drawables, why OutOfMemoryError?

我正在编写一个 Android 应用程序来帮助小孩子学习数学。用户可以select一些问题选项并回答问题。如果he/she全部答对,he/she可以获得奖品。每个问题选项都有不同的奖励。并且有 22 个不同的问题选项。我在网上找到了 22 张图片并将它们放在我的 drawable 文件夹中。然后我写了一个class全图,叫QuestionOptionMaps。在这里,希望你知道我想在这里做什么:

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

大部分只是初始化地图。我没有初始化 optionsTimer 地图,因为那是待办事项。现在我创建了一个 PrizeActivity 来显示用户获得的所有奖品。它基本上显示了所有奖品和您拥有的金额。因为有很多的奖品,所以我决定动态添加浏览量到ScrollView。这是布局:

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

这是我添加视图的方式:

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

当我 运行 应用程序时,它崩溃了 OutOfMemoryError!这是调用堆栈:

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.access0(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)

第 46 行是这一行:

image.setImageResource (imageId);

我真的不明白为什么会这样。其他应用程序有更多图像,为什么它们不会崩溃?有什么办法可以解决这个问题吗?

在清单文件的 application 标签中添加 android:largeHeap="true"

然后重试。

但不推荐这种方式。也看看下面的帖子。

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

我认为你的绘图分辨率更高。您要么必须降低其分辨率,要么可以使用以下替代解决方案

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

实际上 android:largeHeap 是增加分配给应用程序的内存的工具。

没有明确定义是否需要使用这个标志。如果您需要更多内存 - Android 为您提供了增加内存的工具。但是否需要使用,你自己定义。

来自 Android 文档:

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().

缺点:

不建议在所有情况下都使用 largeHeap,请谨慎使用,它可能会减慢其他 运行 应用程序的速度,并且还会影响您的应用程序的反应性,因为垃圾收集器被请求的频率更高。有关更多信息,请查看来自 google i/o Link

的演讲

希望对你有所帮助