Although the size and complexity of Android applications can vary greatly, their structures will be similar. Figure 2-7 shows the structure of the “Hello World!” app you just built.
As you can see from Table 2-1, an Android application is primarily made up of three pieces: the application descriptor, a collection of various resources, and the application’s source code. If you put aside the AndroidManifest.xml file for a moment, you can view an Android app in this simple way: you have some business logic implemented in code, and everything else is a resource. This basic structure resembles the basic structure of a J2EE app, where the resources correlate to JSPs, the business logic correlates to servlets, and the AndroidManifest.xml file correlates to the web.xml file.
You can also compare J2EE’s development model to Android’s development model. In J2EE, the philosophy of building views is to build them using markup language. Android has also adopted this approach, although the markup in Android is XML. You benefit from this approach because you don’t have to hard-code your application’s views; you can modify the look and feel of the application by editing the markup.
It is also worth noting a few constraints regarding resources. First, Android supports only a linear list of files within the predefined folders under res. For example, it does not support nested folders under the layout folder (or the other folders under res). Second, there are some similarities between the assets folder and the raw folder under res. Both folders can contain raw files, but the files within raw are considered resources and the files within assets are not.So the files within raw will be localized, accessible through resource IDs, and so on. But the contents of the assets folder are considered general-purpose contents, to be used without resource constraints and support. Note that because the contents of the assets folder are not considered resources, you can put an arbitrary hierarchy of folders and files within it. (We’ll
talk a lot more about resources in Chapter 3.)
Analyzing the Notepad Application
Not only do you know how to create a new Android application and run it in the emulator, but you also have a feel for the artifacts of an Android application. Next, we are going to look at the Notepad application that ships with the Android SDK. Notepad’s complexity falls between that of the “Hello World!” app and a full-blown Android application, so analyzing its components will give you some realistic insight into Android development.
Loading and Running the Notepad Application
In this section, we’ll show you how to load the Notepad application into the Eclipse IDE and run it in the emulator. Before we start, you should know that the Notepad application implements several use cases. For example, the user can create a new note, edit an existing note, delete a note,view the list of created notes, and so on. When the user launches the application, there aren’t any saved notes yet, so the user sees an empty note list. If the user presses the Menu key, the application presents him with a list of actions, one of which allows him to add a new note. After he adds the note, he can edit or delete the note by selecting the corresponding menu option.
Follow these steps to load the Notepad sample into the Eclipse IDE:
1. Start Eclipse.
2. Go to File ? New ? Project.
3. In the “New Project” dialog, select Android ? Android Project.
4. In the “New Android Project” dialog, select “Create project from existing source” and set the “Location” field to the path of the Notepad application. Note that the Notepad application is located in c:\AndroidSDK\samples\, which you downloaded earlier. After you set the path, the dialog reads the AndroidManifest.xml file and prepopulates the remaining fields in the “New Android Project” dialog box.
5. Click the “Finish” button.
You should now see the NotesList application in your Eclipse IDE. To run the application, you could create a launch configuration (as you did for the “Hello World!” application), or you can simply right-click the project, choose Run As, and select Android Application. This will launch the emulator and install the application on it. After the emulator has completed loading (you’ll see the date and time displayed in the center of the emulator’s screen), press the Menu button to view the Notepad application. Play around with the application for a few minutes to become familiar with it.
Dissecting the Application
As you can see, the application contains several .java files, a few .png images, three views (under the layout folder), and the AndroidManifest.xml file. If this were a command-line application,you would start looking for the class with the Main method. So what’s the equivalent of a Main method in Android?
Android defines an entry-point activity, also called the top-level activity. If you look in the AndroidManifest.xml file, you’ll find one provider and three activities. The NotesList activity defines an intent-filter for the action android.intent.action.MAIN and for the category android.intent.category.LAUNCHER. When an Android application is asked to run, the host loads the
application and reads the AndroidManifest.xml file. It then looks for, and starts, an activity or activities with an intent-filter that has the MAIN action with a category of LAUNCHER, as shown here:
After the host finds the activity it wants to run, it must resolve the defined activity to an actual class. It does this by combining the root package name and the activity name, which in this case is com.example.android.notepad.NotesList (see Listing 2-1).
Listing 2-1. The AndroidManfiest.xml File
package="com.example.android.notepad"
>
android:label="@string/app_name"
>
android:authorities="com.google.provider.NotePad"
/>
android:label="@string/title_notes_list">
android:name="android.intent.category.LAUNCHER" />
e="vnd.android.cursor.dir/vnd.google.note" />
android:mimeType="vnd.android.cursor.item/vnd.google.note" />
…
The application’s root package name is defined as an attribute of the element in the AndroidManifest.xml file, and each activity has a name attribute.
Once the entry-point activity is determined, the host starts the activity and the onCreate() method is called. Let’s have a look at NotesList.onCreate(), shown in Listing 2-2.
Listing 2-2. The onCreate Method
public class NotesList extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(Notes.CONTENT_URI);
}
getListView().setOnCreateContextMenuListener(this);
Cursor cursor = managedQuery(getIntent().getData(),
PROJECTION, null, null,
Notes.DEFAULT_SORT_ORDER);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.noteslist_item, cursor, new String[] { Notes.TITLE },
new int[] { android.R.id.text1 });
setListAdapter(adapter);
}
}
Activities in Android are usually started with an intent, and one activity can start another activity. The onCreate() method checks whether the current activity’s intent has data (notes).If not, it sets the URI to retrieve the data on the intent. We’ll learn in Chapter 3 that Android accesses data through content providers that operate on URIs. In this case, the URI provides enough information to retrieve data from a database. The constant Notes.CONTENT_URI is defined as a static final in Notepad.java:
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes");
The Notes class is an inner class of the Notepad class. For now, know that the preceding
URI tells the content provider to get all of the notes. If the URI looked something like this
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes/11");
then the consuming content provider would return (update or delete) the note with an ID equal to 11. We will discuss content providers and URIs in depth in Chapter 3.
The NotesList class extends the ListActivity class, which knows how to display listoriented data. The items in the list are managed by an internal ListView (a UI component), which displays the notes in the list vertically (by default). After setting the URI on the activity’s intent, the activity registers to build the context menu for notes. If you’ve played with the application, you probably noticed that context-sensitive menu items are displayed depending on your selection. For example, if you select an existing note, the application displays “Edit note” and “Edit title.” Similarly, if you don’t select a note, the application shows you the “Add note” option.
Next, we see the activity execute a managed query and get a cursor for the result. A managed query means that Android will manage the returned cursor. In other words, if the application has to be unloaded or reloaded, neither the application nor the activity has to worry about positioning the cursor, loading it, or unloading it. The parameters to managedQuery(),shown in
Table 2-2, are interesting.
We will discuss managedQuery() and its sibling query() later in this section and also in Chapter 3. For now, realize that a query in Android returns tabular data. The projection parameter allows you to define the columns you are interested in. You can also reduce the overall result set and sort the result set using a SQL order-by clause (such as asc or desc).Also note that an
Android query must return a column named _ID to support retrieving an individual record. Moreover, you must know the type of data returned by the content provider—whether a column contains a string, int, binary, or the like.
After the query is executed, the returned cursor is passed to the constructor of SimpleCursorAdapter, which adapts records in the dataset to items in the user interface (ListView). Look closely at the parameters passed to the constructor of SimpleCursorAdapter:
SimpleCursorAdapter adapter =
new SimpleCursorAdapter(this, R.layout.noteslist_item,
cursor, new String[] { Notes.TITLE }, new int[] { android.R.id.text1 });
Specifically, look at the second parameter: an identifier to the view that represents the items in the ListView. As you’ll see in Chapter 3, Android provides an auto-generated utility class that provides references to the resources in your project. This utility class is called the R class because its name is R.java. When you compile your project, the AAPT generates the R class for you from the resources defined within your res folder. For example, you could put all your string resources into the values folder and the AAPT will generate a public static identifier for each string. Android supports this generically for all of your resources. For example,in the constructor of SimpleCursorAdapter, the NotesList activity passes in the identifier of the view that displays an item from the notes list. The benefit of this utility class is that you don’t have to hard-code your resources and you get compile-time reference checking. In other words, if a resource is deleted, the R class will lose the reference and any code referring to the resource will not compile.
Let’s look at another important concept in Android that we alluded to earlier: the onListItemClick method of NotesList (see Listing 2-3).
Listing 2-3. The onListItemClick Method
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
String action = getIntent().getAction();
if (Intent.ACTION_PICK.equals(action) ||
Intent.ACTION_GET_CONTENT.equals(action)) {
setResult(RESULT_OK, new Intent().setData(uri));
} else {
startActivity(new Intent(Intent.ACTION_EDIT, uri));
}
}
The onListItemClick method is called when a user selects a note in the UI. The method demonstrates that one activity can start another activity. When a note is selected, the method creates a URI by taking the base URI and appending the selected note’s ID to it. The URI is then passed to startActivity() with a new intent. startActivity() is one way to start an activity: it starts an activity but doesn’t report on the results of the activity after it completes. Another way to start an activity is to use startActivityForResult(). With this method, you can start another activity and register a callback to be used when the activity completes. For example,you’ll want to use startActivityForResult() to start an activity to select a contact because you want that contact after the activity completes.
At this point, you might be wondering about user interaction with respect to activities.For example, if the running activity starts another activity, and that activity starts an activity,and so on, then what activity can the user work with? Can she manipulate all the activities simultaneously, or is she restricted to a single activity? Actually, activities have a defined lifecycle. They’re maintained on an activity stack, with the running activity at the top. If the running activity starts another activity, the first running activity moves down the stack and the new activity moves to the top. Activities lower in the stack can be in a so-called “paused” or “stopped” state. A paused activity is partially or fully visible to the user; a stopped activity is not visible to the user. The system can kill paused or stopped activities if it deems that resources are needed elsewhere.
Let’s move on to data persistence now. The notes that a user creates are saved to an actual database on the device. Specifically, the Notepad application’s backing store is a SQLite database. The managedQuery() method that we discussed earlier eventually resolves to data in a database, via a content provider. Let’s examine how the URI, passed to managedQuery(), results in the execution of a query against a SQLite database. Recall that the URI passed to managedQuery() looks like this:
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes");
Content URIs always have this form: content://, followed by the authority, followed by a general segment (context-specific). Because the URI doesn’t contain the actual data, it somehow results in the execution of code that produces data. What is this connection?How is the URI reference resolved to code that produces data? Is the URI an HTTP service or a web service? Actually, the URI, or the authority portion of the URI, is configured in the AndroidManifest.xml file as a content provider:
android:authorities="com.google.provider.NotePad"/>
When Android sees a URI that needs to be resolved, it pulls out the authority portion of it and looks up the ContentProvider class configured for the authority. In the Notepad application, the AndroidManifest.xml file contains a class called NotePadProvider configured for the com.google.provider.NotePad authority. Listing 2-4 shows a small portion of the class.
Listing 2-4. The NotePadProvider Class
public class NotePadProvider extends ContentProvider
{
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs,String sortOrder) {}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {}
@Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {}
@Override
public String getType(Uri uri) {}
@Override
public boolean onCreate() {}
private static class DatabaseHelper extends SQLiteOpenHelper {}
@Override
public void onCreate(SQLiteDatabase db) {}
@Override
public void onUpgrade(SQLiteDatabase db,
int oldVersion, int newVersion) {
//...
}
}
}
Clearly, you can see that the NotePadProvider class extends the ContentProvider class. The ContentProvider class defines six abstract methods, four of which are CRUD (Create, Read, Update, Delete) operations. The other two abstract methods are onCreate() and getType(). onCreate() is called when the content provider is created for the first time. getType() provides
the MIME type for the result set (you’ll see how MIME types work when you read Chapter 3).The other interesting thing about the NotePadProvider class is the internal DatabaseHelper class, which extends the SQLiteOpenHelper class. Together, the two classes take care of initializing the Notepad database, opening and closing it, and performing other database tasks.
Interestingly, the DatabaseHelper class is just a few lines of custom code (see Listing 2-5), while the Android implementation of SQLiteOpenHelper does most of the heavy lifting.
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + NOTES_TABLE_NAME + " ("
+ Notes._ID + " INTEGER PRIMARY KEY,"
+ Notes.TITLE + " TEXT,"
+ Notes.NOTE + " TEXT,"
+ Notes.CREATED_DATE + " INTEGER,"
+ Notes.MODIFIED_DATE + " INTEGER"
+ ");");
}
//…
}
As shown in Listing 2-5, the onCreate() method creates the Notepad table. Notice that the class’s constructor calls the superclass’s constructor with the name of the table. The superclass will call the onCreate() method only if the table does not exist in the database. Also notice that one of the columns in the Notepad table is the _ID column we discussed in the section “Dissecting the Application.” Now let’s look at one of the CRUD operations: the insert() method (see Listing 2-6).
Listing 2-6. The insert() Method
//…
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(NOTES_TABLE_NAME, Notes.NOTE, values);
if (rowId > 0) {
Uri noteUri = ContentUris.withAppendedId(
NotePad.Notes.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
The insert() method uses its internal DatabaseHelper instance to access the database and then inserts a notes record. The returned row ID is then appended to the URI and a new URI is returned to the caller.
At this point, you should be familiar with how an Android application is laid out. You should be able to navigate your way around Notepad, as well as some of the other samples in the Android SDK. You should be able to run the samples and play with them. Now let’s look at the overall lifecycle of an Android application.
As you can see from Table 2-1, an Android application is primarily made up of three pieces: the application descriptor, a collection of various resources, and the application’s source code. If you put aside the AndroidManifest.xml file for a moment, you can view an Android app in this simple way: you have some business logic implemented in code, and everything else is a resource. This basic structure resembles the basic structure of a J2EE app, where the resources correlate to JSPs, the business logic correlates to servlets, and the AndroidManifest.xml file correlates to the web.xml file.
You can also compare J2EE’s development model to Android’s development model. In J2EE, the philosophy of building views is to build them using markup language. Android has also adopted this approach, although the markup in Android is XML. You benefit from this approach because you don’t have to hard-code your application’s views; you can modify the look and feel of the application by editing the markup.
It is also worth noting a few constraints regarding resources. First, Android supports only a linear list of files within the predefined folders under res. For example, it does not support nested folders under the layout folder (or the other folders under res). Second, there are some similarities between the assets folder and the raw folder under res. Both folders can contain raw files, but the files within raw are considered resources and the files within assets are not.So the files within raw will be localized, accessible through resource IDs, and so on. But the contents of the assets folder are considered general-purpose contents, to be used without resource constraints and support. Note that because the contents of the assets folder are not considered resources, you can put an arbitrary hierarchy of folders and files within it. (We’ll
talk a lot more about resources in Chapter 3.)
Analyzing the Notepad Application
Not only do you know how to create a new Android application and run it in the emulator, but you also have a feel for the artifacts of an Android application. Next, we are going to look at the Notepad application that ships with the Android SDK. Notepad’s complexity falls between that of the “Hello World!” app and a full-blown Android application, so analyzing its components will give you some realistic insight into Android development.
Loading and Running the Notepad Application
In this section, we’ll show you how to load the Notepad application into the Eclipse IDE and run it in the emulator. Before we start, you should know that the Notepad application implements several use cases. For example, the user can create a new note, edit an existing note, delete a note,view the list of created notes, and so on. When the user launches the application, there aren’t any saved notes yet, so the user sees an empty note list. If the user presses the Menu key, the application presents him with a list of actions, one of which allows him to add a new note. After he adds the note, he can edit or delete the note by selecting the corresponding menu option.
Follow these steps to load the Notepad sample into the Eclipse IDE:
1. Start Eclipse.
2. Go to File ? New ? Project.
3. In the “New Project” dialog, select Android ? Android Project.
4. In the “New Android Project” dialog, select “Create project from existing source” and set the “Location” field to the path of the Notepad application. Note that the Notepad application is located in c:\AndroidSDK\samples\, which you downloaded earlier. After you set the path, the dialog reads the AndroidManifest.xml file and prepopulates the remaining fields in the “New Android Project” dialog box.
5. Click the “Finish” button.
You should now see the NotesList application in your Eclipse IDE. To run the application, you could create a launch configuration (as you did for the “Hello World!” application), or you can simply right-click the project, choose Run As, and select Android Application. This will launch the emulator and install the application on it. After the emulator has completed loading (you’ll see the date and time displayed in the center of the emulator’s screen), press the Menu button to view the Notepad application. Play around with the application for a few minutes to become familiar with it.
Dissecting the Application
As you can see, the application contains several .java files, a few .png images, three views (under the layout folder), and the AndroidManifest.xml file. If this were a command-line application,you would start looking for the class with the Main method. So what’s the equivalent of a Main method in Android?
Android defines an entry-point activity, also called the top-level activity. If you look in the AndroidManifest.xml file, you’ll find one provider and three activities. The NotesList activity defines an intent-filter for the action android.intent.action.MAIN and for the category android.intent.category.LAUNCHER. When an Android application is asked to run, the host loads the
application and reads the AndroidManifest.xml file. It then looks for, and starts, an activity or activities with an intent-filter that has the MAIN action with a category of LAUNCHER, as shown here:
After the host finds the activity it wants to run, it must resolve the defined activity to an actual class. It does this by combining the root package name and the activity name, which in this case is com.example.android.notepad.NotesList (see Listing 2-1).
Listing 2-1. The AndroidManfiest.xml File
>
>
/>
android:mimeType="vnd.android.cursor.item/vnd.google.note" />
…
The application’s root package name is defined as an attribute of the
Once the entry-point activity is determined, the host starts the activity and the onCreate() method is called. Let’s have a look at NotesList.onCreate(), shown in Listing 2-2.
Listing 2-2. The onCreate Method
public class NotesList extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
Intent intent = getIntent();
if (intent.getData() == null) {
intent.setData(Notes.CONTENT_URI);
}
getListView().setOnCreateContextMenuListener(this);
Cursor cursor = managedQuery(getIntent().getData(),
PROJECTION, null, null,
Notes.DEFAULT_SORT_ORDER);
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.noteslist_item, cursor, new String[] { Notes.TITLE },
new int[] { android.R.id.text1 });
setListAdapter(adapter);
}
}
Activities in Android are usually started with an intent, and one activity can start another activity. The onCreate() method checks whether the current activity’s intent has data (notes).If not, it sets the URI to retrieve the data on the intent. We’ll learn in Chapter 3 that Android accesses data through content providers that operate on URIs. In this case, the URI provides enough information to retrieve data from a database. The constant Notes.CONTENT_URI is defined as a static final in Notepad.java:
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes");
The Notes class is an inner class of the Notepad class. For now, know that the preceding
URI tells the content provider to get all of the notes. If the URI looked something like this
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes/11");
then the consuming content provider would return (update or delete) the note with an ID equal to 11. We will discuss content providers and URIs in depth in Chapter 3.
The NotesList class extends the ListActivity class, which knows how to display listoriented data. The items in the list are managed by an internal ListView (a UI component), which displays the notes in the list vertically (by default). After setting the URI on the activity’s intent, the activity registers to build the context menu for notes. If you’ve played with the application, you probably noticed that context-sensitive menu items are displayed depending on your selection. For example, if you select an existing note, the application displays “Edit note” and “Edit title.” Similarly, if you don’t select a note, the application shows you the “Add note” option.
Next, we see the activity execute a managed query and get a cursor for the result. A managed query means that Android will manage the returned cursor. In other words, if the application has to be unloaded or reloaded, neither the application nor the activity has to worry about positioning the cursor, loading it, or unloading it. The parameters to managedQuery(),shown in
Table 2-2, are interesting.
We will discuss managedQuery() and its sibling query() later in this section and also in Chapter 3. For now, realize that a query in Android returns tabular data. The projection parameter allows you to define the columns you are interested in. You can also reduce the overall result set and sort the result set using a SQL order-by clause (such as asc or desc).Also note that an
Android query must return a column named _ID to support retrieving an individual record. Moreover, you must know the type of data returned by the content provider—whether a column contains a string, int, binary, or the like.
After the query is executed, the returned cursor is passed to the constructor of SimpleCursorAdapter, which adapts records in the dataset to items in the user interface (ListView). Look closely at the parameters passed to the constructor of SimpleCursorAdapter:
SimpleCursorAdapter adapter =
new SimpleCursorAdapter(this, R.layout.noteslist_item,
cursor, new String[] { Notes.TITLE }, new int[] { android.R.id.text1 });
Specifically, look at the second parameter: an identifier to the view that represents the items in the ListView. As you’ll see in Chapter 3, Android provides an auto-generated utility class that provides references to the resources in your project. This utility class is called the R class because its name is R.java. When you compile your project, the AAPT generates the R class for you from the resources defined within your res folder. For example, you could put all your string resources into the values folder and the AAPT will generate a public static identifier for each string. Android supports this generically for all of your resources. For example,in the constructor of SimpleCursorAdapter, the NotesList activity passes in the identifier of the view that displays an item from the notes list. The benefit of this utility class is that you don’t have to hard-code your resources and you get compile-time reference checking. In other words, if a resource is deleted, the R class will lose the reference and any code referring to the resource will not compile.
Let’s look at another important concept in Android that we alluded to earlier: the onListItemClick method of NotesList (see Listing 2-3).
Listing 2-3. The onListItemClick Method
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Uri uri = ContentUris.withAppendedId(getIntent().getData(), id);
String action = getIntent().getAction();
if (Intent.ACTION_PICK.equals(action) ||
Intent.ACTION_GET_CONTENT.equals(action)) {
setResult(RESULT_OK, new Intent().setData(uri));
} else {
startActivity(new Intent(Intent.ACTION_EDIT, uri));
}
}
The onListItemClick method is called when a user selects a note in the UI. The method demonstrates that one activity can start another activity. When a note is selected, the method creates a URI by taking the base URI and appending the selected note’s ID to it. The URI is then passed to startActivity() with a new intent. startActivity() is one way to start an activity: it starts an activity but doesn’t report on the results of the activity after it completes. Another way to start an activity is to use startActivityForResult(). With this method, you can start another activity and register a callback to be used when the activity completes. For example,you’ll want to use startActivityForResult() to start an activity to select a contact because you want that contact after the activity completes.
At this point, you might be wondering about user interaction with respect to activities.For example, if the running activity starts another activity, and that activity starts an activity,and so on, then what activity can the user work with? Can she manipulate all the activities simultaneously, or is she restricted to a single activity? Actually, activities have a defined lifecycle. They’re maintained on an activity stack, with the running activity at the top. If the running activity starts another activity, the first running activity moves down the stack and the new activity moves to the top. Activities lower in the stack can be in a so-called “paused” or “stopped” state. A paused activity is partially or fully visible to the user; a stopped activity is not visible to the user. The system can kill paused or stopped activities if it deems that resources are needed elsewhere.
Let’s move on to data persistence now. The notes that a user creates are saved to an actual database on the device. Specifically, the Notepad application’s backing store is a SQLite database. The managedQuery() method that we discussed earlier eventually resolves to data in a database, via a content provider. Let’s examine how the URI, passed to managedQuery(), results in the execution of a query against a SQLite database. Recall that the URI passed to managedQuery() looks like this:
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/notes");
Content URIs always have this form: content://, followed by the authority, followed by a general segment (context-specific). Because the URI doesn’t contain the actual data, it somehow results in the execution of code that produces data. What is this connection?How is the URI reference resolved to code that produces data? Is the URI an HTTP service or a web service? Actually, the URI, or the authority portion of the URI, is configured in the AndroidManifest.xml file as a content provider:
When Android sees a URI that needs to be resolved, it pulls out the authority portion of it and looks up the ContentProvider class configured for the authority. In the Notepad application, the AndroidManifest.xml file contains a class called NotePadProvider configured for the com.google.provider.NotePad authority. Listing 2-4 shows a small portion of the class.
Listing 2-4. The NotePadProvider Class
public class NotePadProvider extends ContentProvider
{
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs,String sortOrder) {}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {}
@Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {}
@Override
public String getType(Uri uri) {}
@Override
public boolean onCreate() {}
private static class DatabaseHelper extends SQLiteOpenHelper {}
@Override
public void onCreate(SQLiteDatabase db) {}
@Override
public void onUpgrade(SQLiteDatabase db,
int oldVersion, int newVersion) {
//...
}
}
}
Clearly, you can see that the NotePadProvider class extends the ContentProvider class. The ContentProvider class defines six abstract methods, four of which are CRUD (Create, Read, Update, Delete) operations. The other two abstract methods are onCreate() and getType(). onCreate() is called when the content provider is created for the first time. getType() provides
the MIME type for the result set (you’ll see how MIME types work when you read Chapter 3).The other interesting thing about the NotePadProvider class is the internal DatabaseHelper class, which extends the SQLiteOpenHelper class. Together, the two classes take care of initializing the Notepad database, opening and closing it, and performing other database tasks.
Interestingly, the DatabaseHelper class is just a few lines of custom code (see Listing 2-5), while the Android implementation of SQLiteOpenHelper does most of the heavy lifting.
private static class DatabaseHelper extends SQLiteOpenHelper {
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + NOTES_TABLE_NAME + " ("
+ Notes._ID + " INTEGER PRIMARY KEY,"
+ Notes.TITLE + " TEXT,"
+ Notes.NOTE + " TEXT,"
+ Notes.CREATED_DATE + " INTEGER,"
+ Notes.MODIFIED_DATE + " INTEGER"
+ ");");
}
//…
}
As shown in Listing 2-5, the onCreate() method creates the Notepad table. Notice that the class’s constructor calls the superclass’s constructor with the name of the table. The superclass will call the onCreate() method only if the table does not exist in the database. Also notice that one of the columns in the Notepad table is the _ID column we discussed in the section “Dissecting the Application.” Now let’s look at one of the CRUD operations: the insert() method (see Listing 2-6).
Listing 2-6. The insert() Method
//…
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long rowId = db.insert(NOTES_TABLE_NAME, Notes.NOTE, values);
if (rowId > 0) {
Uri noteUri = ContentUris.withAppendedId(
NotePad.Notes.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
The insert() method uses its internal DatabaseHelper instance to access the database and then inserts a notes record. The returned row ID is then appended to the URI and a new URI is returned to the caller.
At this point, you should be familiar with how an Android application is laid out. You should be able to navigate your way around Notepad, as well as some of the other samples in the Android SDK. You should be able to run the samples and play with them. Now let’s look at the overall lifecycle of an Android application.