Android Applications for Java ME Developers
Do you remember writing all the code for a Java ME application? A bunch of class files, with one class extending MIDLet and containing startApp? You had models, views, and controllers; hopefully in separate classes with proper modular design. The view classes contained the code to create your UI, usually with Canvas since the Screens and MIDP UI widgets were so ugly.
The corresponding list for Android is as follows: Android Manifest, Activities, Services, Intents, Content Providers, Broadcast Receivers, main.xml, and strings.xml. While you could write an application without Services, Content Providers, or Broadcast Receivers, you really do need these for the most interesting applications. I'll start with the manifest, as this is the most direct match for something you are already familiar with.
Android Manifest
The Java Application Description file has been replaced by the Android Manifest, an XML file — AndroidManifest.xml — which lists all the components that make up your application. If you do not list a component in the manifest Android will not know about it. Every component in an application is listed in a manifest.
Like the JAD file, the manifest specifies the name of the application and the icon shown to the user. Unlike Java ME, Android applications do not have a specific class such as MIDlet dedicated to being the application entry point. Instead, the Android Manifest file specifies the class to be started when the application is launched. Look for the activity tag that contains android.intent.action.MAIN and android.intent.category.LAUNCHER. That is an entry point of the application. It is possible to have multiple entry points, so do not be surprised if occasionally you see a manifest with more than one activity tag containing android.intent.action.MAIN and android.intent.category.LAUNCHER.
These tags are another major purpose of the manifest file. They indicate how to launch the components that make up the application, or more specifically, what messages the components respond to.
Lastly, the manifest file contains permissions. You had a lot of fun with those when you wrote your Java ME applications, didn't you? Well, you are not going to escape them in Android. The good news is that permissions and permission handling in Android actually make sense. Permissions are there to let the end user know what your application needs access to, and to let them decide if it should have access. Permissions are consistent across all Android handsets, and operate the same way regardless of carrier or manufacturer. Hurrah!
There is a lot of other stuff in the manifest but that will be the subject of other articles.
Activities and the Activity Stack
The basic idea is that any part of an application that interacts with the end user, that is, appears on the screen, is an Activity. Activities represent the code associated with a single screen in an application – for each screen in an application, you will create a separate Activity.
In Java ME as you move from one section of an application to another you simply set a different screen onto the current display. However, in Android there is an explicit architecture for handling a new screen. As you move from screen to screen in your application you push new Activities on a view stack. More precisely, as you move from one part of an application to another, you launch a new Activity, which causes Android to push the new Activity to the top of the stack. The launching Activity will remain on the stack below the current Activity, ready to resume when the current Activity is done. For example, pressing the back key finishes the current Activity and resumes the previous one on the stack.
So now you are thinking, "Okay, I can write some code to make a view and bundle it into an Activity. I even have a bunch of Canvas based code to make my UI just the way I like it." Hold on there. Unlike Java ME, where most of the built in screen widgets looked terrible and nobody used them, the Android UI elements actually look very good and can be customized to allow your application to have its own look and feel.
main.xml and Friends
The other part of an Activity are the associated XML files. An Activity has a layout containing a number of views, an options menu that appears when the menu key is pressed, and contextual menus that appear when you touch and hold a view. Options menus should contain menu items that apply to the Activity as a whole; contextual menus are for choices that apply to the currently selected views.
Each Activity has associated XML files that specify the layout of the screen, views, and menus when the Activity is on the top of the stack. You will be creating one layout XML file for each Activity you create in your application. You may also be creating an options menu XML file. This is where a consistent and well thought out naming scheme will help you. You will find the layout XML files in the layout subdirectory under the res (resources) directory and the options menu in the menu subdirectory. Images, drawings, and the other visual components are placed in the neighboring drawables directory. By default, an application project contains one XML file called main containing a default layout.
Google supplies a layout editor for creating the UI in a drag and drop manner. Interface Builder it is not. Someday it may be usable, but as of now you are probably better off finding a third party UI editor, or editing the XML files by hand. If you liked writing code to create your UI, editing XML files by hand should make you very happy.
I know that some of you will still feel strongly about coding your own UI at this point. Let me give you two other reasons to use XML files: orientation and screen size. Unlike Java ME applications, Android applications need to be able to rotate the screen to match the current orientation of the phone. If you code your UI, then you will need to write all the code to handle the changes in orientation that will occur. This starts to be a lot of code to write and debug. Also, Google is committed to increasing the number of display sizes and aspect ratios supported so hand coding and testing the UI is going to get a lot harder and more tedious quickly. (If you are still determined to code your own UI, as you might need to for a game or some truly dynamic UI system, Android does support code-built user interfaces, Canvas views, and views for Open GL ES graphics.)
Android has pretty much all the standard layout managers and view items that you would expect, and a few you might not. To receive events from and change the state of view widgets, you need to assign them an ID. In your code, you will locate the view object by ID, then assign an event listener or change its state as needed. I'll get into IDs in a moment and in some detail as these are rather critical, but I have to talk about string values first.
strings.xml
I mentioned the images for the UI are in the drawables directory. The strings for the UI are in the values directory in the file strings.xml. The strings file contains all the strings that your application will display, each identified by an ID for easy reference in the other XML files and in your code.
Why the separation? Internationalization and localization, of course! Unlike Java ME, Android has full internationalization and localization support built in, and Android handsets ship with a large number of languages by default, so there is no good reason your application should not eventually be able support all of them. If you follow a simple rule to internationalize your application at the start, even if you never plan on localizing your application for anything other than your own language, you will be in a far, far better position to take advantage of international demand and markets. If your application is good enough, you might be surprised at the number of people who will help you create localizations for their language for free. Trust me, you would rather put up one application that is able to support lots of languages from the start than try to deal with internationalizing later, or worse, keep track of a whole bunch of separate applications, one for each language.
What is the rule? If you will display a string where someone can see it, put the string in the strings.xml file and reference it by ID in your code or in other XML files. This includes the application name in the Android Manifest file! When you create a new localization, a directory with a strings.xml file will be created for the localization. You may then edit that strings file to contain the appropriate text for that language. The directory name will encode the language and region applicable to the enclosed strings.xml file.
The main.xml file may be localized the same way. In addition to language localizations for layout you may also add variations for orientation of the device (between portrait and landscape) and different screen sizes.
IDs
I have mentioned IDs a couple of times. IDs are not something that exist within Java ME and so take a bit of getting used to. Within strings.xml, main.xml, and other files you assign an ID for each string, view, image, etc. These IDs are all collected together into a single R.java file that is generated automatically for you. In your code, you reference the ID with the notation R.string.name, R.drawable.name, R.layout.name, etc. In the string case name is the name of the string within the string.xml file; in the other cases it is the name of the XML files themselves. Within the XML files, you use the notation @+name when specifying the ID and @type/nameto reference the ID. The @+ notation indicates to the build system that name is an ID that should be added to the R.java file if not already present.
If you can, use the ID form of a method. However, some methods expect a string, drawable, or some other type, so you need to obtain the corresponding type before you make the method call. Sometimes you can use Context.gettype(). If not, the call you need is Resource.gettype().
Hello World Example
This is an example of all the components listed above in a project, including two localizations. Why two localizations? Because I really want you to think about internationalizing and localizing your code from the start. Note that I have changed this version of Hello World slightly to show you internationalizing the application name in the Android system in addition to internationalizing the application itself.
Android Manifest
The Android Manifest, AndroidManifest.xml, is located in the base project directory. In this example, the application name is specified via the ID of a string value, allowing the application name to be localized when the application is localized.
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
Declare the contents of this Android application. The namespace
attribute brings in the Android platform namespace, and the package
supplies a unique name for the application. When writing your
own application, the package name must be changed from "com.example.*"
to come from a domain that you own or have control over. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.helloactivity">
<!-- The application tag supplies the name as presented
to the end user. Note that this name is specified by
a resource id. This allows you to localize the name
in the appropriate strings.xml file. -->
<application android:label="@string/application_name">
<!-- The application contains an activity named HelloActivity -->
<activity android:name="HelloActivity">
<intent-filter>
<!-- MAIN indicates that this activity is the one that
should be started when the application is started -->
<action android:name="android.intent.action.MAIN"/>
<!-- LAUNCHER indicates that this is the activity that
should be shown in the application Launcher -->
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Java Source
The Java source, HelloActivity.java, located in a package directory structure under src is shown below. The Java filename, class name, and android:name activity attribute all need to match so that everything gets tied together correctly.
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Copyright (C) 2009 Motorola, Inc.
*/
package com.example.android.helloactivity;
import android.app.Activity;
import android.os.Bundle;
/**
* A minimal "Hello, World!" application.
*/
public class HelloActivity extends Activity {
public HelloActivity() {
}
/**
* onCreate is called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the layout for this activity. You can find it
// in res/layout/hello_activity.xml by default, or in a localized
// version in the res/layout_* directories.
// Notice that the view is referenced by id. This allows the correct
// layout to be selected based on local, geometry, and orientation.
// Many sample applications in tutorials will show the layout being
// specified by a string - a reference to a specific file.
// DO NOT do this! Always use an id instead.
setContentView(R.layout.hello_activity);
}
}
Layout
The layout hello_activity.xml, located in res/layout, is shown below. In this case there is only one layout file as this application is too simple to need orientation or locale-specific layouts. Note that the layout ID is based on the file name as specified in the res directory. If localizations of the layout had been required, each layout would have been in an identically named file in a layout directory whose name would have specified the locale and orientation. No matter how many variations of the layout are present, only one layout ID is needed.
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
-->
<!-- A layout is a view, so we can use any view. In this case we are
using an EditText view instead of a layout manager as there is only
one view to display. -->
<EditText xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textSize="18sp"
android:autoText="true"
android:capitalize="sentences"
android:text="@string/hello_activity_text_text" />
Strings.xml
The default strings.xml file, located in res/values, is shown below. This file contains the default string resources for the application; the strings used if no better localization can be found. Remember, the ID value comes from the name of the string, not the name of the file in this case.
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2007 The Android Open Source Project
-->
<resources>
<string name="hello_activity_text_text">Hello, World!</string>
<string name="application_name">Hello World</string>
</resources>
The Japanese strings.xml file, located in res/values-ja-rJP, is shown below. The strings in this file will be used if the application is launched on a handset set to Japanese. Note that Android will also select the application name from this file, so the name will be properly displayed in the Launcher and other locations within the handset.
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2009 Motorola, Inc.
-->
<resources>
<string name="hello_activity_text_text">こんにちは世!</string>
<string name="application_name">こんにちは世</string>
</resources>
If you are seeing boxes or random characters instead of Japanese, you are probably running Windows or Linux without Asian language support. You can install the Asian language support, or just trust me that it is in Japanese.
Activity States
A moment ago as you were looking at hello_activity.java you may have realized that I had skipped some important information. Where did onCreate() come from? It is one of the lifecycle methods, akin to startApp(), pauseApp(), and destroyApp() in Java ME.
Android activities have three states: Stopped, Paused, and Active (Running), and seven lifecycle methods: onCreate(), onStart(), onRestart(), onResume(), onPause(), onStop(), onDestroy(). The following diagram gives the interaction between the states and lifecycle methods.
Active/Running Activities are running on the top of stack in the foreground. Paused activities are visible to the user, but are not in the foreground. For example, they may be partially obscured by another activity. Stopped activities are not visible to the user. Note that unlike Java ME, an application will immediately be placed in the Active state after launch.
So how to use the lifecycle methods?
onCreate()- Use for initial setup of the activity when first created. If you had saved state from a previous invocation of the activity, you will receive and unpack that state here. More on that later.
onStart()- Use for any setup/restore code that you might have had to duplicate between
onCreate()andonRestart(). If there is none, then you probably should not be using this method. onRestart()- Use for resumption of state or operations that were stopped in
onStop(). onResume()- Use to restart animations and other operations that you might have stopped in
onPause(). onPause()- Called when an activity is going to be moved from foreground status. Note that the activity could still be visible to the user. Use to quickly save the current state of the activity and to stop operations such as animations, timers, etc. that are not necessary when not in foreground.
onStop()- Called when an activity becomes completely obscured and is no longer visible to the user. May be used to free up additional resources (e.g. release memory allocated or stop additional operations).
onDestroy()- Called when an activity is going to be terminated, either by the system or as a result of a request by the activity itself.
There are a couple of more things you need to know. First, applications in the Active/Running state will not be terminated by the system. However, if an Activity is in Paused or Stopped state, it may be terminated at any time without calls to the lifecycle methods onStop() or onDestroy() if needed to resolve low resource situations by Android. Second, applications are normally paused, stopped, and terminated when orientation changes (with the corresponding lifecycle method calls). Finally, if you need an activity to exit itself, call finished().
Interaction Example
Now that you have a pretty good idea of what an Activity is about, the next area I need to cover for you is interacting with the user. I'm going to use a modified version of the SkeletonApp supplied by Google that comes with MOTODEV Studio for this example.
Layout
This is a more complex layout with a layout managers, an editable text view, a couple of buttons, and an image. It is a good example of building a complex layout from a couple of nested layouts. The outermost layout lays out two views vertically: the edit text view and a second linear layout. The nested linear layout places two buttons and an image in a horizontal row. The views that we wish to access have IDs so that we can locate them within the Activity code. The IDs also have one other very important effect when orientation changes. I'll cover that after I finish with this example.
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2007 The Android Open Source Project
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<!--
First view is a text editor. We want it to use all available
horizontal space, and stretch to fill whatever vertical space is
available to it. Note the use of the "id" attribute, which allows us
to find this object from the Java code.
-->
<EditText android:id="@+id/editor" android:layout_width="fill_parent"
android:layout_height="0dip" android:autoText="true"
android:capitalize="sentences" android:layout_weight="1"
android:freezesText="true">
<requestFocus />
</EditText>
<!--
Next view is another linear layout manager, now horizontal. We give it
a custom background; see colors.xml for the definition of
drawable/semi_black
-->
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_gravity="center_vertical"
android:gravity="center_horizontal" android:orientation="horizontal"
android:background="@drawable/semi_black">
<!--
On the left: the "back" button. See styles.xml for the definition of
style/ActionButton, which we use to hold common attributes that are
used for both this and the clear button. See strings.xml for the
definition of string/back.
-->
<Button android:id="@+id/back" style="@style/ActionButton"
android:text="@string/back" />
<!-- In the middle: a custom image, -->
<ImageView android:id="@+id/image" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:paddingLeft="4dip"
android_paddingRight="4dip" android:src="@drawable/violet" />
<!--
On the right: another button, this time with its text color changed
to red. Again, see colors.xml for the definition.
-->
<Button android:id="@+id/clear" style="@style/ActionButton"
android:text="@string/clear" android:textColor="@color/red" />
</LinearLayout>
</LinearLayout>
Styles
You may recall that I mentioned that you can change the default style to customize your appearance. In the layout file above, a button style was referenced. The style specification, located in the res/values, directory, is as follows.
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
-->
<resources>
<style name="ActionButton">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textAppearance">@style/TextAppearance.ActionButton</item>
</style>
<style name="TextAppearance" parent="android:TextAppearance">
</style>
<style name="TextAppearance.ActionButton">
<item name="android:textStyle">italic</item>
</style>
</resources>
Option Menu Layout
The option menu layout is shown below. Note that this is not in the sample application included with MOTODEV Studio. That version shows a programmatic method of creating the options menu. The problem with the sample is that it is only partially internationalized — you would not be able to completely localize the menu. Here, I create two fully internationalized menu items with string labels and numeric & character shortcuts.
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2009 Motorola, Inc.
-->
<!--
This file describes the options menu of the main SkeletonApp activity
user interface.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/back" android:id="@+id/menu_back"
android:alphabeticShortcut="@string/back_char_shortcut"
android:numericShortcut="@string/back_num_shortcut"></item>
<item android:title="@string/clear" android:id="@+id/menu_clear"
android:alphabeticShortcut="@string/clear_char_shortcut"
android:numericShortcut="@string/clear_num_shortcut"></item>
</menu>
Strings
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
Copyright (C) 2009 Motorola, Inc.
-->
<resources>
<!-- Simple strings. -->
<string name="skeleton_app">Skeleton App</string>
<string name="back">Back</string>
<string name="clear">Clear</string>
<!-- This is a complex string containing style runs. -->
<string name="main_label">Hello <u>th<ignore>e</ignore>re</u>, <i>you</i>
<b>Activity</b>!</string>
<string name="back_char_shortcut">b</string>
<string name="clear_char_shortcut">c</string>
<string name="back_num_shortcut">0</string>
<string name="clear_num_shortcut">1</string>
</resources>
Java Source
I'll present it here, and then go over a couple of points.
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Copyright (C) 2009 Motorola, Inc.
*/
package com.example.android.skeletonapp;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class SkeletonActivity extends Activity {
private EditText mEditor;
public SkeletonActivity() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate our UI from its XML layout description.
setContentView(R.layout.skeleton_activity);
// Find the text editor view inside the layout, because we
// want to do various programmatic things with it.
mEditor = (EditText) findViewById(R.id.editor);
// Set the initial content of the EditText view.
mEditor.setText(R.string.main_label);
// Hook up button presses to the appropriate event handler.
((Button) findViewById(R.id.back)).setOnClickListener(mBackListener);
((Button) findViewById(R.id.clear)).setOnClickListener(mClearListener);
}
/**
* A call-back for when the user presses the back button.
*/
OnClickListener mBackListener = new OnClickListener() {
public void onClick(View v) {
finish();
}
};
/**
* A call-back for when the user presses the clear button.
*/
OnClickListener mClearListener = new OnClickListener() {
public void onClick(View v) {
mEditor.setText("");
}
};
/**
* Called when the activity's options menu needs to be created.
* The menu views and other resources will only be loaded if the menu
* key is actually pressed, saving memory, time, and battery in those cases
* where the menu is never used.
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// Inflate our menu from its XML description.
getMenuInflater().inflate(R.menu.skeleton_options_menu, menu);
return true;
}
/**
* Called right before the activity's option menu is displayed.
* Enable/disable menu items as needed based on the Activity state.
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
// Before showing the menu, we need to decide whether the clear menu
// item should be enabled. It will only be enabled if there is text to clear.
menu.findItem(R.id.menu_clear).setEnabled(mEditor.getText().length() > 0);
return true;
}
/**
* Called when a menu item is selected.
* In this example, the menu items and UI buttons have the same functionality.
* To avoid error prone duplication of code, the buttons' listeners are called
* to actually implement the menu/button action.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_back:
mBackListener.onClick(findViewById(R.id.back));
return true;
case R.id.menu_clear:
mClearListener.onClick(findViewById(R.id.clear));
return true;
}
return super.onOptionsItemSelected(item);
}
}
onCreate()
In this method, I create the view for the Activity using our familiar setContentView(). However, this time I also locate the EditText view by the ID that we assigned in the layout file. Once I have the view, I can directly manipulate it. Use the EditText.setText() method to set the initial value of the field to a default string from the strings.xml resource.
In addition, both buttons are linked to their listeners — nested classes with an onClick() method to perform the actual button action. In this case the actions are small enough to be performed in the event thread; however, as in Java ME you should spawn separate threads for more time consuming operations. If you block the UI thread too long in Android, you will get an application not responding warning displayed to the user, with an option to terminate the application.
onCreateOptionsMenu()
Android uses lazy evaluation for UI components — UI components will not be loaded and inflated unless the component is actually going to be displayed. onCreateOptionsMenu() is called the first time the menu button is pressed to load and inflate the menu. All static initialization of the menu should be done here.
onPrepareOptionsMenu()
In some cases, certain menu choices may not be applicable depending on the state of the application or Activity. onPrepareOptionsMenu() is called just before the options menu is displayed each time it is displayed. Dynamic initialization — enabling or disabling menu items — should be done here.
Note that this initialization should depend only on the overall Activity state, not any particular view selection. That is, the options menu is for Activity options, not options for a view within the Activity. Use contextual menus for view options.
onOptionsItemSelected()
Once a menu item is selected, onOptionsItemSelected() receives the event; the argument is the menu item that was selected. The ID, if assigned, of any View object can be obtained via getItemId(), which is used here to select the appropriate action. As the menu items are the same as the buttons, I simply invoke the corresponding button listener's onClick() method.
Tasks and Applications
If you read the Android documentation on the web, it says an Android application is bundled into an Android package – code, resources, etc. This is run in an Android process, but in many cases, it is not really the entire application. When you dig a little farther in, an Android application is actually a collection of activities on the stack working together to accomplish a task. They do not need to be grouped in a single APK file, nor do you have to write all the activities yourself. You may use any activity installed in the system as part of your application, including third party applications installed later, to create your application. All activities that a user invokes to accomplish an objective are part of the same task, even if the activities are not part of the same Android package.
This is a very different concept from Java ME, where all the components of an application are tightly bound together into a single JAR file, and all associations are set at compile time. So how does this loosely coupled system work? The answer is Intents.
Intents and Bundles
When an Activity needs the assistance of another Activity to accomplish a task, it creates an Intent, and asks the Android system to locate an Activity that can accomplish the intent via a call to Context.startActivity() or Activity.startActivityForResults(). Correspondingly, in the Android Manifest, each Activity lists a set of intent filters — basically a list of the intents that the Activity responds to. The Android system looks through all Activities present, and their associated intent filters, and selects the Activity that best matches the Intent. Intents may be explicit, specifying exactly which Activity to invoke, or implicit, where more than one Activity can match in which case this system asks the user which Activity to use.
Data within Intents is specified as a MIME type and URI. The Intent may also contain a set of key/value pairs known as Extras to pass additional information to the Activity being started. These extras may be added as individual values or as a Bundle (a collection of key/value pairs) via putExtras(). On the receiving side, use getExtras() to get the extra values from the Intent. Use getIntent() to get the Intent.
Intents contain information that allows the Android system to find an appropriate Activity, and data for the Activity. Android information includes the Action to be performed, the Category, and the MIME type of the data. This should start sounding familiar at this point, as the intent filter in the Android Manifest example above specified the action and category. You may define your own Action types, or use one of the predefined types.
Intents Example
This is an example of using intents to include a system Activity into the application. I have changed the previous example to allow entry of a valid URL' replacing the back button with a view button. When pressed, a new Activity is started to view the specified URL. In this case, I use the built-in browser activity. So this application consists of my Activity and a system Activity.
Layout
I took out the superfluous image from the previous example, and changed the back button to a view button.
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2007 The Android Open Source Project
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<EditText android:id="@+id/editor" android:layout_width="fill_parent"
android:layout_height="0dip" android:autoText="true"
android:capitalize="sentences" android:layout_weight="1"
android:freezesText="true">
<requestFocus />
</EditText>
<LinearLayout android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_gravity="center_vertical"
android:gravity="center_horizontal" android:orientation="horizontal"
android:background="@drawable/semi_black">
<Button style="@style/ActionButton"
android:id="@+id/view" android:text="@string/view"/>
<Button android:id="@+id/clear" style="@style/ActionButton"
android:text="@string/clear" android:textColor="@color/red" />
</LinearLayout>
</LinearLayout>
Menu
The options menu with two buttons is shown below.
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2009 Motorola, Inc.
-->
<!--
This file describes the options menu of the main SkeletonApp activity
user interface.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/view" android:id="@+id/menu_view"
android:alphabeticShortcut="@string/view_char_shortcut"
android:numericShortcut="@string/view_num_shortcut"></item>
<item android:title="@string/clear" android:id="@+id/menu_clear"
android:alphabeticShortcut="@string/clear_char_shortcut"
android:numericShortcut="@string/clear_num_shortcut"></item>
</menu>
Strings
The strings used in the application.
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2007 The Android Open Source Project
Copyright (C) 2009 Motorola, Inc.
-->
<!-- This file contains resource definitions for displayed strings, allowing
them to be changed based on the locale and options. -->
<resources>
<!-- Simple strings. -->
<string name="app_name">Web App</string>
<string name="view">View</string>
<string name="clear">Clear</string>
<string name="main_label">http://</string>
<string name="view_char_shortcut">v</string>
<string name="clear_char_shortcut">c</string>
<string name="view_num_shortcut">0</string>
<string name="clear_num_shortcut">1</string>
<string name="Invalid_URL">Please enter a valid URL.</string>
</resources>
Java Source
And now the source code to make it all work.
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Copyright (C) 2009 Motorola, Inc.
*/
package com.webapp;
import java.net.URL;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class WebApp extends Activity {
private EditText mEditor;
public WebApp() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.webapp_activity);
// Find the text editor view inside the layout, because we
// want to do various programmatic things with it.
mEditor = (EditText) findViewById(R.id.editor);
// Set the initial content of the EditText view and move the insertion
// point to the end of the string.
mEditor.setText(R.string.main_label);
mEditor.setSelection(getText(R.string.main_label).length());
// Hook up button presses to the appropriate event handler.
((Button) findViewById(R.id.view)).setOnClickListener(mViewListener);
((Button) findViewById(R.id.clear)).setOnClickListener(mClearListener);
}
/**
* A call-back for when the user presses the back button.
*/
OnClickListener mViewListener = new OnClickListener() {
public void onClick(View v) {
try {
// first, check to see if this is a valid URL
// do not keep a reference to the object as the exception
// side effect is what is of interest here.
new URL(mEditor.getText().toString());
// Create a Uri from the string. An intent with a VIEW action
// requires a Uri object (note: Uri and URI are different!)
Uri uri = Uri.parse(mEditor.getText().toString());
// I intend to view the uri
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
// start an Activity to view the uri
// Android will look at the uri to determine the proper viewer
startActivity(intent)
} catch (Exception e) {
Toast toast = Toast.makeText(getApplicationContext(),
R.string.Invalid_URL, Toast.LENGTH_SHORT);
toast.show();
}
}
};
/**
* A call-back for when the user presses the clear button.
*/
OnClickListener mClearListener = new OnClickListener() {
public void onClick(View v) {
mEditor.setText(R.string.main_label);
mEditor.setSelection(getText(R.string.main_label).length());
}
};
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// Inflate our menu from its XML description.
getMenuInflater().inflate(R.menu.web_options_menu, menu);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
// Before showing the menu, we need to decide whether the clear menu
// item should be enabled. It will only be enabled if the text has been
// changed.
menu.findItem(R.id.menu_clear).setEnabled(
!mEditor.getText().toString().equals(
getText(R.string.main_label)));
return true;
}
/**
* Called when a menu item is selected. In this example, the menu items and
* UI buttons have the same functionality. To avoid error prone duplication
* of code, the buttons' listeners are called to actually implement the
* menu/button action.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_view:
mViewListener.onClick(findViewById(R.id.view));
return true;
case R.id.menu_clear;
mClearListener.onClick(findViewById(R.id.clear));
return true;
}
return super.onOptionsItemSelected(item);
}
}
There are two changes of interest. First, the onClearListener() has been changed to check if the value in the EditText has been changed; if it has, it is reset. Second, and more interesting, the onViewListener() checks the string to ensure it is a valid URL; this is a side effect of creating a URL object from the string in the EditText view. If it is valid, discard the URL, and create a Uri object, which is what this particular intent will require. Next, create an intent with a VIEW action, indicating that the system should locate an Activity that is capable of displaying the Uri, which is the intent's argument. Then tell the system to locate and start an Activity. In this case, the Activity selected will be a browser activity, as the Uri is a valid URL.
There is also a simple error handler. If the string is not a valid URL, an exception will be thrown when the string is converted to a URL. If an exception occurs a Toast (a message displayed briefly to the user) will be shown to indicate that the value entered is not a valid URL.
Orientation Changes
Android has the annoying habit of killing and restarting your Activity when the device orientation changes. Considering the average phone, orientation changes are going to happen a lot and your users are not going to appreciate having to navigate back to the same point in your application. So how do you make sure that you are at the exact same state you were before the orientation changed (other than making your application refuse to change orientation....)?
The secret is Bundles. Android calls the method onSaveInstanceState() just before it calls onPause(). If you remember, once you are Paused, all sorts of unpleasant things can happen to your Activity. If you save the current state of your application in the Bundle in onSaveInstanceState(), you can restore that state in onCreate() or in onRestoreInstanceState(), which is a method called after onStart(). This has significant ramifications for application design, so make sure you are considering this early in the design process of every Activity!
UI State and IDs
What happens if a user is in the middle of entering text when, to make it easier to read, they change orientation? Android helps you out a lot in this case. The state for each View with an ID is automatically captured before the Activity is killed, and the state is restored after the call to onCreate() when the Activity is restarted. The state of Views without an ID is not restored, so make sure that you assign an ID to every view who's state you want restored automatically.
This is why the SkeletonApp works even though there is no call to onSaveInstanceState() — all the interesting state is in the EditText view, which was assigned an ID.
Other Android Components
So now you have seen most of the elements of an Android application. I am going to quickly summarize a few more elements, then you are on your own as far as Android concepts are concerned.
Services
The parts of an application that hang around and do things for you, but are not displayed on the screen, are Services. You might initially think of services as an additional thread in the application, but you would be wrong. Think instead of a daemon process – a process that runs in the background and does things for you.
Services are also described in the Android Manifest and are launched by Intents in the same way as Activities. The main differences are that they do not have any visible components, so their lifecycle is different than Activities, and more than one Activity may make use of them at one time.
Services generally run in separate processes, so you will be making RPC calls to them. Not to worry about this too much; Google has created a reasonably simple proxy method to encapsulate the RPC calls. You will use the Android Interface Definition Language (aidl) to define the interface. The build system will create a couple of classes for you, and you use those classes to build the service and to use the service.
By the way, don't mess around with any files that the build system generates for you. I promise you many hours of nasty errors and debugging if you do.
Content Providers
Content Providers are data sources or destinations provided by other applications. For example the Contacts application has a content provider you may access to get names, addresses, or phone numbers. You may also write your own Content Provider if you wish to supply information to or receive updates to your information from other applications. A Content Provider exposes its data to other applications as a table in a database. Values are returned in a Cursor, a collection of rows. You may use the Cursor to read rows and columns in the resulting data.
In order to use a Content Provider you must have the URI that identifies the provider and the names and types of data you want. This information is available for each public content provider, such as the contacts database. When retrieving values, you may use a record ID or an SQL type WHERE clause to select one or more records.
Access to Content Providers may be governed by permissions.
Broadcast Receivers
Broadcast Receivers allow your application to respond to system events or events generated by other applications. Like Services, they have no user visible appearance. As the system can generate many events, it is important that Broadcast Receivers act quickly on the information they receive. Normally you will use a Broadcast Receiver to start a Service or notify the user via a Notification that something of interest has happened.
Where To Go From Here
In no particular order: Check out the online Android Developer Guide. Create a HelloWorld project, update it to match the version in this paper, and add a few other localizations. Look at the help in MOTODEV Studio for Android (particularly the sections on packaging and deploying to a real device). Buy a book on Android development and read it. Highlight all the internationalization and localization mistakes (caution, have at least two new highlighter pens before starting this activity).
Most of all, have fun.
Copyright © 2009, Motorola, Inc. All rights reserved except as otherwise explicitly indicated.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.




