OutOfMemoryError al cargar actividades

Tengo una actividad bastante simple como esta:

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

Los dos Util. * Los métodos estáticos sólo cambian la fuente de TextView .

Los 3 diseños son bastante simples, permítanme mostrarles la primera:

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

Como se puede ver, son sólo varios TextView con ImageView , y un Button que dispara la carga de la siguiente actividad.

Lance la aplicación, muestra la primera pantalla. Desplácese hacia abajo hasta el botón, haga clic, la segunda pantalla se muestra (con un hipo). Me desplazo hacia abajo hasta el botón, haga clic, y el auge.

 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 

No entiendo:

  1. Por qué las actividades anteriores no se liberan para liberar un poco de espacio
  2. ¿Por qué mi aplicación ocupa tanto montón ( Grow heap (frag case) to 60.869MB for 3600016-byte allocation )
  3. ¿Cómo puedo hacer para mostrar sucesivamente 3 actividades que contienen cada una de 10 TextView s y 10 ImageView s
  4. Si hice algo mal, porque no puedo ver nada malo en mi código, pero obviamente hay algo malo.

Gracias.

Editar: tamaños de imagen de la primera actividad:

 -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 

Segunda actividad:

 -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 

Tercera actividad (bloqueo):

 -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 

Y el fondo de la ventana:

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

EDITAR:

OK – Traté de inflar las vistas directamente desde el código para ver si tenía una imagen de buggy. Aquí está mi código de actividad (sólo variables + onCreate como el resto es idéntico)

 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]) { Li("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(); } }); } 

Como se puede ver, solo estoy inflando las vistas directamente, y Li (que es un alias de Log.i(TAG, ...) ) indica cuál fue el último ImageView inflado antes de un accidente.

Corrí la aplicación, vi la primera actividad, luego la segunda, y como antes se bloquea mientras se carga la tercera. La última ID que vi en el logcat fue la última imagen R.drawable.surv_8 . Se trata de un archivo jpeg de 630×377, 45 kib.

Intenté cambiar el orden de los puntos de vista como este:

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

De modo que surv_8 es primero. La aplicación sigue fallando en la última imagen, que es R.drawable.surv_7 . Así que eso no es una cuestión de imagen.

Entonces quité una de las imágenes de mImageViews[2] modo que tenga solamente 7 imágenes en vez de 8. La aplicación está trabajando como esperado: la actividad carga, cuando hago clic en el botón voy al siguiente, y en el último voy a la primera. Sin accidente.

Sin embargo, puedo decir que la actividad 1 y 2 permanecer en la memoria. De hecho, eche un vistazo a los tiempos:

 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 ms en lugar de 1,5-2,5 segundos para cargar la actividad.

¿Por qué se queda en la memoria?

=> ** ¿Necesito liberar manualmente todas las imágenes en onDestroy() ? **

=> ¿Qué piensas sobre esto: http://androidactivity.wordpress.com/2011/09/24/solution-for-outofmemoryerror-bitmap-size-exceeds-vm-budget/
Con esta solución podría cargar como 10 mapas de bits en todo el mundo, y me gustaría liberar algunos mientras se carga otra actividad?

Siento que la respuesta de esta pregunta no fuera realmente posible tener con los elementos que di en mi pregunta.

De hecho, como se menciona en otra pregunta aquí: ¿Por qué todos mis mapas de bits sobresalen un 200%? , Pongo mis imágenes en la carpeta dibujable, que, en una pantalla xhdpi como la mía, hará que Android muestre todo dos veces (para coincidir con xhdpi contra mdpi), y resultará en un uso de 400% de memoria (dos veces el ancho y dos veces el altura tan 4 veces tantos pixeles).

Una cosa más, ya que no quiero que el usuario pueda volver atrás (vea el finish() justo después de la startActivity() ), esto es lo que pongo en la 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(); } } 

Este es un código que tengo aquí en StackOverflow (por ejemplo aquí: Drawable vs solo reutilizable Bitmap mejor con la memoria? ).

Estos son mis comentarios sobre este código y por qué cada línea es importante:

  • El setCallback(null) elimina la referencia a la actividad, para que pueda ser recogida de basura. Si no lo hace, todas las vistas permanecerán en la memoria.
  • También es necesario llamar desde onPause , porque onDestroy no está garantizado para ser llamado.
  • Puede que tenga que hacer una recolección de basura después de esto, con el fin de ayudar al GC a entender que algunas referencias a objetos que se hicieron huérfanos y que pueden ser GC'd. Simplemente llame a System.gc() .

Este es el código de onPause resultante, con el diseño principal de su actividad teniendo el id top_layout :

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

Podría ser simplemente que el mapa de bits que está poniendo en el ImageView es muy grande. Aquí hay algunas cosas que puede probar:

Abandonar el diseño xml y sólo inflar y mostrar el problema ImageView o ImageViews y ver si de esta manera usted está recibiendo el error.

También compruebe cada una de sus imágenes para ver qué tamaño tienen realmente. El tamaño que tomarán en la memoria es aproximadamente 4 * width * height (en bytes, donde el ancho y la altura son el tamaño de la imagen en píxeles).

Dado que tus instancias de Bitmap están ocupando memoria en el montón, imagino que estás utilizando Android 3 o superior. En estas versiones es donde van las instancias de Bitmap (como cualquier otra instancia de Java). En Android 2.xy más abajo, las instancias de mapa de bits se asignaron en alguna memoria extraída. Sabiendo esto, asegúrese de que ese uso de montón de alta que está recibiendo es de hecho de las imágenes, y no de algunas otras instancias que se crean mucho. Intente depurar sus constructores y ver quién llama qué cuando se inicia / ejecuta la aplicación.

Según las observaciones de Bicou, es posible que desee cargar manualmente sus imágenes a través de BitmapFactory y, al hacerlo, puede bajarlas (a través del objeto Options). Además, trate de mantener un contador de cuántos bytes de imagen en la memoria se suma incrementando con 4 * width * altura de cada imagen que está cargando en un objeto Bitmap.

La mejor manera de optimizar el uso de la memoria con mapas de bits en su caso es guardar una lista de sus ImageViews hacer esto antes de terminar la llamada () para cada ImageView:

 ((BitmapDrawable) myImageView.getDrawable()).getBitmap().recycle(); myImageView = null; 
  • Android - MediaController pierde la actividad
  • Límite de la memoria de la aplicación Android / iOS (aclaración) y Adobe Air
  • Fuga de memoria AsyncTask
  • ListView grande que contiene imágenes en Android
  • Android: ¿Cómo probar la pérdida de memoria en una aplicación?
  • Gestión de la memoria android fuera del montón
  • ¿Cuánta memoria recibe cada proceso de Android?
  • Cómo utilizar Eclipse Memory Analyzer Tool (MAT) para analizar un hashmap
  • Cómo hacer que la aplicación de Android que "come" la memoria RAM
  • Pérdida de memoria a pesar de usar weakreference
  • Android java.lang.OutOfMemoryError?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.