The Activity Lifecycle

Every instance of Activity has a lifecycle. During this lifecycle, an activity transitions between three states: running, paused, and stopped. For each transition, there is an Activity method that notifies the activity of the change in its state.

Understand the Activity Lifecycle

You are already acquainted with one of these methods – onCreate(Bundle). The OS calls this method after the activity instance is created but before it is put on screen.

Typically, an activity overrides onCreate(...) to prepare the specifics of its user interface:

  • inflating widgets and putting them on screen (in the call to (setContentView(int))

  • getting references to inflated widgets

  • setting listeners on widgets to handle user interaction

  • connecting to external model data

    It is important to understand that you never call onCreate(...) or any of the other Activity lifecycle methods yourself. You override them in your activity subclasses, and Android calls them at the appropriate time.

Logging the Activity Lifecycle

In this section, you are going to override lifecycle methods to eavesdrop on QuizActivity’s lifecycle. Each implementation will simply log a message informing you that the method has been called.

Making log messages

In Android, the android.util.Log class sends log messages to a shared system-level log. Log has several methods for logging messages. Here is the one that you will use most often in this book:

public static int d(String tag, String msg)

The d stands for “debug” and refers to the level of the log message, Adding log statement to onCreate(…)

   public class QuizActivity extends AppCompatActivity {
 ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            Log.d(TAG, "onCreate(Bundle) called");
            setContentView(R.layout.activity_quiz);

 ...
        }
    }

Overriding more lifecycle methods:

  private void updateQuestion(){
        int question = mQuestionBank[mCurrentIndex].getmTextResId();
        mQuestionTextView.setText(question);
    }
    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG, "onStart() called");
    }
    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onPause() called");
    }
    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume() called");
    }
    @Override
    public void onStop() {
        super.onStop();
        Log.d(TAG, "onStop() called");
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy() called");
    }
 ...
}

You may have been wondering about the @Override annotation. This asks the compiler to ensure that the class actually has the method that you are attempting to override. For example, the compiler would be able to alert you to the following misspelled method name:

public class QuizActivity extends AppCompatActivity {
 @Override
 public void onCreat(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_quiz);
 }

 ...

Device configurations and alternative resources

Rotating the device changes the device configuration. The device configuration is a set of characteristics that describe the current state of an individual device. The characteristics that make up the configuration include screen orientation, screen density, screen size, keyboard type, dock mode, language, and more.

An alternative landscape layout

The FrameLayout will replace the LinearLayout. FrameLayout is the simplest ViewGroup and does not arrange its children in any particular manner. In this layout, child views will be arranged according to their android:layout_gravity attributes.

The TextView, LinearLayout, and Button children of the FrameLayout need android:layout_gravity attributes. The Button children of the LinearLayout will stay exactly the same.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    <TextView
        android:id="@+id/question_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:padding="24dp" />
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical|center_horizontal"
        android:orientation="horizontal" >
        <Button
            android:id="@+id/true_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:text="@string/true_button" />
        <Button
            android:id="@+id/false_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|right"
            android:text="@string/false_button" />
    </LinearLayout>
    <Button
        android:id="@+id/next_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|right"
        android:text="@string/next_button" />
</FrameLayout>

Note that Android destroys the current activity and creates a new one whenever any runtime configuration change occurs.

Saving Data Across Rotation

Android does a great job of providing alternative resources at the right time. However, destroying and re-creating activities on rotation can cause headaches, too, like GeoQuiz’s bug of reverting back to the first question when the device is rotated.

To fix this bug, the post-rotation QuizActivity needs to know the old value of mCurrentIndex. You need a way to save this data across a runtime configuration change, like rotation. One way to do this is to override the Activity method:

protected void onSaveInstanceState(Bundle outState)

This method is normally called by the system before onPause(), onStop(), and onDestroy().

The default implementation of onSaveInstanceState(…) directs all of the activity’s views to save their state as data in the Bundle object. A Bundle is a structure that maps string keys to values of certain limited types.

You have seen this Bundle before. It is passed into onCreate(Bundle):

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

When you override onCreate(…), you call onCreate(…) on the activity’s superclass and pass in the bundle you just received. In the superclass implementation, the saved state of the views is retrieved and used to re-create the activity’s view hierarchy.

Overriding onSaveInstanceState(Bundle)

add a constant that will be the key for the key-value pair that will be stored in the bundle.

public class QuizActivity extends AppCompatActivity {
  private static final String TAG = "QuizActivity";
  private static final String KEY_INDEX = "index";
  private Button mTrueButton;
  ...

Next, override onSaveInstanceState(…) to write the value of mCurrentIndex to the bundle with the constant as its key.

mNextButton.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View v) {
      mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
      updateQuestion();
    }
  });

 updateQuestion();
 }
 @Override
 public void onSaveInstanceState(Bundle savedInstanceState) {
     super.onSaveInstanceState(savedInstanceState);
     Log.i(TAG, "onSaveInstanceState");
     savedInstanceState.putInt(KEY_INDEX, mCurrentIndex);
 }

Finally, in onCreate(…), check for this value. If it exists, assign it to mCurrentIndex.

...
 if (savedInstanceState != null) {
 mCurrentIndex = savedInstanceState.getInt(KEY_INDEX, 0);
 }

 updateQuestion();
 }

getint

Returns the value associated with the given key, or defaultValue if no mapping of the desired type exists for the given key.

Run GeoQuiz and press Next. No matter how many device rotations you perform, the newly minted QuizActivity will “remember” what question you were on.

The Activity Lifecycle, Revisited

Overriding onSaveInstanceState(Bundle) is not just for handling rotation. An activity can also be destroyed if the user navigates away for a while and Android needs to reclaim memory.

Android will never destroy a running activity to reclaim memory.

When onSaveInstanceState(…) is called, the data is saved to the Bundle object. That Bundle object is then stuffed into your activity’s activity record by the OS.

Last updated

Was this helpful?