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.
- The first button will be used to launch a second
activity NewActivity (which will just display a screen with some
text on it).
- The second button calls the finish()
method of Activity.
- The editable textfield will be used for input of a user
name.
This is accomplished by
- Having the class implement the interface OnClickListener.
- 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.
- 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):
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:
- A Toast,
which displays the message as a transient floating window on the screen
(with the duration controlled by the variable TL).
- 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:
- 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.
- Data may be stored on the
device using a relational database, SQLite.
- 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.
- 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.
- 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.
- 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
- 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).
- 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
- Calling onSaveInstanceState(Bundle) on each view
in the view hierarchy having an id.
- Saving the id of the view currently receiving focus.
- 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
- Track onRestoreInstanceState(Bundle) and onSaveInstanceState(Bundle)
invocations.
- Illustrate how to insert and retrieve private user data
in the saved Bundles for these methods.
- 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.
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.
|
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."
|
There are several methods
available for persistent data storage in Android programming:
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.
|
- 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.
- 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.
- 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.
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).
In the event that onSaveInstanceState(Bundle)
is called, it will be called before onStop() but it could be called
before or after onPause().
|
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.
|
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:
- Message passing which involves Intent
class
- Publish/subscribe or pub/sub which also involves Intent class and BroadcastReceiver class
- Late binding
and method calls that are used for accessing ContentProviders and local (in-process) Services
- Late binding
and Inter-process Procedure Communication (IPC) for invoking remote (AIDL) Services
0 coment�rios:
Post a Comment