加载活动时出现OutOfMemoryError

时间:2012-03-02 16:08:52

标签: android out-of-memory

我有一个非常简单的活动:

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. 如果我做错了什么,因为我的代码中看不到任何错误,但显然有些不对劲。
  5. 谢谢。

    编辑:第一项活动的图片尺寸:

    -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
    

    编辑:

    好的 - 我尝试直接从代码中膨胀视图,以便查看我是否有错误的图像。这是我的活动代码(只有变量+ 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被充气。

    我运行了应用程序,看到了第一个活动,然后是第二个活动,并且在加载第三个活动时,之前它崩溃了。我在logcat中看到的最后一个ID是最后一张图片R.drawable.surv_8。这是一个630x377的jpeg文件,45 kiB。

    我试图像这样更改视图的顺序:

    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
    

    400ms而不是1.5-2.5秒来加载活动。

    为什么这会留在记忆中?

    =&GT; **我是否需要在onDestroy()后手动释放所有照片? **

    =&GT;你怎么看待这个:http://androidactivity.wordpress.com/2011/09/24/solution-for-outofmemoryerror-bitmap-size-exceeds-vm-budget/
    有了这个解决方案,我可以全局加载10个位图,我会在加载另一个活动时释放一些吗?

3 个答案:

答案 0 :(得分:5)

对不起,这个问题的答案实际上不可能与我在问题中提出的要素有关。

确实,正如在另一个问题中提到的那样:Why are all my bitmaps upsampled 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 reusable Bitmap better with memory?)。

以下是我对此代码的评论以及为什么每一行都很重要:

  • setCallback(null)删除对活动的引用,以便可以对其进行垃圾回收。如果你不这样做,所有的观点将留在记忆中。
  • 您还需要从onPause调用它,因为无法保证调用onDestroy
  • 您可能需要在此之后进行垃圾收集,以帮助GC了解对孤立的对象的一些引用以及可以进行GC的对象。只需致电System.gc()

这是生成的onPause代码,您的活动的主要布局的ID为top_layout

@Override
protected void onPause() {
    super.onPause();
    unbindDrawables(findViewById(R.id.top_layout));
    System.gc();
}

答案 1 :(得分:2)

可能只是你在ImageView中放置的位图非常大。以下是您可以尝试的一些事项:

抛弃xml布局,只需膨胀并显示有问题的ImageView或ImageViews,看看这是否会出现错误。

同时检查每张图片,看看它们的实际尺寸。它们在内存中的大小大约是4 *宽*高(以字节为单位,其中宽度和高度是图像的大小,以像素为单位)。

由于您的Bitmap实例占用了堆上的内存,我想象您正在使用Android 3或更高版本。在这些版本中,Bitmap实例的位置(就像任何其他Java实例一样)。在Android 2.x及更低版本中,Bitmap实例在一些offheap内存中分配。知道了这一点,请确保您获得的高堆使用率确实来自图像,而不是来自其他一些创建的实例。尝试调试构造函数,看看在应用程序启动/运行时谁调用了什么。

根据Bicou的观察,您可能希望通过BitmapFactory手动加载图像,并且这样做可能会对它们进行下采样(通过Options对象)。另外,尝试通过以实际加载到位图对象中的每个图像的4 *宽度*高度递增来计算内存总量中的图像字节数。

答案 2 :(得分:1)

在您的情况下使用位图优化内存使用情况的最佳方法是在每个ImageView调用finish()之前保存一个ImageViews列表:

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