Actividades En Android: Concepto Y Ciclo De Vida

La actividad es un componente encargado de la mayoría de interacción con el usuario en las aplicaciones Android. Con el fin de estudiar dicho elemento, en este tutorial veremos su concepto, el ciclo de vida que recorren en el sistema operativo y sus posibles estados. Así mismo iremos viendo un ejemplo en Android Studio que nos permita practicar su uso.

Definición De Actividad

En cuestiones de diseño y navegación la actividad es el bloque de construcción encargado por la creación de una ventana que nuestra aplicación usa para dibujar y recibir eventos del sistema (gestos táctiles como tap, swipe, press and hold, etc.) .

Ejemplos de actividades Android

Y en código es representada con la clase Activity para resolver este modelo.

Android está hecho para que el usuario esté centrado en una sola actividad para que sea la única cosa que esté haciendo, lo que quiere decir que tan solo una puede estar en primer plano. Y Las demás se mueven a segunda instancia. Esta capacidad es originada por el ciclo de vida de las actividades que veremos más adelante.

Otro punto a tener en cuenta es que si tu app tiene varias actividades es posible marcar una como la actividad principal (main). Esto nos permite que al presionar el icono de la app desde el Launcher, dicha actividad sea iniciada.

Registro De Actividad En El Manifesto

Debido a que la actividad es un bloque de construcción de una app, se convierte en un componente necesario de registrar en el archivo AndroidManifest.xml.

Para hacerlo incluimos dentro del nodo <application> la etiqueta <activity> como se muestra en el siguiente código:

<manifest ...>

    <application...>
        <activity android:name=".ExampleActivity">
        </activity>
    </application>

</manifest>

La etiqueta de la actividad tiene una gran cantidad de atributos, pero el único que debe incluirse es android:name. Su valor es el nombre de clase, el cual puede ser escrito con el paquete completo (ej. com.ejemplo.app1.MainActivity), o usar el carácter punto para dejar que se complete con el paquete usado en la etiqueta <manifest> (ej. .MainActivity).

Declarar La Actividad Principal

Si deseamos que una actividad sea inicia por el launcher como el punto de entrada principal una vez iniciamos la app, debemos usar el mecanismo de Intent filters.

Este tema es complejo y lo veremos a fondo en otro tutorial. Sin embargo, en palabras sencillas los intent filters dotan a una actividad la capacidad de responder a llamadas de otras apps o del mismo sistema. En nuestro caso deseamos que el launcher de Android inicie a nuestra actividad principal.

Para lograrlo incluimos un elemento <intent-filter> en la etiqueta de la actividad como vemos a continuación:

<activity
    android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Donde <action> especifica el nombre de la acción. En este caso android.intent.action.MAIN especifica que la actividad se inicia como principal.

Y <category> determina el tipo de componente que debería manejar el mensaje de lanzamiento. Nosotros seleccionaremos al launcher con android.intent.category.LAUNCHER.

Con esto la actividad será marcada como principal.

Ciclo De Vida De Una Actividad

Debido al diseño del sistema operativo Android que hablamos anteriormente, una actividad puede atravesar los siguientes estados para mantener el flujo entre apps y eventos del sistema:

  • Inexistente
  • Detenida
  • Pausada
  • En marcha

La siguiente tabla nos muestra que sucede en memoria, UI y el proceso de primer plano, cuando la actividad pasa por su ciclo de vida:

Estado ¿En memoria? ¿Visible al usuario? ¿En primer plano?
Inexistente No No No
Detenida Si No No
Pausada Si Si No
En Marcha Si Si Si

El cambio entre un estado es disparado a través de métodos tipo callback llamados por Android (mas precisamente el componente ActivityManager). En este diagrama podemos ver los nombres de dichos métodos cuando una actividad va desde la construcción hasta su destrucción:

Ciclo de vida estándar de una actividad

Puedes ver un resumen de sus usos comunes al momento de escribir el código de las Actividades.

Método onCreate()

Se dispara cuando el sistema crea una nueva instancia en memoria de la actividad.

Es el lugar donde se incluye la mayor de la inicialización. Tareas prioritarias como:

  • Inflar la UI de la actividad con setContentView()
  • Agregar fragmentos
  • Obtener referencias de views con findViewById()
  • Bindear datos a los view
  • Iniciar consultas iniciales a fuentes de datos
  • Crear instancias de los componentes de tu arquitectura

Luego veremos que su parámetro de tipo Bundle nos permite guardar valores simples asociados a la actividad para luego restaurarlos.

Método onStart()

Este método es llamado luego de onCreate(). En el podemos escribir instrucciones asociadas a la UI, ya que la actividad es visible al usuario. Sin embargo no es muy usado ya que son pocos los casos donde es trascendental (registros de componentes, librerías, etc.) su implementación.

Método onRestart()

El método onRestart()se ejecuta solo cuando la actividad se ha puesto en el estado Detenida. Este es ejecutado antes de onStart() y luego de onStop() . Su uso no es muy popular, pero es de utilidad cuando deseas diferenciar entre una recreación y un reinicio.

Método onResume()

Se ejecuta antes de que la actividad se ponga En Marcha para interactuar con el usuario en primer plano. Normalmente en su interior se suele ejecutar animaciones, iniciar la aplicación Camara, cargar datos actualizados, etc.

Método onPause()

Como vimos en el diagrama, onPause() es llamado cuando el sistema quita del primer plano a la actividad y así entrar en el estado Pausada. Del cuadro de la sección anterior se deduce que en este estado la acción es parcialmente visible, así que en esta callback podemos ejecutar instrucciones de actualización de UI si el caso lo amerita.

Método onStop()

Es llamado antes de llegar al estado Detenida donde la actividad no será visible para el usuario. En consecuencia, aquí minimizaremos los recursos y acciones que requieran visibilidad. Ejemplos de ello sería pausar animaciones o usar una baja frecuencia de obtención de ubicaciones si se usa Google Play services location APIs.

Método onDestroy()

El sistema llama a este método antes de destruir la instancia de la actividad (estado Inexistente). En su interior incluiremos instrucciones de limpieza de recursos asociados (un buen ejemplo serían hilos generados en la creación).

Importante: Debes llamar al supermétodo de todos los métodos nombrados para que la actividad cumpla con sus acciones por defecto en el sistema, de lo contrario se lanzaran excepciones.

En la implementación puedes sobrescribir todo el ciclo de vida como muestro en el siguiente ejemplo:

public class AlgunaActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_alguna);
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onRestart() {
        super.onRestart();
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
    }
}

 

Ejemplo De Actividades En Android Studio

Con el fin de que interiorices el conocimiento de las actividades en Android he creado un ejemplo en el cual usarás una actividad para sumar dos números al presionar un botón. Adicionalmente habrá otro botón para que obtengas la raíz del resultado de la suma. El siguiente wireframe bosqueja rápidamente el propósito de la interacción en UI:

Mockup de app Android

Crear Nuevo Proyecto Android Studio

Abre Android Studio y crea un nuevo proyecto llamado Ejemplo Actividades. Añade el paquete y ubicación que desees y presiona Next. En mi caso el setup me quedaría así:

Crear nuevo proyecto en Android Studio

En seguida, presiona Next dejando con el target por defecto que Android Studio nos sugiere (en el momento en que escribo el tutorial es la API 15; Android 4.0.3 IceCreamSandwich).

Target Android Devices API 15

En la siguiente pantalla nos saldrá un menú con varios tipos de actividades prefabricados de Android Studio.  Puedes elegir alguna de ellas para incorporarla como actividad principal inicial en tu nuevo proyecto. Sin embargo, nosotros marcaremos Add No Activity para comenzar en blanco y ver luego como añadir una actividad (esto por cuestiones lúdicas). Al finalizar presiona Finish.

Añadir Nueva Actividad

Explora tu folder app del proyecto Android Studio hasta el paquete ejemploactividades y presiona click derecho. Seguido, elige la opción New > Activity > Empty Activity . Este tipo de actividad está vacía, por lo que su layout se mantiene básico a diferencia de las demás opciones con otros fines especializados.

Crear nueva empty activity

Ahora aparecerá un asistente que te guiará en la creación de la nueva actividad.

Creación de SumaActivity

Esta será tu actividad para la suma de los dos números, así que llámala SumaActivity. Deja los demás datos por defecto y marca Launcher Activity para especificar en el manifesto que será la principal. Confirma con Finish.

Si revisas el código de la actividad verás algo como esto:

public class SumaActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_suma);
    }
}

E incluso Android Studio añadió automáticamente la declaración en el AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.herpro.ejemploactividades">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".SumaActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Modificar Layout Para La Suma

Abre el layout res/activity_suma.xml para agregar los views que viste en el boceto. Necesitaremos usar campos de edición (EditText) para recibir las entradas de los números, etiquetas para los símbolos (TextView) y el resultado; también botones (Button) para el cálculo de la suma y la apertura de la actividad de raíz. Todos ellos debes acoplarlos con un ConstraintLayout para la distribución. Te comparto el código final por si aún no tienes claro el uso de layouts:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".SumaActivity">

    <EditText
        android:id="@+id/number_a_input"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:hint="0"
        android:gravity="center"
        android:inputType="number"
        android:maxLength="2"
        app:layout_constraintEnd_toStartOf="@+id/plus_label"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="1" />

    <TextView
        android:id="@+id/plus_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:text="+"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintBottom_toBottomOf="@+id/number_a_input"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.45"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.666" />

    <EditText
        android:id="@+id/number_b_input"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:hint="0"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:gravity="center"
        android:inputType="number"
        android:maxLength="2"
        app:layout_constraintEnd_toStartOf="@+id/sum_label"
        app:layout_constraintStart_toEndOf="@+id/plus_label"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="2" />

    <TextView
        android:id="@+id/sum_label"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:textAppearance="@style/TextAppearance.AppCompat.Large"
        app:layout_constraintBottom_toBottomOf="@+id/number_b_input"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:text="= 0"
        tools:text="= 3" />

    <Button
        android:id="@+id/add_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Sumar"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/number_b_input"
        app:layout_constraintVertical_bias="0.119" />

    <Button
        android:id="@+id/get_root_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Raíz"
        app:layout_constraintEnd_toEndOf="@+id/add_button"
        app:layout_constraintStart_toStartOf="@+id/add_button"
        app:layout_constraintTop_toBottomOf="@+id/add_button" />
</android.support.constraint.ConstraintLayout>

Loguear Ciclo De Vida De SumaActivity

Para el posterior estudio de los diferentes estados que atraviesa la actividad SumaActivity debes loguear las callbacks vistas hasta ahora. Para ello sobrescribe cada método en el código Java y agrega el método Log.d() con un mensaje alusivo al método por el que transita.

public class SumaActivity extends AppCompatActivity {

    private static final String TAG = SumaActivity.class.getSimpleName();
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_suma);
        Log.d(TAG, "onCreate()");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d(TAG, "onStart()");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d(TAG, "onRestart()");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d(TAG, "onResume()");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d(TAG, "onPause()");
    }

    @Override
    protected void onStop() {
        super.onStop();
        Log.d(TAG, "onStop()");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy()");
    }
   
}

Obtener Instancias De Views Y Asignar Eventos

Como ves, aún no sumas los números que tu usuario estaría ingresando. Esto se debe a que no tienes las instancias de los campos de texto y tampoco has asignado una escucha al botón de suma. La solución entonces, consiste en tomar los views con findViewById() y setear una escucha anónima del OnClickListener al botón para operar los valores.

public class SumaActivity extends AppCompatActivity {

    private static final String TAG = SumaActivity.class.getSimpleName();
    private static final String STATE_SUM_OUTPUT = "sumOutput";

    private EditText mNumberAInput;
    private EditText mNumberBInput;
    private TextView mSumTotalLabel;
    private Button mAddButton;
    private Button mGetRootButton;

    private String sumOutput;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_suma);
        Log.d(TAG, "onCreate()");

        // Instancias
        mNumberAInput = findViewById(R.id.number_a_input);
        mNumberBInput = findViewById(R.id.number_b_input);
        mSumTotalLabel = findViewById(R.id.sum_label);
        mAddButton = findViewById(R.id.add_button);
        mGetRootButton = findViewById(R.id.get_root_button);

        // Actualización
        mAddButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addUserNumbers();
            }
        });

    }

    private void addUserNumbers() {
        String inputA = mNumberAInput.getText().toString();
        String inputB = mNumberBInput.getText().toString();
        int numberA = inputA.isEmpty() ? 0 : Integer.parseInt(inputA);
        int numberB = inputB.isEmpty() ? 0 : Integer.parseInt(inputB);

        sumOutput = String.format(Locale.getDefault(), "= %d", numberA + numberB);
        mSumTotalLabel.setText(sumOutput);
    }

}

El valor de la suma junto al caracter '=' lo almacenaremos en una variable global llamada sumOutput para mantener su estado.

Ejemplos De Cliclos De Vida

Lanzamiento De Una Actividad

Este es el caso más básico, donde Android abre un proceso nuevo para nuestra app con el fin de referenciar tus actividades. Como ya sabes, lanzar una app requiere que te sitúes en el Launcher y luego presiones el icono de la misma. Así que corre la aplicación y verifica en qué orden son impresos los métodos en el logcat de Android Studio luego del lanzamiento.

Ciclo de vida de una actividad al ser lanzada del Launcher

 

En mi logcat obtuve el recorrido clásico hacia la construcción de SumaActivity, pasando por los 4 estados de forma secuencial como ves en la imagen anterior. El siguiente diagrama me permite explicarme mejor:

Estados de Actividad al ser ejecutada

Navegación Hacia Atrás

Ahora luego de tener la actividad en primer inserta los 2 números y presiona el botón de suma. Si todo sale bien podrás ver el cálculo de la suma:

Actividad para sumar dos números

Seguidamente navega hacia atrás con el Back Button de la barra de navegación del sistema y fíjate en las callbacks del ciclo de vida impresas. Como ves, ahora tienes la proyección reversa del caso de construcción: onPause() > onStop() > onDestroy():

Estados de una actividad al navegar atrás

Luego de presionar el botón volverás al Launcher, pero ahora presiona el icono de la app para iniciarla y ver qué pasó con el resultado de tu suma.

Actividad suma con estado perdido

– ¡Todos tus datos se han perdido!… lo cual, no debería tomarte por sorpresa. Cuando la actividad no estaba en primer plano llegó hasta su estado Inexistente, por lo que la instancia de la actividad fue destruida, lo que implica la destrucción de todo su contenido. Una vez reconstruida por el sistema, tu actividad generó una nueva instancia en memoria con sus valores por defecto.

Presionar El Home Button

Suma de nuevo dos números y esta vez prueba que estados recorre la actividad al presiona el botón Home de la barra de navegación.

Al terminar verás que solo se han llamado a la secuencia onPause() > onStop(). Esto se debe a que el propósito del Home button es ir al Launcher para cambiar de app y realizar otras acciones que el usuario desee.

Estados de actividad al presionar home

El no llegar al estado Inexistente evita que la instancia de la actividad sea destruida, por lo que si abres de nuevo la aplicación presionando su icono verás que se conservan los valores de la suma. Esta vez se invoca onRestart() para diferenciar que la construcción viene de una instancia conservada.

Estados de actividad al reiniciar luego del home

Presionar El Overview Button

Esta vez haz el mismo ejercicio pero presionando el botón Overview de la barra de navegación para abrir la screen de recientes.

Actividad de suma en recents screen

Notarás que el logcat evidencia que se invocan los mismos métodos que el anterior caso.

Estados de actividad al presionar overview button

Al reiniciar tendremos exactamente el flujo onRestart() > onStart() > onResume() y la actividad mantendrá sus valores.

Lanzar Otra Actividad En La Aplicación

Para probar este caso crea la actividad RaizActivity en el proyecto Android Studio.

Crear segunda actividad en android studio

El layout no necesitas modificarlo, pero si deseas puedes poner un TextView para asignar el resultado de la raíz (para pasar el valor de la suma sigue mi tutorial Comunicar Actividades A Través De Intents En Android). Lo que si necesitas hacer es agregar una escucha al botón para iniciar la actividad en SumaActivity.

mGetRootButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        showRootScreen();
    }
});

El inicio de la actividad de raíz lo simplificamos con el método showRootScreen(), donde crearemos un Intent e iniciaremos la actividad con startActivity():

private void showRootScreen() {
    Intent intent = new Intent(this, RaizActivity.class);
    startActivity(intent);
}

Con esto hecho investiga que métodos del ciclo de vida transita SumaActivity al iniciar y al finalizar.

Ir de actividad suma a actividad raíz

Notamos que el sistema conserva la instancia de la actividad de sumar como en los casos anteriores:

Estados actividad al iniciar otra

Y que al presionar el botón Back retoma el foco marcando a onRestart().

Estados actividad al reiniciar ante otra

Iniciar Una Actividad Con Estilo De Dialogo

El siguiente caso es cuando una actividad con estilo de diálogo aparece tomando el foco (no ocurre lo mismo con los diálogos comunes derivados de DialogFragment) , pero nuestra actividad de suma aún es parcialmente visible.

Para probarlo en RaizActivity ve al AndroidManifest.xml y agregar la siguiente línea en su nodo XML:

<activity
    android:name=".RaizActivity"
    android:theme="@style/Theme.AppCompat.Light.Dialog.Alert" />

Ejecuta la app y observa el ciclo de vida.

Estados actividad al iniciar actividad estilo dialogo

Tan solo se llamará a onPause() debido a que la actividad aún es visible, pero no está en primer plano. Por lo que si presionas Back será solo onResume() la callback faltante para llegar al estado En Marcha.

Estados de actividad al volver de diálogo

Cambios De Configuración

Un cambio de configuración fácil de percibir es la rotación de pantalla. Si rotas la actividad de suma verás que esta es reconstruida completamente, pasando por los 4 estados. Por lo que perderemos el valor de nuestra variable de suma (afortunadamente el framework nos ayuda a conservar el contenido de los EditText).

Estados de actividad al rotar la pantalla

El Sistema Termina La App

Android está diseñado para acabar con los procesos de apps de baja prioridad si en el primer plano hay una app que requiere recursos adicionales. Por ejemplo, si estas interactuando con la actividad suma, presionas Home y luego lanzas una app que consuma una gran porción de tu memoria (como el videojuego Vain Glory) es muy probable que Android elimine el proceso de SumaActivity, ya que la probabilidad de supervivencia es más baja cuando el estado de la actividad está más lejano a En Marcha.

Y eso no es todo. Android evita la ejecución de las instrucciones de onDestroy() para eliminar (temporalmente) lo más pronto posible el proceso. Por esta razón este es otro caso donde perdemos el valor de nuestra variable suma.

Habiendo visto todos estos casos ahora verás cómo guardar el valor de suma para recuperarlo aunque la actividad pase al estado de Inexistente.

Guardar Estado Simples De UI

Android te permite guardar el estado de las instancias mediante un objeto Bundle, el cual te permite insertar parejas clave-valor. El momento donde almacenas dichos valores es en el método onSaveInstanceState() donde viene el parámetro bundle. Por ejemplo, para guardar el valor de la suma en nuestro actividad lo añadiremos con el método Bundle.putString()  y le asignamos una clave asociada al valor.

public class SumaActivity extends AppCompatActivity {
    
    private static final String STATE_SUM_OUTPUT = "sumOutput";

...

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(TAG, "onSaveInstanceState()");

        outState.putString(STATE_SUM_OUTPUT, sumOutput);
    }
...

}

(Es necesario que llames al supermétodo para mantener la conservación por defecto de los widgets)

Restaurar Estado De La Actividad

Ya guardada nuestra variable antes de ser destruida la actividad, ahora solo queda restaurarla cuando se vuelva a crear la instancia. Esto lo podemos hacer en onCreate(), ya que su parámetro es el mismo Bundle que contiene la colección de estados tomados en onSaveInstanceState(). Para ello comprobamos que dicho objeto no sea null (puede que no sea una recreación) y usamos los métodos Bundle.get*() para obtener los valores:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_suma);
    Log.d(TAG, "onCreate()");

    // Instancias
    mNumberAInput = findViewById(R.id.number_a_input);
    mNumberBInput = findViewById(R.id.number_b_input);
    mSumTotalLabel = findViewById(R.id.sum_label);
    mAddButton = findViewById(R.id.add_button);
    mGetRootButton = findViewById(R.id.get_root_button);

    if(savedInstanceState!=null){
        sumOutput = savedInstanceState.getString(STATE_SUM_OUTPUT);
    }else {
        sumOutput = "= 0";
    }

    // Actualización
    mSumTotalLabel.setText(sumOutput);
    mAddButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            addUserNumbers();
        }
    });
    mGetRootButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            showRootScreen();
        }
    });

}

Otra opción para cargar el estado guardado es usar el método onRestoreInstanceState() el cual recibe el mismo Bundle solo si se detectó un guardado antes de la destrucción. Este método es ejecutado luego de onStart() y te evita comprobar nulidad. Normalmente usaremos onCreate() para restaurar los estados, pero habrá ocasiones especiales donde este método se ajustará mejor a tus necesidades si tu actividad posee alguna condición particular.

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    sumOutput = savedInstanceState.getString(STATE_SUM_OUTPUT);
}

(Si deseas ver el flujo en mayor extensión de los métodos llamados en el ciclo de vida de la actividad, el repositorio “Complete Android Fragment & Activity Lifecycle Github” de Steve Pomeroy podría serte de utilidad)

¡Muy bien!

Al ya saber guardar/restaurar tus estados en la actividad intenta ejecutar tu app y prueba ponerla en situaciones de destrucción de instancia para ver el efecto de la conservación.

¿Qué tal si rotamos la pantalla?

Conservación estado de instancia actividad al rotar pantalla

¡Perfecto!, el valor de la suma ha sido restaurado. Ahora ya solo queda que pruebes los demás casos por tu cuenta.

Descarga Código De Android Studio

Puedes descargar el proyecto completo del ejemplo para la suma de dos números y mostrar con el siguiente link:

Conclusión

El que comprendas el propósito de una actividad y como funciona su ciclo de vida es uno de los pasos más importantes a la hora de aprender a desarrollar aplicaciones Android. Cuando sabes como el sistema operativo trata a las actividades en diferentes casos, puedes anticiparte para codificar soluciones que mantengan la integridad de datos y la experiencia de usuario intacta
(Si deseas continuar con tu aprendizaje ve al indice de contenido del sitio)