OutOfMemoryError при загрузке действий - PullRequest
3 голосов
/ 02 марта 2012

У меня довольно простое действие, подобное этому:

public class SurvivalActivity extends Activity {
    private static final String KEY_LAYOUT_ID = "SurvivalLayoutId";
    int mLayoutId;
    private static final int[] mSurvivalLayouts = {
        R.layout.survival1,
        R.layout.survival2,
        R.layout.survival3,
    };

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final Intent i = getIntent();
        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_LAYOUT_ID)) {
            mLayoutId = savedInstanceState.getInt(KEY_LAYOUT_ID, 0);
        } else if (i != null) {
            mLayoutId = i.getIntExtra(KEY_LAYOUT_ID, 0);
        } else {
            mLayoutId = 0;
        }

        // Layout
        setContentView(mSurvivalLayouts[mLayoutId]);

        // Title
        Util.setTitleFont(this, findViewById(R.id.title));

        // "Next" button
        final View nextButton = findViewById(R.id.survival_next);
        Util.setFont(this, nextButton, "Lobster.ttf");
        nextButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                final Intent next = new Intent(SurvivalActivity.this, SurvivalActivity.class);
                next.putExtra(KEY_LAYOUT_ID, (mLayoutId + 1) % mSurvivalLayouts.length);
                startActivity(next);
                finish();
            }
        });
    }

    @Override
    protected void onSaveInstanceState(final Bundle outState) {
        outState.putInt(KEY_LAYOUT_ID, mLayoutId);
    }
}

Два статических метода Util. * просто меняют шрифт TextView.

Все 3 макета довольно просты, позвольте мне показать вам первый:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <TextView
            style="@style/Title"
            android:text="@string/surv1_title" />

        <TextView
            style="@style/Content"
            android:text="@string/surv1_1" />

        <ImageView
            style="@style/Picture"
            android:src="@drawable/surv_crafting" />
        <TextView
            style="@style/PicLegend"
            android:src="@string/surv_crafting_leg" />
        <ImageView
            style="@style/Picture"
            android:src="@drawable/surv_hache" />
        <TextView
            style="@style/PicLegend"
            android:src="@string/surv_hache_leg" />
        <ImageView
            style="@style/Picture"
            android:src="@drawable/surv_pioche" />
        <TextView
            style="@style/PicLegend"
            android:src="@string/surv_pioche_leg" />

        <TextView
            style="@style/Content"
            android:text="@string/surv1_2" />

        <ImageView
            style="@style/Picture"
            android:src="@drawable/surv_charbon" />
        <TextView
            style="@style/PicLegend"
            android:src="@string/surv_charbon_leg" />

        <TextView
            style="@style/Content"
            android:text="@string/surv1_3" />

        <ImageView
            style="@style/Picture"
            android:src="@drawable/surv_torch" />
        <TextView
            style="@style/PicLegend"
            android:src="@string/surv_torch_leg" />

        <TextView
            style="@style/Content"
            android:text="@string/surv1_4" />

        <ImageView
            style="@style/Picture"
            android:src="@drawable/surv_abri" />
        <TextView
            style="@style/PicLegend"
            android:src="@string/surv_abri_leg" />

        <TextView
            style="@style/Content"
            android:text="@string/surv1_5" />

        <Button
            style="@style/SurvivalBoxNext"
            android:text="@string/surv1_next" />

    </LinearLayout>
</ScrollView>

Как вы можете видеть, это всего несколько TextView с ImageView с и Button, которые запускают загрузку следующего действия.

Я запускаю приложение, оно отображает первый экран. Прокручиваю вниз до кнопки, нажимаю, отображается второй экран (с икотой). Я прокручиваю вниз до кнопки, нажимаю и бум.

03-02 16:36:46.488 E/AndroidRuntime(10611): java.lang.RuntimeException: Unable to start activity ComponentInfo{net.bicou.myapp/net.bicou.myapp.SurvivalActivity}: android.view.InflateException: Binary XML file line #72: Error inflating class <unknown>
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1955)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1980)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.ActivityThread.access$600(ActivityThread.java:122)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1146)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.os.Handler.dispatchMessage(Handler.java:99)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.os.Looper.loop(Looper.java:137)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.ActivityThread.main(ActivityThread.java:4340)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at java.lang.reflect.Method.invokeNative(Native Method)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at java.lang.reflect.Method.invoke(Method.java:511)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at dalvik.system.NativeStart.main(Native Method)
03-02 16:36:46.488 E/AndroidRuntime(10611): Caused by: android.view.InflateException: Binary XML file line #72: Error inflating class <unknown>
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.createView(LayoutInflater.java:606)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:56)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.onCreateView(LayoutInflater.java:653)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:678)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.rInflate(LayoutInflater.java:739)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.rInflate(LayoutInflater.java:742)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:251)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.Activity.setContentView(Activity.java:1835)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at net.bicou.myapp.SurvivalActivity.onCreate(SurvivalActivity.java:34)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.Activity.performCreate(Activity.java:4465)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1919)
03-02 16:36:46.488 E/AndroidRuntime(10611):     ... 11 more
03-02 16:36:46.488 E/AndroidRuntime(10611): Caused by: java.lang.reflect.InvocationTargetException
03-02 16:36:46.488 E/AndroidRuntime(10611):     at java.lang.reflect.Constructor.constructNative(Native Method)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.view.LayoutInflater.createView(LayoutInflater.java:586)
03-02 16:36:46.488 E/AndroidRuntime(10611):     ... 25 more
03-02 16:36:46.488 E/AndroidRuntime(10611): Caused by: java.lang.OutOfMemoryError
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.graphics.Bitmap.nativeCreate(Native Method)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.graphics.Bitmap.createBitmap(Bitmap.java:605)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.graphics.Bitmap.createBitmap(Bitmap.java:551)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:437)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:524)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:499)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:351)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:773)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.content.res.Resources.loadDrawable(Resources.java:1937)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.content.res.TypedArray.getDrawable(TypedArray.java:601)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.widget.ImageView.<init>(ImageView.java:119)
03-02 16:36:46.488 E/AndroidRuntime(10611):     at android.widget.ImageView.<init>(ImageView.java:109)
03-02 16:36:46.488 E/AndroidRuntime(10611):     ... 28 more
03-02 16:36:46.520 W/ActivityManager(  211):   Force finishing activity net.bicou.myapp/.SurvivalActivity

Я не понимаю:

  1. Почему предыдущие действия не освобождаются, чтобы освободить место
  2. Почему мое приложение занимает столько кучи (Grow heap (frag case) to 60.869MB for 3600016-byte allocation)
  3. Как сделать так, чтобы последовательно отображались 3 действия, каждое из которых содержит примерно 10 TextView с и 10 ImageView с
  4. Если я сделал что-то не так, потому что я не вижу ничего плохого в своем коде, но, очевидно, что-то не так.

Спасибо.

Редактировать: размеры изображения первого действия:

-rw-r--r--  1 bicou  staff   167197 29 fév 16:42 surv_abri.jpg 600x377 px
-rw-r--r--  1 bicou  staff    24658 29 fév 16:42 surv_charbon.png 559x277 px
-rw-r--r--  1 bicou  staff     5285 29 fév 16:42 surv_crafting.png 214x121 px
-rw-r--r--  1 bicou  staff     4190 29 fév 16:42 surv_hache.png 214x121 px
-rw-r--r--  1 bicou  staff     3809 29 fév 16:42 surv_pioche.png 214x121 px
-rw-r--r--  1 bicou  staff     2252 29 fév 16:42 surv_torch.png 215x121 px

Второе занятие:

-rw-r--r--  1 bicou  staff     3371 29 fév 16:42 surv_four.png 204x112 px
-rw-r--r--  1 bicou  staff     5059 29 fév 16:42 surv_glass.png 215x122 px
-rw-r--r--  1 bicou  staff     3176 29 fév 16:42 surv_malle.png 204x112 px
-rw-r--r--  1 bicou  staff     2676 29 fév 16:42 surv_pelle.png 204x112 px
-rw-r--r--  1 bicou  staff   167166 29 fév 16:42 surv_repere.png 528x331 px
-rw-r--r--  1 bicou  staff   134706 29 fév 16:42 surv_sand.jpg 854x480 px

Третье действие (сбой):

-rw-r--r--  1 bicou  staff    58919 29 fév 16:42 surv_1.jpg 600x377 px
-rw-r--r--  1 bicou  staff    51144 29 fév 16:42 surv_2.jpg 600x377 px
-rw-r--r--  1 bicou  staff    46917 29 fév 16:42 surv_3.jpg 600x377 px
-rw-r--r--  1 bicou  staff    53226 29 fév 16:42 surv_4.jpg 600x377 px
-rw-r--r--  1 bicou  staff    37787 29 fév 16:42 surv_5.jpg 600x377 px
-rw-r--r--  1 bicou  staff    31050 29 fév 16:42 surv_6.jpg 600x377 px
-rw-r--r--  1 bicou  staff    38389 29 fév 16:42 surv_7.jpg 600x377 px
-rw-r--r--  1 bicou  staff    44471 29 fév 16:42 surv_8.jpg 600x377 px

И фон окна:

-rw-r--r--  1 bicou  staff    30857 29 fév 16:42 background_img.png

EDIT:

ОК - я попытался надувать представления непосредственно из кода, чтобы проверить, не было ли у меня глючного изображения. Вот мой код активности (только переменные + onCreate, а остальные идентичны)

private static final int[] mTitles = {
    R.string.surv1_title, R.string.surv2_title, R.string.surv3_title
};
private static final int[][] mTextViews = {
    {   R.string.surv1_1, R.string.surv1_2, R.string.surv1_3, R.string.surv1_4, R.string.surv1_5 },
    {   R.string.surv2_1, R.string.surv2_2, R.string.surv2_3, R.string.surv2_4, R.string.surv2_5, R.string.surv2_6 },
    {   R.string.surv3_1, R.string.surv3_2, R.string.surv3_3 }
};
private static final int[][] mImageViews = {
    {   R.drawable.surv_pioche, R.drawable.surv_crafting, R.drawable.surv_hache, R.drawable.surv_charbon,
        R.drawable.surv_torch, R.drawable.surv_abri },
    {   R.drawable.surv_malle, R.drawable.surv_four, R.drawable.surv_pelle, R.drawable.surv_repere,
        R.drawable.surv_sand, R.drawable.surv_glass },
    {   R.drawable.surv_1, R.drawable.surv_2, R.drawable.surv_3, R.drawable.surv_4,
        R.drawable.surv_5, R.drawable.surv_6, R.drawable.surv_7, R.drawable.surv_8  },
};
private static final int[] mNextButtons = {
    R.string.surv1_next, R.string.surv2_next, R.string.surv3_next
};

@Override
public void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final Intent i = getIntent();
    if (savedInstanceState != null && savedInstanceState.containsKey(KEY_LAYOUT_ID)) {
        mLayoutId = savedInstanceState.getInt(KEY_LAYOUT_ID, 0);
    } else if (i != null) {
        mLayoutId = i.getIntExtra(KEY_LAYOUT_ID, 0);
    } else {
        mLayoutId = 0;
    }

    // Layout
    //setContentView(mSurvivalLayouts[mLayoutId]);
    LinearLayout ll;
    ScrollView sv;
    TextView tv;
    ImageView iv;
    Button b;

    sv = new ScrollView(this);
    sv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    ll = new LinearLayout(this);
    ll.setOrientation(LinearLayout.VERTICAL);
    ll.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

    tv = new TextView(this);
    tv.setId(R.id.title);
    tv.setText(getString(mTitles[mLayoutId]));
    ll.addView(tv);

    for (final int textId: mTextViews[mLayoutId]) {
        tv = new TextView(this);
        tv.setText(getString(textId));
        ll.addView(tv);
    }

    for (final int imgId: mImageViews[mLayoutId]) {
        L.i("Creating image: "+imgId);
        iv = new ImageView(this);
        iv.setImageResource(imgId);
        ll.addView(iv);
    }

    b = new Button(this);
    b.setText(getString(mNextButtons[mLayoutId]));
    b.setId(R.id.survival_next);
    ll.addView(b);

    sv.addView(ll);
    setContentView(sv);

    // Title
    Util.setTitleFont(this, findViewById(R.id.title));

    // "Next" button
    final View nextButton = findViewById(R.id.survival_next);
    Util.setFont(this, nextButton, "Lobster.ttf");
    nextButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(final View v) {
            final Intent next = new Intent(SurvivalActivity.this, SurvivalActivity.class);
            next.putExtra(KEY_LAYOUT_ID, (mLayoutId + 1) % mSurvivalLayouts.length);
            startActivity(next);
            finish();
        }
    });
}

Как вы можете видеть, я просто раздуваю виды напрямую, а L.i (который является псевдонимом Log.i(TAG, ...)) указывает, что было последним накачанным ImageView перед любым падением.

Я запустил приложение, увидел 1-е действие, затем второе, и, как и раньше, оно вылетает при загрузке третьего. Последним идентификатором, который я видел в logcat, была последняя картинка R.drawable.surv_8. Это файл JPEG размером 630x377, 45 КБ.

Я пытался изменить порядок просмотров следующим образом:

private static final int[][] mImageViews = {
    {   R.drawable.surv_pioche, R.drawable.surv_crafting, R.drawable.surv_hache, R.drawable.surv_charbon,
        R.drawable.surv_torch, R.drawable.surv_abri },
    {   R.drawable.surv_malle, R.drawable.surv_four, R.drawable.surv_pelle, R.drawable.surv_repere,
        R.drawable.surv_sand, R.drawable.surv_glass },
    {   R.drawable.surv_8, R.drawable.surv_1, R.drawable.surv_2, R.drawable.surv_3, R.drawable.surv_4,
        R.drawable.surv_5, R.drawable.surv_6, R.drawable.surv_7,  },
};

Так что Surv_8 является первым. Приложение по-прежнему падает на последнем снимке, который равен R.drawable.surv_7. Так что это не проблема с изображением.

Затем я удалил одну из картинок из mImageViews[2], чтобы у меня было только 7 картинок вместо 8. Приложение работает должным образом: активность загружается, когда я нажимаю кнопку, я перехожу к следующей и на последнем я иду к первому. Без сбоев.

Однако я могу сказать, что действия 1 и 2 остаются в памяти. Действительно, взгляните на время:

03-04 15:47:13.685 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +2s570ms
03-04 15:47:36.834 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +803ms
03-04 15:47:39.756 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +1s476ms
03-04 15:47:44.201 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +462ms
03-04 15:47:46.717 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +474ms
03-04 15:47:48.599 I/ActivityManager(17430): Displayed net.bicou.myapp/.SurvivalActivity: +474ms

400 мс вместо 1,5-2,5 с для загрузки упражнения.

Почему это остается в памяти?

=> ** Нужно ли вручную освобождать все фотографии при onDestroy()? **

=> Что вы думаете об этом: http://androidactivity.wordpress.com/2011/09/24/solution-for-outofmemoryerror-bitmap-size-exceeds-vm-budget/
С помощью этого решения я мог загружать как 10 битовых карт по всему миру, и я мог бы освободить некоторые из них при загрузке другого действия?

Ответы [ 3 ]

5 голосов
/ 09 марта 2012

Мне жаль, что ответ на этот вопрос на самом деле невозможно было получить с элементами, которые я дал в своем вопросе.

Действительно, как уже упоминалось в другом вопросе здесь: Почему всемои растровые изображения увеличились на 200%? , я поместил свои изображения в папку drawable, что на таком экране xhdpi, как мой, приведет к тому, что Android увеличит выборку всего дважды (для сопоставления xhdpi с mdpi) и приведет к400% использования памяти (в два раза больше ширины и в два раза больше высоты, что в 4 раза больше пикселей).

Еще одна вещь, поскольку я не хочу, чтобы пользователь мог вернуться назад (см. finish()сразу после startActivity()) вот что я вставил в onPause():

private void unbindDrawables(View view) {
    if (view.getBackground() != null) {
        view.getBackground().setCallback(null);
    }
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            unbindDrawables(((ViewGroup) view).getChildAt(i));
        }
        ((ViewGroup) view).removeAllViews();
    }
}

Вот какой-то код, который я получил здесь в StackOverflow (например, здесь: Drawable vs Single многоразовый Bitmap лучшес памятью? ).

Вот мои замечания об этом коде и почему важна каждая строка:

  • setCallback(null) удаляет ссылку на действие, так чтоэто может быть мусор.Если вы этого не сделаете, все виды останутся в памяти.
  • Вам также нужно вызывать его с onPause, потому что onDestroy гарантированно не будет вызываться.
  • После этого вам может потребоваться вызвать сборку мусора, чтобы помочь GC понять, что некоторые ссылки на объекты были сделаны сиротами и могут быть GC'd.Просто позвоните по номеру System.gc().

. Полученный код onPause, основной макет вашей деятельности с идентификатором top_layout:

@Override
protected void onPause() {
    super.onPause();
    unbindDrawables(findViewById(R.id.top_layout));
    System.gc();
}
.
2 голосов
/ 02 марта 2012

Возможно, что растровое изображение, которое вы помещаете в ImageView, очень большое.Вот некоторые вещи, которые вы можете попробовать:

Откажитесь от XML-макета и просто надуйте и отобразите проблемный ImageView или ImageViews и посмотрите, нет ли такой ошибки.

Также проверьте каждое из ваших изображений, чтобы увидеть, какой у них размер.Размер, который они принимают в памяти, составляет примерно 4 * ширина * высота (в байтах, где ширина и высота - это размер изображения в пикселях).

Поскольку ваши экземпляры Bitmap занимают память в куче, яЯ предполагаю, что вы используете Android 3 или выше.В этих версиях именно туда и идут экземпляры Bitmap (как и любой другой экземпляр Java).В Android 2.x и ниже экземпляры растрового изображения размещались в некоторой свободной памяти.Зная это, убедитесь, что полученное вами большое количество кучи действительно связано с изображениями, а не с другими созданными экземплярами.Попробуйте отладить ваши конструкторы и посмотрите, кто что вызывает, когда приложение запускается / запускается.

Согласно наблюдениям Bicou, вы можете захотеть вручную загрузить ваши изображения через BitmapFactory, и при этом, возможно, уменьшите их (через объект Options).).Также попробуйте сохранить счетчик количества байтов изображения в памяти, которое вы увеличиваете, увеличивая на 4 * ширина * высоту каждого изображения, которое вы фактически загружаете в растровый объект.

1 голос
/ 08 марта 2012

Лучший способ оптимизировать использование памяти с помощью растровых изображений в вашем случае - сохранить список ваших ImageViews, сделайте это перед вызовом finish () для каждого ImageView:

((BitmapDrawable) myImageView.getDrawable()).getBitmap().recycle();
myImageView = null;
...