Lifecycle Methods

 In this Topic we shall investigate more directly the Android lifecycle methods discussed under Application Lifecycles. Specifically, we shall
  • Override the lifecycle methods to track how various actions cause them to be invoked.
  • Test the invocation of lifecycle methods in various scenarios.
  • Illustrate the proper time in the lifecycle sequence to store persistent user data.
  • Demonstrate long-term data storage using shared preferences.
  • Illustrate how to insert private user data in the data Bundles stored and retrieved by Android when switching between applications.
  • Illustrate how to examine the content of data Bundles.
  • Show how to examine the amount of memory in use.
  • As we have emphasized in Application Lifecycles, understanding these issues is vital to writing responsive and user-friendly Android applications.

Then, create a class NewActivity by right-clicking on src/com.lightcone.lifecyclemethods, selecting New > Class, and filling out the resulting dialog window as follows:
click Finish and edit the resulting file src/com.lightcone.lifecyclemethods/NewActivity.java to read

    package com.lightcone.lifecyclemethods;
   
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.Toast;
   
    public class NewActivity extends Activity {
           
        private static final int TL = Toast.LENGTH_SHORT;  // Toast.LENGTH_LONG for longer
        private static final String TAG = "LIFECYCLES";
        private static final String AC = "Second Activity: ";
        private String message = "";
            
        /** Lifecycle method: Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.newactivity);
            message = "onCreate() called";
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        } 
        /** Lifecycle method: Called when the activity is becoming visible to user. */
        @Override
        protected void onStart(){
            super.onStart();
            message = "onStart() called";
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        }
       
        /** Lifecycle method: Called when the activity begins interacting with the user. */
        @Override
        protected void onResume(){
            super.onResume();
            message = "onResume() called";
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        }
       
        /** Lifecycle method: Called when the activity is being placed in the background */
        @Override
        protected void onPause(){
            super.onPause();
            message = "onPause() called";
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        }
       
        /** Lifecycle method: Called after the activity has been stopped, prior to restarting */
        @Override
        protected void onRestart(){
            super.onRestart();
            message = "onRestart() called";
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        }
       
        /** Lifecycle method: Called when the activity is no longer visible to the user. */
        @Override
        protected void onStop(){
            super.onStop();
            message = "onStop() called";
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        }
       
        /** Lifecycle method: The final call received before the activity is destroyed. */
        @Override
        protected void onDestroy(){
            super.onDestroy();
            message = "onDestroy() called";
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        }
       
        // Note that this one is not a lifecycle method
        @Override
        protected void onSaveInstanceState (Bundle outState) {
            super.onSaveInstanceState(outState);
            message = "onSaveInstanceState(Bundle outState) called.";
            message += " Bundle mappings = "+outState.size();
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        }
        
        // Note that this one is not a lifecycle method
        @Override
        protected void onRestoreInstanceState (Bundle inState) {
            super.onRestoreInstanceState(inState);
            message = "onRestoreInstanceState(Bundle inState) called.";
            message += " Bundle mappings = "+inState.size();
            Toast.makeText(this, AC+message, TL).show();
            Log.i(TAG, AC+message);
        } 
    }

Finally, since we have created a new activity we must add a line to register it in the manifest file. Open the file AndroidManifest.xml and edit it to read:

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.lightcone.lifecyclemethods"
        android:versionCode="1"
        android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".LifeCycleMethods"
                    android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity android:name=".NewActivity" android:label="NewActivity"> </activity>
        </application>
        <uses-sdk android:minSdkVersion="3" />
    </manifest>

where the added line is indicated in red.

What It Does
The functionality of the code in these two activities is documented by extensive comments, but let's give a concise overview of what it does. Basically, we use the class LifeCycleMethods to put on the screen an initial activity with two Buttons and an EditText field.
  1. The first button will be used to launch a second activity NewActivity (which will just display a screen with some text on it).
  2. The second button calls the finish() method of Activity.
  3. The editable textfield will be used for input of a user name.
This is accomplished by
  1. Having the class implement the interface OnClickListener.
  2. Adding to onCreate() the code
3.   
4.      // Add Click listeners for buttons
5.      View button1 = findViewById(R.id.button1);
6.      button1.setOnClickListener(this);
7.      View button2 = findViewById(R.id.button2);
8.      button2.setOnClickListener(this);
9.      
10.    // Identify EditText field
11.    textfield = (EditText) findViewById(R.id.EditText01);
12. 
to identify and attach event listeners to the widgets.
  1. Implementing the onClick method of OnClickListener to sort out events and act accordingly:
14. 
15.// Process button clicks
16.@Override
17.public void onClick(View v) {
18.    switch(v.getId()){
19.        case R.id.button1:
20.            Intent j = new Intent(this, NewActivity.class);
21.            startActivity(j);
22.            break;
23.        case R.id.button2:
24.            message = AC+"Calling finish()";
25.            Toast.makeText(this, message, TL).show();
26.            Log.i(TAG, message);
27.            finish();
28.    }   
29.}
30. 
The preceding is all similar to manipulations illustrated in earlier projects such as WebView Demo, so we won't elaborate further. This gives us a caricature of the sort of things that a simple application might do (display widgets, respond to events, launch new activities, call finish() when done, ...). Our purpose now is to add some things to document the invocation of lifecycle methods as various actions are taken using this application, so that we gain a deeper understanding of Android lifecycle management.
 Tracking Lifecycle Method Invocations
The classes defined in LifeCycleMethods.java and NewActivity.java each extend Activity and override all seven of its lifecycle methods (see the diagram and table illustrating the lifecycle methods in Application Lifecycles):
  1. onCreate(Bundle savedInstanceState)
  2. onStart()
  3. onResume()
  4. onPause()
  5. onRestart()
  6. onStop()
  7. onDestroy()
In each of the overriden methods we first call through to the superclass and then implement two diagnostic outputs indicating that the particular method has been invoked:
  1. A Toast, which displays the message as a transient floating window on the screen (with the duration controlled by the variable TL).
  2. Output to the LogCat stream, generated by the Log.i output statements.
The diagnostic messages allow us to see visually (on the screen and in the LogCat output) the invocation of various lifecycle callbacks by Android as we perform operations on the device or emulator.

You must call through to the superclass (for example, with super.onPause() in onPause()) for any Android lifecycle method that you override. If you fail to do so, a SuperNotCalledException will be thrown at runtime, leading to a Forced Close of your application.
Using onPause() to Store User Data that Should Persist
From the diagram and table of lifecycle methods in Application Lifecycles we may infer that user data that need to persist should be written to long-term storage when onPause() is invoked,since after onPause() returns there is no guarantee that further lifecycle methods will be executed if the system decides to kill the app while it is in the background to reclaim resources.
The lifecycle method onDestroy() is the last one called in the normal termination of an application. But, to quote directly from the onDestroy() documentation, "... do not count on this method being called as a place for saving data! ... There are situations where the system will simply kill the activity's hosting process without calling this method ..., so it should not be used to do things that are intended to remain around after the process goes away."
Thus, in the activity LifeCycleMethods we also illustrate using onPause() to store user data. In this case, we store any username input in the editable textfield using shared preferences (see a further example of using shared preferences in the Options Menu implemented in the Mapping Demo project).
There are several methods available for persistent data storage in Android programming:
  1. Data may be stored as shared preferences, which are invoked through an interface SharedPreferences that stores data as name-value pairs in XML files on the device.

  1. Data may be stored on the device using a relational database, SQLite.

  1. Data may be written to flat files (on the SD card, for example), using the standard Java I/O streams such as FileInputStream and FileOutputStream.

  1. Data may be stored on and retrieved from a server over a network.
A more complete discussion of storage options may be found in the document Data Storage. For large amounts of data one of the latter three methods should be used, but shared preferences is a very convenient storage method for small things like user names or interface settings.
There are two steps to managing shared preferences after we define a SharedPreferences variable mprefs.
  1. In onCreate() we insert the lines
2.   
3.      // Set up shared prefs to hold username
4.      mPrefs = getSharedPreferences("LSprefs",0);
5.      userName = mPrefs.getString("user_name", "");
6.      textfield.setText(userName);
7.   
The first line uses the method getSharedPreferences(String filename, int mode) that Activity inherits from the abstract class Context to return a SharedPreferences object and assign it to mprefs.
    • The argument filename references a user-defined file to hold the preferences (if the file does not yet exist, it will be created automatically in following steps when an editor is retrieved using SharedPreferences.edit() and then changes are committed using Editor.commit()---see the onPause() method in step 2 below).
    • The argument mode controls access permissions for the shared preferences. The value 0 (equivalent to the Context class constant MODE_PRIVATE) is the default and implies that the file can be accessed only by the calling application or by applications sharing the same user ID. Other possible values are 1 (MODE_WORLD_READABLE) and 2 (MODE_WORLD_WRITEABLE), which extend read and write access to all applications.
We then use the getString(String key, String defaultValue) method of SharedPreferences to retrieve a string value corresponding to the user-defined key user_name and assign it to the String variable userName
    • The second argument of getString is the default value to assign if user_name has no value.
    • If the name-value pair corresponding to the key user_name does not yet exist, it will be created in the editing and commit steps to follow in onPause().
      Finally, we use the
      setText(CharSequence text) method that EditText inherits from TextField to display the value of userName in the EditText widget textfield.
  1. Next, in onPause() we insert the lines
9.   
10.    // Write current userName to shared prefs so that it will persist even if the app is
11.    // killed after onPause() returns.
12. 
13.    userName = textfield.getText().toString();
14.    SharedPreferences.Editor editor = mPrefs.edit();
15.    editor.putString("user_name", userName);
16.    editor.commit();
17. 
In this snippet of code we
    • Use the getText() method of EditText to retrieve the current user name from textfield (which is of type Editable), use toString() to convert it to a String, and assign the value to userName (the toString() method is inherited by Editable from the interface CharSequence).
    • Define a SharedPreferences.Editor called editor and assign to it the value returned by the method edit() that mPrefs inherits from SharedPreferences.
    • Use the method putString(String key, String value) that editor inherits from SharedPreferences.Editor to insert the current value of userName as a name-value pair.
    • Store the changes using the method commit() that editor inherits from SharedPreferences.Editor.
This ensures that the current value of userName in textfield will be written to permanent storage when the application is moved to the background. Thus, it will not be lost even if Android kills the application process while it is in the background. 
Examining and Adding User Data to Android savedInstanceState Bundles
As discussed in Application Lifecycles, Android gives a multitasking experience on devices that have limited resources. One key to that capability is prompt restoration of applications that may have had their tasks killed while in a background state when they are restarted. Android does this by storing the state of the user interface in a data Bundle that can be used to quickly restore the user interface to its last visible state when the application is created again. The key methods in this regard are
  1. onSaveInstanceState(Bundle outState), which is called to retrieve the per-instance state from an activity before it is killed. The Bundle specified by outState will be passed to both onCreate(Bundle) and onRestoreInstanceState(Bundle) if the application is re-initialized at a later time, so that either can be used to restore the user interface (normally this will be done by onCreate(Bundle)). For example, if activity B is launched in front of activity A, and at some point activity A is killed to reclaim resources, when the user returns to activity A the state of the user interface can be restored through onCreate(Bundle) or onRestoreInstanceState(Bundle).

  1. onRestoreInstanceState(Bundle savedInstanceState), which is called after onStart() when the activity is being re-initialized from a previously-saved state, specified by savedInstanceState. The default implementation performs a restore of any view state previously saved by onSaveInstanceState(Bundle). Since this same data Bundle is also passed to onCreate(), in typical implementations the UI state will be restored in onCreate() and it is then not necessary to call onRestoreInstanceState() also. Note that neither of these methods is a lifecycle method. They are often, but not always, called when lifecycle methods are called.
For example, the lifecycle method onPause() will always be called when an activity is placed in the background or is on its way to destruction, but this is not assured for onSaveInstanceState(Bundle).
  • To illustrate with a specific case, suppose a user launches activity B in front of activity A and then navigates back from B to A. That instance of B will never be restored so the system calls onPause() and onStop(), but not onSaveInstanceState(Bundle) on B.

  • As another example, if activity B is launched in front of activity A, onPause() will be called on A but the system may decide not to call onSaveInstanceState(Bundle) on A if it isn't killed during the lifetime of B because in that case the user interface for A will stay intact.
In the event that onSaveInstanceState(Bundle) is called, it will be called before onStop() but it could be called before or after onPause().

The default implementation of Android handles most of the UI per-instance state restoration automatically by
  1. Calling onSaveInstanceState(Bundle) on each view in the view hierarchy having an id.
  2. Saving the id of the view currently receiving focus.
  3. Automatically restoring these properties of the UI by passing the corresponding data Bundle to onRestoreInstanceState(Bundle) and onCreate(Bundle) when an app is reinitialized.
However, the user can also insert private data in the form of name-value pairs into the Bundle that stores the UI state by overriding onRestoreInstanceState(Bundle). This would be of use, for example, in a game where the basic UI is saved automatically by Android but details not captured by each individual view that would be required to restore the last state of the game are not. Thus, in this project we also override the onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle) methods of both activities to
  1. Track onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle) invocations.
  2. Illustrate how to insert and retrieve private user data in the saved Bundles for these methods.

  1. Illustrate how to examine the contents of the data Bundles manipulated by these methods.
In both LifeCycleMethods.java and NewActivity.java the overriden onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle) methods have diagnostic Toast and LogCat outputs similar to those described for the lifecycle methods that should require no further explanation. Thus we concentrate on explaining the additional statements in these methods for LifeCycleMethods.java that illustrate inserting user data into the Bundle and querying the Bundle for its contents.

Notice in all of these implementations that when we override onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle) methods to save additional information not captured by each individual view, we must call through to the superclass in order to ensure that the default view information saved by Android is included in the Bundle.

We shall illustrate storing user information in the data Bundle by storing the current system time in milliseconds each time onSaveInstanceState(Bundle) is invoked, and retrieving that stored time each time onRestoreInstanceState(Bundle) is invoked for the activity LifeCycleMethods. In onSaveInstanceState(Bundle) in LifeCycleMethods.java,
  • First we obtain a system time by using the Java method System.currentTimeMillis() (which returns a long integer that is the difference between the current time and midnight, January 1, 1970 UTC.)
  • We then use the Bundle method putLong(String key long value) to insert the time in milliseconds into the Bundle variable outState as a name-value pair with a key "timeMillis".
Next we explore the contents of the saved Bundle outState by obtaining the set of keys in the Bundle and then displaying the corresponding content. (This should show entries for both the user interface data saved automatically by Android and the private user data inserted in the Bundle above.)
  • We use the Bundle method keySet() to return a Set called set of Strings corresponding to the keys for the name-value pairs in the Bundle.
  • We then convert set to an array sset[] of Objects using the Set method toArray().
  • Then we execute a for-loop over the elements of the array sset[], using the toString() method to convert each Object to a String named keyString, use that key to extract the corresponding value using the Bundle method get(String keyString) followed by a toString() conversion on the resulting Object, and send to the logcat stream a listing of each name-value pair in the Bundle.
Finally, we insert a similar for-loop in the method onRestoreInstanceState(Bundle) to explore the contents of the Bundle inState, and also in the method onCreate(Bundle) since both onRestoreInstanceState(Bundle) and onCreate(Bundle) should receive the same saved instance-state Bundles when an activity is reinitialized.

Note that to guard against a nullPointerException we must test for null on the input Bundles before trying to manipulate them, since they might not exist in particular circumstances. For example, if this is the first time the activity has been run there will be no saved Bundle for its UI state.

                           architectural styles and patterns

              In this post we’ll be talking about architectural patterns used by Android application architecture.
Why understanding of patterns implemented by a certain architecture is important? Because it puts a new architecture we are learning into context of our previous experience and allows us to leverage our previous knowledge and skills.
Android documentation never mentions any pattern; at least I haven’t seen such references. But in reality Android does implement a number of patterns and architectural styles and I will show you this.
Android application architecture is a framework-based application architecture as opposite to a free-style application architecture.
What is the difference? A free-style application written in Java starts from a class with main() method and a developer is free to do pretty much whatever she/he wants.
As opposite to this a framework-based application is based on an existing framework and a developer extends certain classes or implements interfaces provided by the framework to build an application; the application can’t run outside of/without the framework it was built upon. Examples are Java web applications where a developer implements Servlet interface or extends one of its subclasses, or Eclipse RCP application where a developer extends one of Editor and View classes.
Framework-based architectures limit freedom of developers by prescribing what to do and how to do certain things. But in return they eliminate necessity to write a lot of “boilerplate” code and (hopefully) enforce well thought through design patterns.
There are many application frameworks available in Java. However Android team decided to build their own framework. Probably one of the reasons why they did it was necessity to support unique Android Java memory management.
In a “regular” Java an object is kept in memory until it is garbage collected. The garbage collection occurs only if an object does not have any reference to it from other “live” objects (see more details here). In Android it is not so. If a certain GUI gets hidden (it is not visible on the screen), there is no guarantee that it will be kept in memory even if the application intends to use it later. It might be kept in memory alright if Android OS has enough free memory, but it can be garbage collected as well at any moment when the OS finds that it needs more free memory. The same is true for processes as well. If an application process does not show any GUI to the user at the moment, it can be legitimately terminated by Android OS (there is one exception to this rule related to Android Services; we’ll discuss this later).
Here is an example. Let’s assume our application has GUI screen A and GUI screen B. A user accesses screen A first and then screen B; at this point screen A is not visible anymore. This means screen A and all logic that supports it may or may not be kept in memory. Since there is no guarantee that objects associated with screen A exist in memory while screen B is shown, the developer writing screen B logic must not expect that a certain object instantiated for screen A is still in memory. A side result of this is that Android architecture enforces “shared nothing” architectural style for Android applications. This means that different parts of Android application can invoke each other and communicate between them only in an explicit way; there is no shared state between them in-memory.
Well, what happens if the user decides to return back to screen A? She will probably expect to see the GUI in the same state as she left it, right? Here is how Android application framework solves the problem: it introduces so-called lifecycle methods in certain classes an application developer has to implement in order to create the application. Those methods are called by Android application framework at predefined transitional moments, e.g. when a GUI which was shown gets hidden or a GUI which was hidden gets to the forefront again etc. A developer can implement logic for storing and restoring state of objects inside those methods.
Such way of handling hidden GUIs and presence of “Back” button on Android devices makes it necessary to have a sort of GUI stack where the current visible GUI gets on top of the stack and all others are pushed down the stack (“push” stack operation). Pressing “Back” button removes the GUI which was on the top of the stack and shows the one which was below it in the stack (“pop” stack operation). Such stack exists in Android indeed. It is called sometimes “activity stack” and sometimes “back stack” in Android documentation.
Android application architecture follows very modern Model-View-ViewModel (MVVM) architectural pattern in how it handles relationships between GUI and a logic supporting the GUI. This is actually good news for Android developers since this is the latest and greatest architecture for GUI applications at the moment.
MVVM architecture was created in attempt to solve a disparity of skills which occurs when e.g. a Java developer tries to build GUI in Swing or a Visual C++ developer tries to build GUI in MFC. Developers are smart folks and have many skills, but building nice and sexy GUIs requires quite different talents than those the developers usually have. It is work more suitable for GUI designers. Good GUI designers are more artists and a user experience professionals than experts in designing and writing code, so we can’t expect from them writing a good code. Clearly, it would be better if a GUI designer was designing a GUI and a developer was writing a code which implements a logic behind the GUI, but such technologies as Swing or MFC simply do not allow this.
MVVM architecture overcomes this contradiction by clearly separating responsibilities:
  • GUI development is done by a GUI designer in technology more or less natural for this task (XML)
  • Logic behind the GUI is implemented by a developer as ViewModel (which actually means Model of View) component
  • Functional relationships between GUI and ViewModel are implemented through bindings that essentially define rules like “if a button A is clicked, method onButtonAClick() should be called on ViewModel”. Bindings can be written in the code or defined in a declarative way (Android uses both types of bindings)
MVVM architecture is used in one way or another by all modern GUI technologies like Microsoft WPF and Silverlight, Oracle (former Sun) JavaFX, Adobe Flex, AJAX.
We mentioned that different parts of Android application can invoke each other and communicate between them only in an explicit way. How this is done? Android application framework uses several communication patterns:
Seo Directory List

Comments

Popular posts from this blog

What is Android ?