Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Reproducing Activity Life-Cycle Scenarios for Testing

Author: Daniel Fowler
Published? true
FormatLanguage: WikiFormat

Problem:

Apps should be resilient to the Activity Lifecycle. Developers need to know how to reproduce different lifecycle scenarios.

Solution:

Use logging to get a good understanding of the Activity Lifecycle. Lifecycle scenarios are then easier to reproduce for App testing.

Discussion:

Android is designed for life on the go, where a user is engaged in multiple tasks, taking calls, checking email, sending SMS, social networking, taking pictures, accessing the Internet, running Apps and more, maybe including some work! As such a device can have multiple Apps and hence many Activities loaded in memory. The foreground App and its current Activity can be interrupted and paused at any moment. Apps, and hence Activities, that are paused can be removed from memory to free up space for newly started Apps. An App has a lifecycle which it cannot control as it is the Android operating system that starts, monitors, pauses, resumes and destroys the Apps Activities. Yet an Activity does know what is going on, as Activities are instantiated, hidden and destroyed various functions are called. This allows the Activity to keep track on what the operating system is doing to the App, see the Understanding the Android Activity Life Cycle. App developers become familiar with the functions invoked when an Activity starts:

  • onCreate(Bundle savedInstanceState){...};
  • onStart(){...};
  • onResume(){...};

And the functions called when an Activity is paused and then removed from memory (destroyed).

  • onPause(){...};
  • onStop(){...};
  • onDestroy(){..};

It is easy to see them in action create a simple App in Android Studio. Then in the first loaded Activity override the above functions, calling through to the super class versions. Add a call to Log.d() to pass in the name of the App and the function being invoked. (Here for a basic App the Application name is set to MyAndroid and an Empty Activity is used. Note by default Studio uses the AppCompatActivity class, which is derived from Activity. The MainActivity code will look like this:

package com.example.myandroid;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d("MyAndroid", "onCreate");
    }
    @Override
    protected void onStart(){
        super.onStart();
        Log.d("MyAndroid", "onStart");
    }
    @Override
    protected void onResume(){
        super.onResume();
        Log.d("MyAndroid", "onResume");
    }
    @Override
    protected void onPause(){
        super.onPause();
        Log.d("MyAndroid", "onPause");
    }
    @Override
    protected void onStop(){
        super.onStop();
        Log.d("MyAndroid", "onStop");
    }
    @Override
    protected void onDestroy(){
        super.onDestroy();
        Log.d("MyAndroid", "onDestroy");
    }
}

There are other ways to print the program name and function name in Java but hard coded strings are used here for convenience and simplicity. Run the program on a device (virtual or physical) to see the debug messages in logcat. If Logcat is not visible open the Android Monitor (at the bottom of Studio or use the View then Tool Windows menu, or press Alt-6). When the back key is pressed the three teardown messages are seen:

To see only the messages from the App add a LogCat filter. Use the end drop down above the Logcat display area and select Edit Filter Configuration. In Create New LogCat Filter give the filter a name, here MyAndroid is used. The Log Tag is used for filtering (the first parameter of the Log.d() call, again set to MyAndroid. LogCat will now show only the messages explicitly sent from the App.

The Logcat output can be further simplfied by changing the header configuration (use the gear icon to the left of the Logcat area). The LogCat output can be cleared by clicking the trash can icon to the left of the Logcat area. It is useful to have a clean sheet before performing an action to watch for more messages. Now test the functions called when an App is opened over the MyAndroid program. First add the function for onRestart() and the debug message.

@Override
public void onRestart() {
    super.onRestart();
    Log.d("MyAndroid","onRestart");
}

Run the program, click the Home button, then select the App again from tiled App button.

LogCat shows the usual start up function sequence, then when the Home button is pressed onPause() and onStop() run, but no onDestroy(). The program is not ending but effectively sleeping. When the program is selected again it is not reloaded so no onCreate() executes, instead onRestart() is called.

Run the program again then swipe it from the screen in tiled view to kill it (or go into Apps via Settings and select the program and press the Force Close button). Then start the App again.

The usual start up functions are invoked then the Activity "sleeps". No onDestroy() is seen as the second instance is run.

The above has shown different lifecycle scenarios.

  • Normal start up and then finish.
  • Start up, pause and then restart.
  • Start up, pause, forced removal from memory and then start up again.

These scenarios result in different sequences of lifecycle functions being executed. Using these scenarios when testing ensures an App performs correctly for a user. The techniques shown here can be extended when implementing additional overridden functions. The techniques also apply to using Fragments in an Activity and testing their lifecycle.

See Also:

Understanding the Android Activity Life Cycle

[1]

[2]

[3]

Download:

The source code for this project can be downloaded from http://tekeye.uk/android/examples/download/lifecycletesting.zip.
No records found.