Sans Pareil Technologies, Inc.

Key To Your Business

Lesson 3


Activity



An Activity is an application component that provides a screen with which users can interact in order to do something, such as dial the phone, take a photo, send an email, or view a map. Each activity is given a window in which to draw its user interface. The window typically fills the screen, but may be smaller than the screen and float on top of other windows.

An application usually consists of multiple activities that are loosely bound to each other. Typically, one activity in an application is specified as the "main" activity, which is presented to the user when launching the application for the first time. Each activity can then start another activity in order to perform different actions. Each time a new activity starts, the previous activity is stopped, but the system preserves the activity in a stack (the "back stack"). When a new activity starts, it is pushed onto the back stack and takes user focus. The back stack abides to the basic "last in, first out" stack mechanism, so, when the user is done with the current activity and presses the Back button, it is popped from the stack (and destroyed) and the previous activity resumes.

When an activity is stopped because a new activity starts, it is notified of this change in state through the activity's lifecycle callback methods. There are several callback methods that an activity might receive, due to a change in its state—whether the system is creating it, stopping it, resuming it, or destroying it—and each callback provides you the opportunity to perform specific work that's appropriate to that state change. For instance, when stopped, your activity should release any large objects, such as network or database connections. When the activity resumes, you can reacquire the necessary resources and resume actions that were interrupted. These state transitions are all part of the activity lifecycle.

Creating an Activity



To create an activity, you must create a subclass of Activity (or an existing subclass of it). In your subclass, you need to implement callback methods that the system calls when the activity transitions between various states of its lifecycle, such as when the activity is being created, stopped, resumed, or destroyed. The two most important callback methods are:
  • onCreate() - You must implement this method. The system calls this when creating your activity. Within your implementation, you should initialize the essential components of your activity. Most importantly, this is where you must call setContentView() to define the layout for the activity's user interface.
  • onPause() - The system calls this method as the first indication that the user is leaving your activity (though it does not always mean the activity is being destroyed). This is usually where you should commit any changes that should be persisted beyond the current user session (because the user might not come back).There are several other lifecycle callback methods that you should use in order to provide a fluid user experience between activities and handle unexpected interuptions that cause your activity to be stopped and even destroyed.

You must declare your activity in the manifest file in order for it to be accessible to the system. To declare your activity, open your manifest file and add an element as a child of the element. For example:

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



There are several other attributes that you can include in this element, to define properties such as the label for the activity, an icon for the activity, or a theme to style the activity's UI. The android:name attribute is the only required attribute - it specifies the class name of the activity. Once you publish your application, you should not change this name, because if you do, you might break some functionality, such as application shortcuts.

See the <activity> element reference for more information about declaring your activity in the manifest.

Activity Lifecycle



Managing the lifecycle of your activities by implementing callback methods is crucial to developing a complete application. The lifecycle of an activity is directly affected by its association with other activities, its task and back stack.
An activity can exist in three states:
  • Resumed - The activity is in the foreground of the screen and has user focus. (This state is also sometimes referred to as "running".)
  • Paused - Another activity is in the foreground and has focus, but this one is still visible. That is, another activity is visible on top of this one and that activity is partially transparent or doesn't cover the entire screen. A paused activity is completely alive (the Activity object is retained in memory, it maintains all state and member information, and remains attached to the window manager), but can be killed by the system in extremely low memory situations.
  • Stopped - The activity is completely obscured by another activity (the activity is now in the "background"). A stopped activity is also still alive (the Activity object is retained in memory, it maintains all state and member information, but is not attached to the window manager). However, it is no longer visible to the user and it can be killed by the system when memory is needed elsewhere.
If an activity is paused or stopped, the system can drop it from memory either by asking it to finish (calling its finish() method), or simply killing its process. When the activity is opened again (after being finished or killed), it must be created all over.

Lifecycle Callbacks



When an activity transitions into and out of the different states described above, it is notified through various callback methods. All of the callback methods are hooks that you can override to do appropriate work when the state of your activity changes. The following skeleton activity includes each of the fundamental lifecycle methods:

public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // The activity is being created.
    }
    @Override
    protected void onStart() {
        super.onStart();
        // The activity is about to become visible.
    }
    @Override
    protected void onResume() {
        super.onResume();
        // The activity has become visible (it is now "resumed").
    }
    @Override
    protected void onPause() {
        super.onPause();
        // Another activity is taking focus (this activity is about to be "paused").
    }
    @Override
    protected void onStop() {
        super.onStop();
        // The activity is no longer visible (it is now "stopped")
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // The activity is about to be destroyed.
    }
}


Note: Your implementation of these lifecycle methods must always call the superclass implementation before doing any work, as shown in the examples above.
Taken together, these methods define the entire lifecycle of an activity. By implementing these methods, you can monitor three nested loops in the activity lifecycle:
  • The entire lifetime of an activity happens between the call to onCreate() and the call to onDestroy(). Your activity should perform setup of "global" state (such as defining layout) in onCreate(), and release all remaining resources in onDestroy(). For example, if your activity has a thread running in the background to download data from the network, it might create that thread in onCreate() and then stop the thread in onDestroy().
  • The visible lifetime of an activity happens between the call to onStart() and the call to onStop(). During this time, the user can see the activity on-screen and interact with it. For example, onStop() is called when a new activity starts and this one is no longer visible. Between these two methods, you can maintain resources that are needed to show the activity to the user. For example, you can register a BroadcastReceiver in onStart() to monitor changes that impact your UI, and deregister it in onStop() when the user can no longer see what you are displaying. The system might call onStart() and onStop() multiple times during the entire lifetime of the activity, as the activity alternates between being visible and hidden to the user.
  • The foreground lifetime of an activity happens between the call to onResume() and the call to onPause(). During this time, the activity is in front of all other activities on screen and has user input focus. An activity can frequently transition in and out of the foreground—for example, onPause() is called when the device goes to sleep or when a dialog appears. Because this state can transition often, the code in these two methods should be fairly lightweight in order to avoid slow transitions that make the user wait.

Figure illustrates these loops and the paths an activity might take between states. The rectangles represent the callback methods you can implement to perform operations when the activity transitions between states.
activity_lifecycle(1)

Saving State



The introduction to Activity Lifecycle briefly mentions that when an activity is paused or stopped, the state of the activity is retained. This is true because the Activity object is still held in memory when it is paused or stopped—all information about its members and current state is still alive. Thus, any changes the user made within the activity are retained so that when the activity returns to the foreground (when it "resumes"), those changes are still there.

However, when the system destroys an activity in order to recover memory, the Activity object is destroyed, so the system cannot simply resume it with its state intact. Instead, the system must recreate the Activity object if the user navigates back to it. Yet, the user is unaware that the system destroyed the activity and recreated it and, thus, probably expects the activity to be exactly as it was. In this situation, you can ensure that important information about the activity state is preserved by implementing an additional callback method that allows you to save information about the state of your activity: onSaveInstanceState().

The system calls onSaveInstanceState() before making the activity vulnerable to destruction. The system passes this method a Bundle in which you can save state information about the activity as name-value pairs, using methods such as putString() and putInt(). Then, if the system kills your application process and the user navigates back to your activity, the system recreates the activity and passes the Bundle to both onCreate() and onRestoreInstanceState(). Using either of these methods, you can extract your saved state from the Bundle and restore the activity state. If there is no state information to restore, then the Bundle passed to you is null (which is the case when the activity is created for the first time).

The two ways in which an activity returns to user focus with its state intact: either the activity is destroyed, then recreated and the activity must restore the previously saved state, or the activity is stopped, then resumed and the activity state remains intact.
restore_instance

Note: There's no guarantee that onSaveInstanceState() will be called before your activity is destroyed, because there are cases in which it won't be necessary to save the state (such as when the user leaves your activity using the Back button, because the user is explicitly closing the activity). If the system calls onSaveInstanceState(), it does so before onStop() and possibly before onPause().

However, even if you do nothing and do not implement onSaveInstanceState(), some of the activity state is restored by the Activity class's default implementation of onSaveInstanceState(). The default implementation calls the corresponding onSaveInstanceState() method for every View in the layout, which allows each view to provide information about itself that should be saved. Almost every widget in the Android framework implements this method as appropriate, such that any visible changes to the UI are automatically saved and restored when your activity is recreated. The only work required of you is to provide a unique ID (with the android:id attribute) for each widget you want to save its state. If a widget does not have an ID, then the system cannot save its state.

You can also explicitly stop a view in your layout from saving its state by setting the android:saveEnabled attribute to "false" or by calling the setSaveEnabled() method. Usually, you should not disable this, but you might if you want to restore the state of the activity UI differently.

Although the default implementation of onSaveInstanceState() saves useful information about your activity's UI, you still might need to override it to save additional information. For example, you might need to save member values that changed during the activity's life (which might correlate to values restored in the UI, but the members that hold those UI values are not restored, by default).

Because the default implementation of onSaveInstanceState() helps save the state of the UI, if you override the method in order to save additional state information, you should always call the superclass implementation of onSaveInstanceState() before doing any work. Similarly, you should also call the superclass implementation of onRestoreInstanceState() if you override it, so the default implementation can restore view states.

Note: Because onSaveInstanceState() is not guaranteed to be called, you should use it only to record the transient state of the activity (the state of the UI)—you should never use it to store persistent data. Instead, you should use onPause() to store persistent data (such as data that should be saved to a database) when the user leaves the activity.

A good way to test your application's ability to restore its state is to simply rotate the device so that the screen orientation changes. When the screen orientation changes, the system destroys and recreates the activity in order to apply alternative resources that might be available for the new screen configuration. For this reason alone, it's very important that your activity completely restores its state when it is recreated, because users regularly rotate the screen while using applications.

Handling configuration changes



Some device configurations can change during runtime (such as screen orientation, keyboard availability, and language). When such a change occurs, Android recreates the running activity (the system calls onDestroy(), then immediately calls onCreate()). This behaviour is designed to help your application adapt to new configurations by automatically reloading your application with alternative resources that you've provided (such as different layouts for different screen orientations and sizes).

If you properly design your activity to handle a restart due to a screen orientation change and restore the activity state as described above, your application will be more resilient to other unexpected events in the activity lifecycle.

The best way to handle such a restart is to save and restore the state of your activity using onSaveInstanceState() and onRestoreInstanceState() (or onCreate()), as discussed earlier.

Events



Events encapsulate actions or changes that occur in the system.  Within an application or activity, the events of most interest are the ones that occur on a widget.

Listeners are objects that listen to events.  Listener instances are registered with the system and the system invokes the appropriate method when an event occurs.  Listeners are usually single method interfaces that apply to a specific type of event.  Most listener interfaces in the Android API are defined as nested interfaces within the corresponding widget.
  • High-level events occur only on specific types of widgets.  For example, the EditorAction event only occurs on certain types of widgets such as the EditText widget.
  • Low-level events occur on all types of widgets.  The listeners for these events are nested within the parent View class.  For example, the OnClickListener can be applied to any widget.

Activity implementations that listen to events may choose to implement the appropriate listener interface(s), or use separate listener implementations (stand-alone classes or anonymous inner classes) to listen to events.
  • Check boxes and radio buttons fire the CheckedChanged event when the selection state of the widget is changed.  A radio group also fires a CheckedChanged event when the selection state of any of the child radio buttons is changed.
  • AdapterView class instances (eg. Spinner) define the OnItemSelectedListener interface.  The onItemSelected method is used to perform application specific actions when an item is selected. The OnItemSelectedListener interface also includes a onNothingSelected method, which needs to be implemented, but rarely needs specific actions to be performed.
  • Seek bars define the OnSeekBarChangeListener which defines three methods: onStartTrackingTouch, onProgressChanged and onStopTrackingTouch.
  • When a user touches a touchscreen device, a Touch event is fired by the system.  Most often this event is translated into a higher level event by the Android system and handled accordingly by the activity.  Activities that need to handle touch events directly (games for instance) can implement the OnTouchListener interface.  The low-level event listeners typically defined listener methods that return a boolean value.  The boolean value is used to indicate whether the event should be bubbled up the widget hierarchy (from parent view at the bottom the view stack to the top) or not.  Returning true from these methods stop the event propagation.

Application Testing



Android SDK provides integration with JUnit for automating testing of applications.  Android Studio generates two separate directory structures for test sources
  • androidTest - For testing UI components and application behavior while running on a device/emulator.  These are referred to as Instrumented Unit Tests.
  • test - For pure unit tests of business logic.  These are compiled and executed on your local JVM.
  • The Android Testing Support Library provides a set of APIs that allow you to quickly build and run test code for your apps, including JUnit 4 and functional user interface (UI) tests. The library includes the following instrumentation-based APIs that are useful when you want to automate your tests:
  • AndroidJUnitRunner: JUnit 4-compatible test runner for Android
  • Espresso: UI testing framework; suitable for functional UI testing within an app
  • UI Automator: UI testing framework; suitable for cross-app functional UI testing across system and installed apps

Refer the official Testing Concepts page for further details.

Work on
Lab2