Professional Android 4 Application Development (2012)
Chapter 2. Getting Started
What's in this Chapter?
Installing the Android SDK, creating a development environment, and debugging your projects
Understanding mobile design considerations
The importance of optimizing for speed and efficiency
Designing for small screens and mobile data connections
Using Android Virtual Devices, the Emulator, and other development tools
All you need to start writing your own Android applications is a copy of the Android SDK and the Java Development Kit (JDK). Unless you're a masochist, you'll probably want a Java integrated development environment (IDE)—Eclipse is particularly well supported—to make development a little bit less painful.
The Android SDK, the JDK, and Eclipse are each available for Windows, Mac OS, and Linux, so you can explore Android from the comfort of whatever operating system you favor. The SDK tools and Emulator work on all three OS environments, and because Android applications are run on a Dalvik virtual machine (VM), there's no advantage to developing on any particular OS.
Android code is written using Java syntax, and the core Android libraries include most of the features from the core Java APIs. Before you can run your projects, you must translate them into Dalvik bytecode. As a result, you get the familiarity of Java syntax while your applications gain the advantage of running on a VM optimized for mobile devices.
The Android SDK starter package contains the SDK platform tools, including the SDK Manager, which is necessary to download and install the rest of the SDK packages.
The Android SDK Manager is used to download Android framework SDK libraries, optional add-ons (including the Google APIs and the support library), complete documentation, and a series of excellent sample applications. It also includes the platform and development tools you will use to write and debug your applications, such as the Android Emulator to run your projects and the Dalvik Debug Monitoring Service (DDMS) to help debug them.
By the end of this chapter, you'll have downloaded the Android SDK starter package and used it to install the SDK and its add-ons, the platform tools, documentation, and sample code. You'll set up your development environment, build your first Hello World application, and run and debug it using the DDMS and the Emulator running on an Android Virtual Device (AVD).
If you've developed for mobile devices before, you already know that their small-form factor, limited battery life, and restricted processing power and memory create some unique design challenges. Even if you're new to the game, it's obvious that some of the things you can take for granted on the desktop or the Web aren't going to work on mobile or embedded devices.
The user environment brings its own challenges, in addition to those introduced by hardware limitations. Many Android devices are used on the move and are often a distraction rather than the focus of attention, so your application needs to be fast, responsive, and easy to learn. Even if your application is designed for devices more conducive to an immersive experience, such as tablets or televisions, the same design principles can be critical for delivering a high-quality user experience.
This chapter examines some of the best practices for writing Android applications that overcome the inherent hardware and environmental challenges associated with mobile development. Rather than try to tackle the whole topic, we'll focus on using the Android SDK in a way that's consistent with good design principles.
Developing for Android
The Android SDK includes all the tools and APIs you need to write compelling and powerful mobile applications. The biggest challenge with Android, as with any new development toolkit, is learning the features and limitations of its APIs.
If you have experience in Java development, you'll find that the techniques, syntax, and grammar you've been using will translate directly into Android, although some of the specific optimization techniques may seem counterintuitive.
If you don't have experience with Java but have used other object-oriented languages (such as C#), you should find the transition straightforward. The power of Android comes from its APIs, not the language being used, so being unfamiliar with some of the Java-specific classes won't be a big disadvantage.
What You Need to Begin
Because Android applications run within the Dalvik VM, you can write them on any platform that supports the developer tools. This currently includes the following:
· Microsoft Windows (XP or later)
· Mac OS X 10.5.8 or later (Intel chips only)
· Linux (including GNU C Library 2.7 or later)
To get started, you'll need to download and install the following:
· The Android SDK starter package
· Java Development Kit (JDK) 5 or 6
You can download the latest JDK from Sun at http://java.sun.com/javase/downloads/index.jsp.
If you already have a JDK installed, make sure that it meets the preceding requirements, and note that the Java Runtime Environment (JRE) is not sufficient.
In most circumstances, you'll also want to install an IDE. The following sections describe how to install the Android SDK and use Eclipse as your Android IDE.
Downloading and Installing the Android SDK
There's no cost to download or use the API, and Google doesn't require your application to pass a review to distribute your finished programs on the Google Play Store. Although the Google Play Store requires a small one-time fee to publish applications, if you chose not to distribute via the Google Play Store, you can do so at no cost.
You can download the latest version of the SDK starter package for your chosen development platform from the Android development home page at http://developer.android.com/sdk/index.html.
Unless otherwise noted, the version of the Android SDK used for writing this book was version 4.0.3 (API level 15).
As an open-source platform, the Android SDK source is also available for you to download and compile from http://source.android.com.
The starter package is a ZIP file that contains the latest version of the Android tools required to download the rest of the Android SDK packages. Install it by unzipping the SDK into a new folder. Take note of this location, as you'll need it later.
If you are developing from a Windows platform, an executable Windows installer is available (and recommended) as an alternative to the ZIP file for installing the platform tools.
Before you can begin development, you need to download at least one SDK platform release. You can do this on Windows by running the SDK Manager.exe executable, or on Mac OS or Linux by running the “android” executable in the tools subfolder of the starter package download.
The screen that appears (see Figure 2.1) shows each of the packages available for the download. This includes a node for the platform tools, each of the platform releases, and a collection of extras, such as the Android Support Package and billing/licensing packages.
Figure 2.1
You can expand each platform release node to see a list of the packages included within it, including the tools, documentation, and sample code packages.
To get started, simply check the boxes corresponding to the newest framework SDK and the latest version of the tools, compatibility/support library, documentation, and sample code.
For testing the backward compatibility of your applications, it can often be useful to download the framework SDK for each version you intend to support.
To use the Google APIs (which contain the Maps APIs), you also need to select the Google APIs by Google package from the platform releases you want to support.
When you click the Install Packages button, the packages you've chosen will be downloaded to your SDK installation folder. The result is a collection of framework API libraries, documentation, and several sample applications.
The examples included in the SDK are well documented and are an excellent source for full, working examples of applications written for Android. When you finish setting up your development environment, it's worth going through them.
Downloading and Installing Updates to the SDK
As new versions of the Android framework SDK, developer tools, sample code, documentation, compatibility library, and third-party add-ons become available, you can use the Android SDK Manager to download and install those updates.
All future packages and upgrades will be placed in the same SDK location.
Developing with Eclipse
The examples and step-by-step instructions in this book are targeted at developers using Eclipse with the Android Developer Tools (ADT) plug-in. Neither is required, though; you can use any text editor or Java IDE you're comfortable with and use the developer tools in the SDK to compile, test, and debug the code snippets and sample applications.
As the recommended development platform, using Eclipse with the ADT plug-in for your Android development offers some significant advantages, primarily through the tight integration of many of the Android build and debug tools into your IDE.
Eclipse is a particularly popular open-source IDE for Java development. It's available for download for each of the development platforms supported by Android (Windows, Mac OS, and Linux) from the Eclipse foundation (www.eclipse.org/downloads).
Many variations of Eclipse are available, with Eclipse 3.5 (Galileo) or above required to use the ADT plugin. The following is the configuration for Android used in the preparation of this book:
· Eclipse 3.7 (Indigo) (Eclipse Classic download)
· Eclipse Java Development Tools (JDT) plug-in
· Web Standard Tools (WST)
The JDT plug-in and WST are included in most Eclipse IDE packages.
Installing Eclipse consists of uncompressing the download into a new folder, and then running the eclipse executable. When it starts for the first time, you should create a new workspace for your Android development projects.
Using the Android Developer Tools Plug-In for Eclipse
The ADT plug-in for Eclipse simplifies your Android development by integrating the developer tools, including the Emulator and .class-to-.dex converter, directly into the IDE. Although you don't have to use the ADT plug-in, it can make creating, testing, and debugging your applications faster and easier.
The ADT plug-in integrates the following into Eclipse:
· An Android Project Wizard, which simplifies creating new projects and includes a basic application template
· Forms-based manifest, layout, and resource editors to help create, edit, and validate your XML resources
· Automated building of Android projects, conversion to Android executables (.dex), packaging to package files (.apk), and installation of packages onto Dalvik VMs (running both within the Emulator or on physical devices)
· The Android Virtual Device manager, which lets you create and manage virtual devices to host Emulators that run a specific release of the Android OS and with set hardware and memory constraints
· The Android Emulator, including the ability to control the Emulator's appearance and network connection settings, and the ability to simulate incoming calls and SMS messages
· The Dalvik Debug Monitoring Service (DDMS), which includes port forwarding, stack, heap, and thread viewing, process details, and screen-capture facilities
· Access to the device or Emulator's filesystem, enabling you to navigate the folder tree and transfer files
· Runtime debugging, which enables you to set breakpoints and view call stacks
· All Android/Dalvik log and console outputs
Figure 2.2 shows the DDMS perspective within Eclipse with the ADT plug-in installed.
Figure 2.2
Installing the ADT Plug-In
Install the ADT plug-in by following these steps:
1. Select → Install New Software from within Eclipse.
2. In the Available Software dialog box that appears, click the Add button.
3. In the next dialog, enter a name you will remember (e.g., Android Developer Tools) into the Name field, and paste the following address into the Location text entry box: https://dl-ssl.google.com/android/eclipse/.
4. Press OK and Eclipse searches for the ADT plug-in. When finished, it displays the available plug-ins, as shown in Figure 2.3. Select it by clicking the check box next to the Developer Tools root node, and then click Next.
Figure 2.3
5. Eclipse now downloads the plug-in. When it finishes, a list of the Developer Tools displays for your review. Click Next.
6. Read and accept the terms of the license agreement, and click Next and then Finish. As the ADT plug-in is not signed, you'll be prompted before the installation continues.
7. When installation is complete, you need to restart Eclipse and update the ADT preferences. Restart and select Window → Preferences (or → Preferences for Mac OS).
8. Select Android from the left panel.
9. Click Browse, navigate to the folder into which you installed the Android SDK, and then click Apply. The list updates to display each available SDK target, as shown in Figure 2.4. Click OK to complete the SDK installation.
If you move your SDK installation to a different location, you will need to update the ADT preference, as described in steps 7 to 9 above, to reflect the new path to the SDK against which the ADT should be building.
Figure 2.4
Updating the ADT Plug-In
In most cases, you can update your ADT plug-in simply as follows:
1. Navigate to Help → Check for Updates.
2. If there are any ADT updates available, they will be presented. Simply select them and choose Install.
Sometimes a plug-in upgrade may be so significant that the dynamic update mechanism can't be used. In those cases you may have to remove the previous plug-in completely before installing the newer version, as described in the previous section.
Using the Support Package
The support library package (previously known as the compatibility library) is a set of static libraries that you can include as part of your projects to gain either convenience APIs that aren't packaged as part of the framework (such as the View Pager), or useful APIs that are not available on all platform releases (such as Fragments).
The support package enables you to use framework API features that were introduced in recent Android platform releases on any device running Android 1.6 (API level 4) and above. This helps you provide a consistent user experience and greatly simplifies your development process by reducing the burden of supporting multiple platform versions.
It's good practice to use the support library rather than the framework API libraries when you want to support devices running earlier platform releases and where the support library offers all the functionality you require.
In the interest of simplicity, the examples in this book target Android API level 15 and use the framework APIs in preference to the support library, highlighting specifi c areas where the support library would not be a suitable alternative.
To incorporate a support library into your project, perform the following steps:
1. Add a new /libs folder in the root of your project hierarchy.
2. Copy the support library JAR file from the /extras/android/support/ folder in your Android SDK installation location.
You'll note that the support folder includes multiple subfolders, each of which represents the minimum platform version supported by that library. Simply use the corresponding JAR file stored in the subfolder labeled as less than or equal to the minimum platform version you plan to support.
3. For example, if you want to support all platform versions from Android 1.6 (API level 4) and above, you would copy v4/android-support-v4.jar.
4. After copying the file into your project's /libs folder, add it to your project build path by right-clicking in the Package Explorer and selecting Build Path → Add to Build Path.
By design, the support library classes mirror the names of their framework counterparts. Some of these classes (such as SimpleCursorAdapter) have existed since early platform releases. As a result, there's a significant risk that the code completion and automatic import-management tools in Eclipse (and other IDEs) will select the wrong library—particularly when you're building against newer versions of the SDK.
It's good practice to set your project build target to the minimum platform version you plan to support, and to ensure the import statements are using the compatibility library for classes that also exist in the target framework.
Creating Your First Android Application
You've downloaded the SDK, installed Eclipse, and plugged in the plug-in. You are now ready to start programming for Android. Start by creating a new Android project and setting up your Eclipse run and debug configurations, as described in the following sections.
Creating a New Android Project
To create a new Android project using the Android New Project Wizard, do the following:
1. Select File → New → Project.
2. Select the Android Project application type from the Android folder, and click Next.
3. In the wizard that appears, enter the details for your new project. On the first page (Figure 2.5), the Project Name is the name of your project file. You can also select the location your project should be saved.
Figure 2.5
4. The next page (Figure 2.6) lets you select the build target for your application. The build target is the version of the Android framework SDK that you plan to develop with. In addition to the open sourced Android SDK libraries available as part of each platform release, Google offers a set of proprietary APIs that offer additional libraries (such as Maps). If you want to use these Google-specific APIs, you must select the Google APIs package corresponding to the platform release you want to target.
Figure 2.6
Your project's build target does not need to correspond to its minimum SDK or target SDK. For new projects it's good practice to build against the newest version of the SDK to take advantage of efficiency and UI improvements in newer platform releases.
5. The final page (Figure 2.7) allows you to specify the application properties. The Application Name is the friendly name for your application; the Package Name specifies its Java package; the Create Activity option lets you specify the name of a class that will be your initial Activity; and setting the Minimum SDK lets you specify the minimum version of the SDK that your application will run on.
Figure 2.7
Selecting the minimum SDK version requires you to choose the level of backward compatibility you want to support to target a wider group of Android devices. Your application will be available from the Google Play Store on any device running the specified build or higher.
At the time of this writing, more than 98% of Android devices were running at least Android 2.1 (API level 7). The latest Ice Cream Sandwich SDK is 4.0.3 (API level 15).
6. When you've entered the details, click Finish.
If you selected Create Activity, the ADT plug-in will create a new project that includes a class that extends Activity. Rather than being completely empty, the default template implements Hello World. Before modifying the project, take this opportunity to configure launch configurations for running and debugging.
Creating an Android Virtual Device
AVDs are used to simulate the hardware and software configurations of different Android devices, allowing you test your applications on a variety of hardware platforms.
There are no prebuilt AVDs in the Android SDK, so without a physical device, you need to create at least one before you can run and debug your applications.
1. Select Window → AVD Manager (or select the AVD Manager icon on the Eclipse toolbar).
2. Select the New… button.
3. The resulting Create new Android Virtual Device (AVD) dialog allows you to configure a name, a target build of Android, an SD card capacity, and device skin.
4. Create a new AVD called “My_AVD” that targets Android 4.0.3, includes a 16MB SD Card, and uses the Galaxy Nexus skin, as shown in Figure 2.8.
5. Click Create AVD and your new AVD will be created and ready to use.
Figure 2.8
Creating Launch Configurations
Launch configurations let you specify runtime options for running and debugging applications. Using a launch configuration you can specify the following:
· The Project and Activity to launch
· The deployment target (virtual or physical device)
· The Emulator's launch parameters
· Input/output settings (including console defaults)
You can specify different launch configurations for running and debugging applications. The following steps show how to create a launch configuration for an Android application:
1. Select Run Configurations… or Debug Configurations… from the Run menu.
2. Select your application from beneath the Android Application node on the project type list, or right-click the Android Application node and select New.
3. Enter a name for the configuration. You can create multiple configurations for each project, so create a descriptive title that will help you identify this particular setup.
4. Choose your start-up options. The first (Android) tab lets you select the project to run and the Activity that you want to start when you run (or debug) the application. Figure 2.9 shows the settings for the project you created earlier.
5. Use the Target tab, as shown in Figure 2.10, to select the default virtual device to launch on, or select Manual to select a physical or virtual device each time you run the application. You can also configure the Emulator's network connection settings and optionally wipe the user data and disable the boot animation when launching a virtual device.
The Android SDK does not include a default AVD, so you need to create one before you can run or debug your applications using the Emulator. If the Virtual Device selection list in Figure 2.10 is empty, click Manager... to open the Android Virtual Device Manager and create one as described in the previous section.
Further details on the Android Virtual Device Manager are available later in this chapter.
6. Set any additional properties in the Common tab.
7. Click Apply, and your launch configuration will be saved.
Figure 2.9
Figure 2.10
Running and Debugging Your Android Application
You've created your first project and created the run and debug configurations for it. Before making any changes, test your installation and configurations by running and debugging the Hello World project.
From the Run menu, select Run or Debug to launch the most recently selected configuration, or select Run Configurations… or Debug Configurations… to select a specific configuration.
If you're using the ADT plug-in, running or debugging your application does the following:
· Compiles the current project and converts it to an Android executable (.dex)
· Packages the executable and your project's resources into an Android package (.apk)
· Starts the virtual device (if you've targeted one and it's not already running)
· Installs your application onto the target device
· Starts your application
If you're debugging, the Eclipse debugger will then be attached, allowing you to set breakpoints and debug your code.
If everything is working correctly, you'll see a new Activity running on the device or in the Emulator, as shown in Figure 2.11.
Figure 2.11
Understanding Hello World
Take a step back and have a good look at your first Android application.
Activity is the base class for the visual, interactive components of your application; it is roughly equivalent to a Form in traditional desktop development (and is described in detail in Chapter 3, “Creating Applications and Activities”). Listing 2.1 shows the skeleton code for an Activity-based class; note that it extends Activity and overrides the onCreate method.
Listing 2.1: Hello World
package com.paad.helloworld;
import android.app.Activity;
import android.os.Bundle;
public class MyActivity extends Activity {
/** Called when the Activity is first created. **/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
code snippet PA4AD_Ch02_HelloWorld/src/MyActivity.java
In Android, visual components are called Views, which are similar to controls in traditional desktop development. The Hello World template created by the wizard overrides the onCreate method to call setContentView, which lays out the UI by inflating a layout resource, as highlighted in bold in the following snippet:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
The resources for an Android project are stored in the res folder of your project hierarchy, which includes layout, values, and a series of drawable subfolders. The ADT plug-in interprets these resources to provide design-time access to them through the R variable, as described in Chapter 3.
Listing 2.2 shows the UI layout defined in the main.xml file created by the Android project template and stored in the project's res/layout folder.
Listing 2.2: Hello World layout resource
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>
code snippet PA4AD_Ch02_HelloWorld/res/layout/main.xml
Defining your UI in XML and inflating it is the preferred way of implementing your user interfaces (UIs), as it neatly decouples your application logic from your UI design.
To get access to your UI elements in code, you add identifier attributes to them in the XML definition. You can then use the findViewById method to return a reference to each named item. The following XML snippet shows an ID attribute added to the Text View widget in the Hello World template:
<TextView
android:id="@+id/myTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
And the following snippet shows how to get access to it in code:
TextView myTextView = (TextView)findViewById(R.id.myTextView);
Alternatively (although it's not generally considered good practice), you can create your layout directly in code, as shown in Listing 2.3.
Listing 2.3: Creating layouts in code
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout.LayoutParams lp;
lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.FILL_PARENT);
LinearLayout.LayoutParams textViewLP;
textViewLP = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.FILL_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
TextView myTextView = new TextView(this);
myTextView.setText(getString(R.string.hello));
ll.addView(myTextView, textViewLP);
this.addContentView(ll, lp);
}
code snippet PA4AD_Ch02_Manual_Layout/src/MyActivity.java
All the properties available in code can be set with attributes in the XML layout.
More generally, keeping the visual design decoupled from the application code helps keep the code concise. With Android available on hundreds of different devices of varying screen sizes, defining your layouts as XML resources makes it easier for you to include multiple layouts optimized for different screens.
You'll learn how to build your user interface by creating layouts and building your own custom Views in Chapter 4, “Building User Interfaces.”
Types of Android Applications
Most of the applications you create in Android will fall into one of the following categories:
· Foreground—An application that's useful only when it's in the foreground and is effectively suspended when it's not visible. Games are the most common examples.
· Background—An application with limited interaction that, apart from when being configured, spends most of its lifetime hidden. These applications are less common, but good examples include call screening applications, SMS auto-responders, and alarm clocks.
· Intermittent—Most well-designed applications fall into this category. At one extreme are applications that expect limited interactivity but do most of their work in the background. A common example would be a media player. At the other extreme are applications that are typically used as foreground applications but that do important work in the background. Email and news applications are great examples.
· Widgets and Live Wallpapers—Some applications are represented only as a home-screen Widget or as a Live Wallpaper.
Complex applications are often difficult to pigeonhole into a single category and usually include elements of each of these types. When creating your application, you need to consider how it's likely to be used and then design it accordingly. The following sections look more closely at some of the design considerations for each application type.
Foreground Applications
When creating foreground applications, you need to consider carefully the Activity lifecycle (described in Chapter 3) so that the Activity switches seamlessly between the background and the foreground.
Applications have little control over their lifecycles, and a background application with no running Services is a prime candidate for cleanup by Android's resource management. This means that you need to save the state of the application when it leaves the foreground, and then present the same state when it returns to the front.
It's also particularly important for foreground applications to present a slick and intuitive user experience. You'll learn more about creating well-behaved and attractive foreground Activities in Chapters 3, 4, 10, and 11.
Background Applications
These applications run silently in the background with little user input. They often listen for messages or actions caused by the hardware, system, or other applications, rather than relying on user interaction.
You can create completely invisible services, but in practice it's better to provide at least a basic level of user control. At a minimum you should let users confirm that the service is running and let them configure, pause, or terminate it, as needed.
Services and Broadcast Receivers, the driving forces of background applications, are covered in depth in Chapter 5, “Intents and Broadcast Receivers,” and Chapter 9, “Working in the Background.”
Intermittent Applications
Often you'll want to create an application that can accept user input and that also reacts to events when it's not the active foreground Activity. Chat and e-mail applications are typical examples. These applications are generally a union of visible Activities and invisible background Services and Broadcast Receivers.
Such an application needs to be aware of its state when interacting with the user. This might mean updating the Activity UI when it's visible and sending notifications to keep the user updated when it's in the background, as described in the section “Using Notifications” in Chapter 10.
You must be particularly careful to ensure that the background processes of applications of this type are well behaved and have a minimal impact on the device's battery life.
Widgets and Live Wallpapers
In some circumstances your application may consist entirely of a Widget or Live Wallpaper. By creating Widgets and Live Wallpapers, you provide interactive visual components that can add functionality to user's home screens.
Widget-only applications are commonly used to display dynamic information, such as battery levels, weather forecasts, or the date and time.
You'll learn how to create Widgets and Live Wallpapers in Chapter 14, “Invading the Home Screen.”
Developing for Mobile and Embedded Devices
Android does a lot to simplify mobile- or embedded-device software development, but you need to understand the reasons behind the conventions. There are several factors to account for when writing software for mobile and embedded devices, and when developing for Android in particular.
In this chapter you'll learn some of the techniques and best practices for writing efficient Android code. In later examples, efficiency is sometimes compromised for clarity and brevity when new Android concepts or functionality are introduced. In the best tradition of “Do as I say, not as I do,” the examples are designed to show the simplest (or easiest-to-understand) way of doing something, not necessarily the best way of doing it.
Hardware-Imposed Design Considerations
Small and portable, mobile devices offer exciting opportunities for software development. Their limited screen size and reduced memory, storage, and processor power are far less exciting, and instead present some unique challenges.
Compared to desktop or notebook computers, mobile devices have relatively:
· Low processing power
· Limited RAM
· Limited permanent storage capacity
· Small screens with low resolution
· High costs associated with data transfer
· Intermittent connectivity, slow data transfer rates, and high latency
· Unreliable data connections
· Limited battery life
Each new generation of phones improves many of these restrictions. In particular, newer phones have dramatically improved screen resolutions and significantly cheaper data costs.
The introduction of tablet devices and Android-powered televisions has expanded the range of devices on which your application may be running and eliminating some of these restrictions. However, given the range of devices available, it's always good practice to design to accommodate the worst-case scenario to ensure your application provides a great user experience no matter what the hardware platform it's installed on.
Be Efficient
Manufacturers of embedded devices, particularly mobile devices, generally value small size and long battery life over potential improvements in processor speed. For developers, that means losing the head start traditionally afforded thanks to Moore's law (the doubling of the number of transistors placed on an integrated circuit every two years). In desktop and server hardware, this usually results directly in processor performance improvements; for mobile devices, it instead means thinner, more power-efficient mobiles, with brighter, higher resolution screens. By comparison, improvements in processor power take a back seat.
In practice, this means that you always need to optimize your code so that it runs quickly and responsively, assuming that hardware improvements over the lifetime of your software are unlikely to do you any favors.
Code efficiency is a big topic in software engineering, so I'm not going to try and cover it extensively here. Later in this chapter you'll learn some Android-specific efficiency tips, but for now note that efficiency is particularly important for resource-constrained platforms.
Expect Limited Capacity
Advances in flash memory and solid-state disks have led to a dramatic increase in mobile-device storage capacities. (MP3 collections still tend to expand to fill the available storage.) Although an 8GB flash drive or SD card is no longer uncommon in mobile devices, optical disks offer more than 32GB, and terabyte drives are now commonly available for PCs. Given that most of the available storage on a mobile device is likely to be used to store music and movies, many devices offer relatively limited storage space for your applications.
Android lets you specify that your application can be installed on the SD card as an alternative to using internal memory (described in detail in Chapter 3), but there are significant restrictions to this approach and it isn't suitable for all applications. As a result, the compiled size of your application is an important consideration, though more important is ensuring that your application is polite in its use of system resources.
You should carefully consider how you store your application data. To make life easier, you can use the Android databases and Content Providers to persist, reuse, and share large quantities of data, as described in Chapter 8, “Databases and Content Providers.” For smaller data storage, such as preferences or state settings, Android provides an optimized framework, as described in Chapter 7, “Files, Saving State, and Preferences.”
Of course, these mechanisms won't stop you from writing directly to the filesystem when you want or need to, but in those circumstances always consider how you're structuring these files, and ensure that yours is an efficient solution.
Part of being polite is cleaning up after yourself. Techniques such as caching, pre-fetching, and lazy loading are useful for limiting repetitive network lookups and improving application responsiveness, but don't leave files on the filesystem or records in a database when they're no longer needed.
Design for Different Screens
The small size and portability of mobiles are a challenge for creating good interfaces, particularly when users are demanding an increasingly striking and information-rich graphical user experience. Combined with the wide range of screen sizes that make up the Android device ecosystem, creating consistent, intuitive, and pleasing user interfaces can be a significant challenge.
Write your applications knowing that users will often only glance at the screen. Make your applications intuitive and easy to use by reducing the number of controls and putting the most important information front and center.
Graphical controls, such as the ones you'll create in Chapter 4, are an excellent means of displaying a lot of information in a way that's easy to understand. Rather than a screen full of text with a lot of buttons and text-entry boxes, use colors, shapes, and graphics to convey information.
You'll also need to consider how touch input is going to affect your interface design. The time of the stylus has passed; now it's all about finger input, so make sure your Views are big enough to support interaction using a finger on the screen. To support accessibility and non-touch screen devices such as Google TV, you need to ensure your application is navigable without relying purely on touch.
Android devices are now available with a variety of screen sizes, from small-screen QVGA phones to 10.1” tablets and 46” Google TVs. As display technology advances and new Android devices are released, screen sizes and resolutions will be increasingly varied. To ensure that your application looks good and behaves well on all the possible host devices, you need to design and test your application on a variety of screens, optimizing for small screens and tablets, but also ensuring that your UIs scale well on any display.
You'll learn some techniques for optimizing your UI for different screen sizes in Chapters 3 and 4.
Expect Low Speeds, High Latency
The ability to incorporate some of the wealth of online information within your applications is incredibly powerful. Unfortunately, the mobile Web isn't as fast, reliable, or readily available as we would like; so, when you're developing your Internet-based applications, it's best to assume that the network connection will be slow, intermittent, and expensive.
With unlimited 4G data plans and citywide Wi-Fi, this is changing, but designing for the worst case ensures that you always deliver a high-standard user experience. This also means making sure that your applications can handle losing (or not finding) a data connection.
The Android Emulator enables you to control the speed and latency of your network connection. Figure 2.12 shows the Emulator's network connection speed and latency, simulating a distinctly suboptimal EDGE connection.
Figure 2.12
Experiment to ensure seamlessness and responsiveness no matter what the speed, latency, and availability of network access. Some techniques include limiting the functionality of your application, or reducing network lookups to cached bursts, when the available network connection supports only limited data transfer capabilities.
In Chapter 6, “Using Internet Resources,” you'll learn how to use Internet resources in your applications.
Further details, including how to detect the kind of network connections available at run time, are included in Chapter 16, “Bluetooth, NFC, Networks, and Wi-Fi.”
At What Cost?
If you're a mobile device owner, you know all too well that some of your device's functionality can literally come at a price. Services such as SMS and data transfer can incur additional fees from your service provider.
It's obvious why any costs associated with functionality in your applications should be minimized, and that users should be made aware when an action they perform might result in their being charged.
It's a good approach to assume that there's a cost associated with any action involving an interaction with the outside world. In some cases (such as with GPS and data transfer), the user can toggle Android settings to disable a potentially costly action. As a developer, it's important that you use and respect those settings within your application.
In any case, it's important to minimize interaction costs by doing the following:
· Transferring as little data as possible
· Caching data and geocoding results to eliminate redundant or repetitive lookups
· Stopping all data transfers and GPS updates when your Activity is not visible in the foreground (provided they're only used to update the UI)
· Keeping the refresh/update rates for data transfers (and location lookups) as low as practicable
· Scheduling big updates or transfers at off-peak times or when connected via Wi-Fi by using Alarms and Broadcast Receivers, as shown in Chapter 9
· Respecting the user's preferences for background data transfers
Often the best solution is to use a lower-quality option that comes at a lower cost.
When using location-based services, as described in Chapter 13, “Maps, Geocoding, and Location-Based Services,” you can select a location provider based on whether there is an associated cost. Within your location-based applications, consider giving users the choice of lower cost or greater accuracy.
In some circumstances costs are either hard to define or different for different users. Charges for services vary between service providers and contract plans. Although some people will have free unlimited data transfers, others will have free SMS.
Rather than enforcing a particular technique based on which seems cheaper, consider letting your users choose. For example, when users are downloading data from the Internet, ask them if they want to use any network available or limit their transfers to times when they're connected via Wi-Fi.
Considering the User's Environment
You can't assume that your users will think of your application as the most important feature of their device.
Although Android has already expanded beyond its roots as a mobile phone platform, most Android devices are phones or tablet devices. For most people, such a device is first and foremost a phone, secondly an SMS and email communicator, thirdly a camera, and fourthly an MP3 player. The applications you write will most likely be in the fifth category of “useful stuff.”
That's not a bad thing—they'll be in good company with others, including Google Maps and the web browser. That said, each user's usage model will be different; some people will never use their device to listen to music, some devices don't support telephony, and some don't include cameras — but the multitasking principle inherent in a device as ubiquitous as it is indispensable is an important consideration for usability design.
It's also important to consider when and how your users will use your applications. People use their mobiles all the time—on the train, walking down the street, or even while driving their cars. You can't make people use their phones appropriately, but you can make sure that your applications don't distract them any more than necessary.
What does this mean in terms of software design? Make sure that your application:
· Is predictable and well behaved—Start by ensuring that your Activities suspend when they're not in the foreground. Android fires event handlers when your Activity is paused or resumed, so you can pause UI updates and network lookups when your application isn't visible—there's no point updating your UI if no one can see it. If you need to continue updating or processing in the background, Android provides a Service class designed for this purpose, without the UI overheads.
· Switches seamlessly from the background to the foreground—With the multitasking nature of mobile devices, it's likely that your applications will regularly move into and out of the background. It's important that they “come to life” quickly and seamlessly. Android's nondeterministic process management means that if your application is in the background, there's every chance it will get killed to free resources. This should be invisible to the user. You can ensure seamlessness by saving the application state and queuing updates so that your users don't notice a difference between restarting and resuming your application. Switching back to it should be seamless, with users being shown the UI and application state they last saw.
· Is polite—Your application should never steal focus or interrupt a user's current Activity. Instead, use Notifications (detailed in Chapter 10) to request your user's attention when your application isn't in the foreground. There are several ways to alert users—for example, incoming calls are announced by a ringtone and/or vibration; when you have unread messages, the LED flashes; and when you have new voice mail, a small unread mail icon appears in the status bar. All these techniques and more are available to your application using the Notifications mechanism.
· Presents an attractive and intuitive UI—Your application is likely to be one of several in use at any time, so it's important that the UI you present is easy to use. Spend the time and resources necessary to produce a UI that is as attractive as it is functional, and don't force users to interpret and relearn your application every time they load it. Using it should be simple, easy, and obvious—particularly given the limited screen space and distracting user environment.
· Is responsive—Responsiveness is one of the most critical design considerations on a mobile device. You've no doubt experienced the frustration of a “frozen” piece of software; the multifunctional nature of a mobile makes this even more annoying. With the possibility of delays caused by slow and unreliable data connections, it's important that your application use worker threads and background Services to keep your Activities responsive and, more important, to stop them from preventing other applications from responding promptly.
Developing for Android
Nothing covered so far is specific to Android; the preceding design considerations are just as important in developing applications for any mobile device. In addition to these general guidelines, Android has some particular considerations.
Take a few minutes to read the design best practices included in Google's Android Dev Guide at http://developer.android.com/guide/index.html.
The Android design philosophy demands that applications be designed for:
· Performance
· Responsiveness
· Freshness
· Security
· Seamlessness
· Accessibility
Being Fast and Efficient
In a resource-constrained environment, being fast means being efficient. A lot of what you already know about writing efficient code will be applicable to Android, but the limitations of embedded systems and the use of the Dalvik VM mean you can't take things for granted.
The smart bet for advice is to go to the source. The Android team has published some specific guidance on writing efficient code for Android, so rather than reading a rehash of its advice, visit http://developer.android.com/guide/practices/design/performance.html for suggestions.
You may find that some of these performance suggestions contradict established design practices—for example, avoiding the use of internal setters and getters or preferring virtual classes over using interfaces. When writing software for resource-constrained systems such as embedded devices, there's often a compromise between conventional design principles and the demand for greater efficiency.
One of the keys to writing efficient Android code is not to carry over assumptions from desktop and server environments to embedded devices.
At a time when 2 to 4GB of memory is standard for most desktop and server rigs, typical smartphones feature approximately 200MB of SDRAM. With memory such a scarce commodity, you need to take special care to use it efficiently. This means thinking about how you use the stack and heap, limiting object creation, and being aware of how variable scope affects memory use.
Being Responsive
Android takes responsiveness very seriously. Android enforces responsiveness with the Activity Manager and Window Manager. If either service detects an unresponsive application, it will display an “[Application] is not responding” dialog—previously described as a force close error, as shown in Figure 2.13.
Figure 2.13
This alert is modal, steals focus, and won't go away until you press a button. It's pretty much the last thing you ever want to confront a user with.
Android monitors two conditions to determine responsiveness:
· An application must respond to any user action, such as a key press or screen touch, within five seconds.
· A Broadcast Receiver must return from its onReceive handler within 10 seconds.
The most likely culprit in cases of unresponsiveness is a lengthy task being performed on the main application thread. Network or database lookups, complex processing (such as the calculating of game moves), and file I/O should all be moved off the main thread to ensure responsiveness. There are a number of ways to ensure that these actions don't exceed the responsiveness conditions, in particular by using Services and worker threads, as shown in Chapter 9.
Android 2.3 (API level 9) introduced Strict Mode—an API that makes it easier for you to discover file I/O and network transfers being performed on the main application thread. Strict Mode is described in more detail in Chapter 18.
The “[Application] is not responding” dialog is a last resort of usability; the generous five-second limit is a worst-case scenario, not a target. Users will notice a regular pause of anything more than one-half second between key press and action. Happily, a side effect of the efficient code you're already writing will be more responsive applications.
Ensuring Data Freshness
The ability to multitask is a key feature in Android. One of the most important use cases for background Services is to keep your application updated while it's not in use.
Where a responsive application reacts quickly to user interaction, a fresh application quickly displays the data users want to see and interact with. From a usability perspective, the right time to update your application is immediately before the user plans to use it. In practice, you need to weigh the update frequency against its effect on the battery and data usage.
When designing your application, it's critical that you consider how often you will update the data it uses, minimizing the time users are waiting for refreshes or updates, while limiting the effect of these background updates on the battery life.
Developing Secure Applications
Android applications have access to networks and hardware, can be distributed independently, and are built on an open-source platform featuring open communication, so it shouldn't be surprising that security is a significant consideration.
For the most part, users need to take responsibility for the applications they install and the permissions requests they accept. The Android security model sandboxes each application and restricts access to services and functionality by requiring applications to declare the permissions they require. During installation users are shown the application's required permissions before they commit to installing it.
You can learn more about Android's security model in Chapter 18, “Advanced Android Development,” and at http://developer.android.com/resources/faq/security.html.
This doesn't get you off the hook. You not only need to make sure your application is secure for its own sake, but you also need to ensure that it doesn't “leak” permissions and hardware access to compromise the device. You can use several techniques to help maintain device security, and they'll be covered in more detail as you learn the technologies involved. In particular, you should do the following:
· Require permissions for any Services you publish or Intents you broadcast. Take special care when broadcasting an Intent that you aren't leaking secure information, such as location data.
· Take special care when accepting input to your application from external sources, such as the Internet, Bluetooth, NFC, Wi-Fi Direct, SMS messages, or instant messaging (IM). You can find out more about using Bluetooth, NFC, Wi-Fi Direct, and SMS for application messaging in Chapters 16 and 17.
· Be cautious when your application may expose access to lower-level hardware to third-party applications.
· Minimize the data your application uses and which permissions it requires.
For reasons of clarity and simplicity, many of the examples in this book take a relaxed approach to security. When you're creating your own applications, particularly ones you plan to distribute, this is an area that should not be overlooked.
Ensuring a Seamless User Experience
The idea of a seamless user experience is an important, if somewhat nebulous, concept. What do we mean by seamless? The goal is a consistent user experience in which applications start, stop, and transition instantly and without perceptible delays or jarring transitions.
The speed and responsiveness of a mobile device shouldn't degrade the longer it's on. Android's process management helps by acting as a silent assassin, killing background applications to free resources as required. Knowing this, your applications should always present a consistent interface, regardless of whether they're being restarted or resumed.
With an Android device typically running several third-party applications written by different developers, it's particularly important that these applications interact seamlessly. Using Intents, applications can provide functionality for each other. Knowing your application may provide, or consume, third-party Activities provides additional incentive to maintain a consistent look and feel.
Use a consistent and intuitive approach to usability. You can create applications that are revolutionary and unfamiliar, but even these should integrate cleanly with the wider Android environment.
Persist data between sessions, and when the application isn't visible, suspend tasks that use processor cycles, network bandwidth, or battery life. If your application has processes that need to continue running while your Activities are out of sight, use a Service, but hide these implementation decisions from your users.
When your application is brought back to the front, or restarted, it should seamlessly return to its last visible state. As far as your users are concerned, each application should be sitting silently, ready to be used but just out of sight.
You should also follow the best-practice guidelines for using Notifications and use generic UI elements and themes to maintain consistency among applications.
There are many other techniques you can use to ensure a seamless user experience, and you'll be introduced to some of them as you discover more of the possibilities available in Android in the upcoming chapters.
Providing Accessibility
When designing and developing your applications, it's important not to assume that every user will be exactly like you. This has implications for internationalization and usability but is critical for providing accessible support for users with disabilities that require them to interact with their Android devices in different ways.
Android provides facilities to help these users navigate their devices more easily using text-to-speech, haptic feedback, and trackball or D-pad navigation.
To provide a good user experience for everyone—including people with visual, physical, or age-related disabilities that prevent them from fully using or seeing a touchscreen—you can leverage Android's accessibility layer.
Best practices for making your application accessible are covered in detail in Chapter 11, “Advanced User Experience.”
As a bonus, the same steps required to help make your touchscreen applications useful for users with disabilities will also make your applications easier to use on non-touch screen devices, such as GoogleTV.
Android Development Tools
The Android SDK includes several tools and utilities to help you create, test, and debug your projects. A detailed examination of each developer tool is outside the scope of this book, but it's worth briefly reviewing what's available. For additional details, check out the Android documentation athttp://developer.android.com/guide/developing/tools/index.html.
As mentioned earlier, the ADT plug-in conveniently incorporates many of these tools into the Eclipse IDE, where you can access them from the DDMS perspective, including the following:
· The Android Virtual Device and SDK Managers—Used to create and manage AVDs and to download SDK packages, respectively. The AVD hosts an Emulator running a particular build of Android, letting you specify the supported SDK version, screen resolution, amount of SD card storage available, and available hardware capabilities (such as touchscreens and GPS).
· The Android Emulator—An implementation of the Android VM designed to run within an AVD on your development computer. Use the Emulator to test and debug your Android applications.
· Dalvik Debug Monitoring Service (DDMS)—Use the DDMS to monitor and control the Emulators on which you're debugging your applications.
· Android Debug Bridge (ADB)—A client-server application that provides a link to virtual and physical devices. It lets you copy files, install compiled application packages (.apk), and run shell commands.
· Logcat—A utility used to view and filter the output of the Android logging system.
· Android Asset Packaging Tool (AAPT)—Constructs the distributable Android package files (.apk).
The following additional tools are also available:
· SQLite3—A database tool that you can use to access the SQLite database files created and used by Android.
· Traceview and dmtracedump—Graphical analysis tools for viewing the trace logs from your Android application.
· Hprof-conv—A tool that converts HPROF profiling output files into a standard format to view in your preferred profiling tool.
· MkSDCard—Creates an SD card disk image that can be used by the Emulator to simulate an external storage card.
· Dx—Converts Java .class bytecode into Android .dex bytecode.
· Hierarchy Viewer—Provides both a visual representation of a layout's View hierarchy to debug and optimize your UI, and a magnified display to get your layouts pixel-perfect.
· Lint—A tool that analyzes your application and its resources to suggest improvements and optimizations.
· Draw9patch: A handy utility to simplify the creation of NinePatch graphics using a WYSIWYG editor.
· Monkey and Monkey Runner: Monkey runs within the VM, generating pseudo-random user and system events. Monkey Runner provides an API for writing programs to control the VM from outside your application.
· ProGuard—A tool to shrink and obfuscate your code by replacing class, variable, and method names with semantically meaningless alternatives. This is useful to make your code more difficult to reverse engineer.
Now take a look at some of the more important tools in more detail.
The Android Virtual Device Manager
The Android Virtual Device Manager is used to create and manage the virtual devices that will host instances of the Emulator.
AVDs are used to simulate the software builds and hardware configurations available on different physical devices. This lets you test your application on a variety of hardware platforms without needing to buy a variety of phones.
The Android SDK doesn't include any prebuilt virtual devices, so you will need to create at least one device before you can run your applications within an Emulator.
Each virtual device is configured with a name, a target build of Android (based on the SDK version it supports), an SD card capacity, and screen resolution, as shown in the Create new Android Virtual Device (AVD) dialog in Figure 2.14.
Figure 2.14
You can also choose to enable snapshots to save the Emulator state when it's closed. Starting a new Emulator from a snapshot is significantly faster.
Each virtual device also supports a number of specific hardware settings and restrictions that can be added in the form of name-value pairs (NVPs) in the hardware table. Selecting one of the built-in skins will automatically configure these additional settings corresponding to the device the skin represents.
The additional settings include the following:
· Maximum VM heap size
· Screen pixel density
· SD card support
· Existence of D-pad, touchscreen, keyboard, and trackball hardware
· Accelerometer, GPS, and proximity sensor support
· Available device memory
· Camera hardware (and resolution)
· Support for audio recording
· Existence of hardware back and home keys
Different hardware settings and screen resolutions will present alternative UI skins to represent the different hardware configurations. This simulates a variety of mobile device types. Some manufacturers have made hardware presets and virtual device skins available for their devices. Some, including Samsung, are available as SDK packages.
Android SDK Manager
The Android SDK Manager can be used to see which version of the SDK you have installed and to install new SDKs when they are released.
Each platform release is displayed, along with the platform tools and a number of additional support packages. Each platform release includes the SDK platform, documentation, tools, and examples corresponding to that release.
The Android Emulator
The Emulator is available for testing and debugging your applications.
The Emulator is an implementation of the Dalvik VM, making it as valid a platform for running Android applications as any Android phone. Because it's decoupled from any particular hardware, it's an excellent baseline to use for testing your applications.
Full network connectivity is provided along with the ability to tweak the Internet connection speed and latency while debugging your applications. You can also simulate placing and receiving voice calls and SMS messages.
The ADT plug-in integrates the Emulator into Eclipse so that it's launched automatically within the selected AVD when you run or debug your projects. If you aren't using the plug-in or want to use the Emulator outside of Eclipse, you can telnet into the Emulator and control it from its console. (For more details on controlling the Emulator, check out the documentation at http://developer.android.com/guide/developing/tools/emulator.html.)
To execute the Emulator, you first need to create a virtual device, as described in the previous section. The Emulator will launch the virtual device and run a Dalvik instance within it.
At the time of this writing, the Emulator doesn't implement all the mobile hardware features supported by Android. For example, it does not implement the camera, vibration, LEDs, actual phone calls, accelerometer, USB connections, audio capture, or battery charge level.
The Dalvik Debug Monitor Service
The Emulator enables you to see how your application will look, behave, and interact, but to actually see what's happening under the surface, you need the Dalvik Debug Monitoring Service. The DDMS is a powerful debugging tool that lets you interrogate active processes, view the stack and heap, watch and pause active threads, and explore the filesystem of any connected Android device.
The DDMS perspective in Eclipse also provides simplified access to screen captures of the Emulator and the logs generated by LogCat.
If you're using the ADT plug-in, the DDMS tool is fully integrated into Eclipse and is available from the DDMS perspective. If you aren't using the plug-in or Eclipse, you can run DDMS from the command line (it's available from the tools folder of the Android SDK), and it will automatically connect to any running device or Emulator.
The Android Debug Bridge
The Android Debug Bridge (ADB) is a client-service application that lets you connect with an Android device (virtual or actual). It's made up of three components:
· A daemon running on the device or Emulator
· A service that runs on your development computer
· Client applications (such as the DDMS) that communicate with the daemon through the service
As a communications conduit between your development hardware and the Android device/Emulator, the ADB lets you install applications, push and pull files, and run shell commands on the target device. Using the device shell, you can change logging settings and query or modify SQLite databases available on the device.
The ADT tool automates and simplifies a lot of the usual interaction with the ADB, including application installation and updating, file logging, and file transfer (through the DDMS perspective).
To learn more about what you can do with the ADB, check out the documentation at http://developer.android.com/guide/developing/tools/adb.html.
The Hierarchy Viewer and Lint Tool
To build applications that are fast and responsive, you need to optimize your UI. The Hierarchy Viewer and Lint tools help you analyze, debug, and optimize the XML layout definitions used within your application.
The Hierarchy Viewer displays a visual representation of the structure of your UI layout. Starting at the root node, the children of each nested View (including layouts) is displayed in a hierarchy. Each View node includes its name, appearance, and identifier.
To optimize performance, the performance of the layout, measure, and draw steps of creating the UI of each View at runtime is displayed. Using these values, you can learn the actual time taken to create each View within your hierarchy, with colored “traffic light” indicators showing the relative performance for each step. You can then search within your layout for Views that appear to be taking longer to render than they should.
The Lint tool helps you to optimize your layouts by checking them for a series of common inefficiencies that can have a negative impact on your application's performance. Common issues include a surplus of nested layouts, a surplus of Views within a layout, and unnecessary parent Views.
Although a detailed investigation into optimizing and debugging your UI is beyond the scope of this book, you can find further details at http://developer.android.com/guide/developing/debugging/debugging-ui.html.
Monkey and Monkey Runner
Monkey and Monkey Runner can be used to test your applications stability from a UI perspective.
Monkey works from within the ADB shell, sending a stream of pseudo-random system and UI events to your application. It's particularly useful to stress test your applications to investigate edge-cases you might not have anticipated through unconventional use of the UI.
Alternatively, Monkey Runner is a Python scripting API that lets you send specific UI commands to control an Emulator or device from outside the application. It's extremely useful for performing UI, functional, and unit tests in a predictable, repeatable fashion.