Architecting Android Apps
1. About Marko Gargenta
1.1. Entrepreneur, Author, Speaker
-
Developer of Android Bootcamp for Marakana.
-
Instructor for 1,000s of developers on Android at Qualcomm, Cisco, Motorola, Intel, DoD and other great orgs.
-
Author of Learning Android published by O???Reilly.
-
Speaker at OSCON (4x), ACM, IEEE(2x), SDC(2x), AnDevCon(2x), DroidCon.
-
Co-Founder of SFAndroid.org
-
Co-Chair of Android Open conference: Android Open
2. Overview
Seven iterations of an app:
-
Part 1 - Activities and Android UI
-
Part 2 - Intents, Action Bar, and More
-
Part 3 - Services
-
Part 4 - Content Providers
-
Part 5 - Lists and Adapters
-
Part 6 - Broadcast Receivers
-
Part 7 - App Widgets
3. Yamba
3.1. Objectives of Yamba
The objective of this module is to explain how to go about designing a typical Android app. You will have a chance to see an example app, Yamba, using most of the standard Android building blocks. By the end of this talk, you should have high-level understanding about designing an Android app.
4. Yamba Approach
- Yamba Overview
-
-
Yamba: Yet Another Micro-Blogging App
-
Comprehensive Android example application.
-
Works with services that support Twitter API.
-
- Project Philosophy
-
-
Small increments - build it organically
-
App must always run - whole and complete
-
Refactor when needed - do simplest thing first
-
5. Part 1 - Activities and Android UI
6. Activity Overview
-
An activity is roughly a screen.
-
A typical application may have many activities.
-
Activities are typically expensive and so are managed by the system.
7. Activity Lifecycle
-
Activities are highly managed by the system’s ActivityManager.
-
ActivityManager drives the activity through its states.
-
You as an app developer get to say what happens on transitions.
8. Activity Lifecycle Explored
D/ActivityDemo( 6708): onCreate D/ActivityDemo( 6708): onStart D/ActivityDemo( 6708): onResume
... D/ActivityDemo( 6708): onClickAnotherActivity D/ActivityDemo( 6708): onPause D/ActivityDemo( 6708): onStop D/ActivityDemo( 6708): onRestart D/ActivityDemo( 6708): onStart D/ActivityDemo( 6708): onResume
... D/ActivityDemo( 6708): onPause D/ActivityDemo( 6708): onStop D/ActivityDemo( 6708): onDestroy D/ActivityDemo( 6708): onCreate D/ActivityDemo( 6708): onStart D/ActivityDemo( 6708): onResume
... D/ActivityDemo( 6708): onPause D/ActivityDemo( 6708): onStop
... D/ActivityDemo( 6708): onRestart D/ActivityDemo( 6708): onStart D/ActivityDemo( 6708): onResume
9. Activity Template
package com.marakana.android.lifecycle; import android.app.Activity; import android.content.ContentValues; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; public class ActivityDemo extends Activity { static final String TAG = "ActivityDemo"; // --- Lifecycle methods @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Log.d(TAG, "onCreate"); } @Override protected void onStart() { super.onStart(); Log.d(TAG, "onStart"); } @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume"); } @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause"); } @Override protected void onStop() { super.onStop(); Log.d(TAG, "onStop"); } @Override protected void onRestart() { super.onRestart(); Log.d(TAG, "onRestart"); } @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy"); } // --- Options menu methods @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.item_start_service: startService(new Intent(this, ServiceDemo.class)); return true; case R.id.item_stop_service: stopService(new Intent(this, ServiceDemo.class)); return true; case R.id.item_refresh: startService(new Intent("marakana.intent.action.IntentServiceDemo")); return true; case R.id.item_send_broadcast: sendBroadcast(new Intent("marakana.intent.action.ReceiverDemo")); return true; case R.id.item_location: startActivity(new Intent(this, SystemServicesDemo.class)); return true; case R.id.item_insert: getContentResolver() .insert(ProviderDemo.CONTENT_URI, new ContentValues()); return true; case R.id.item_query: getContentResolver().query(ProviderDemo.CONTENT_URI, null, null, null, null); return true; } return false; } // --- Button click event handler public void onClickAnotherActivity(View v) { startActivity(new Intent(this, AnotherActivity.class)); Log.d(TAG, "onClickAnotherActivity"); } }
10. Activity Callbacks
10.1. Lifecycle
- onCreate()
-
Used to setup your activity. You will almost always have to have it. Good place to inflate the UI and setup listeners.
- onResume() and onPause()
-
Use them to turn on and off things that you’d like to have running only while the activity is visible. This is important for things that consume a lot of battery, such as GPS and sensors.
- onStart() and onStop()
-
Use to setup code that starts/stops the activity. Unlike onResume() and onPause(), it includes Paused state as well.
- onRestart()
-
Called when the activity is restarted. It is followed by onStart() and onResume().
- onDistroy()
-
A good place to do any cleanup before the activity is cleaned up from memory. This is the counter-part to onCreate().
10.2. Other
- onCreateOptionsMenu() and onOptionsItemSelected()
-
Use them to setup your menu. onOptionsItemSelected() loads the menu, typically from an XML resource. onOptionsItemSelected() is called whenever an option menu item is clicked on.
- Various listeners and event handlers, such as onClick()
-
Used to handle the UI events.
11. Registering Activity
11.1. Main Activity
Register the activity in the Android Manifest file:
... <activity android:name=".ActivityDemo" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> ...
The intent filter specifies that this activity is to be the main entry point into the application as well as that it should be shown in the app launcher on home screen.
11.2. Any other activity
... <activity android:name=".AnotherActivity"></activity> ...
12. Building Android UI
There are two approaches to building Android UI:
12.1. Declaratively
-
Declare UI in XML
-
Eclipse provides nice drag-n-drop tools
-
Inflate the XML views in Java
12.2. Programmatically
-
Instantiate all widgets programmatically
-
Set properties for each
The best is to use both:
-
Star with declaring the look and feel using XML
-
Inflate XML into Java
-
Finish by programming the actions using Java
13. Layouts and Views
14. Part 2 - Intents, Action Bar, and More
15. Intent Overview
-
Intents are like events or messages.
-
You can use them so start activities, start/stop services, or send broadcasts.
-
Intents can be implicit or explicit.
16. Using Intents
- startActivity()
-
Starts an activity specified by the intent. If activity does not exist already, it calls onCreate() to create it. Otherwise, it calls onStart() and onResum().
- startService()
-
Starts a service. Even if the service is not created yet, it called onCreate() on the service first.
- stopService()
-
Stops a service that is already running. If service is not running, it does nothing.
- bindService()
-
Binds to a service. Requires that the service returns a binder via onBind() method.
- sendBroadcast()
-
Sends a broadcast. If there’s a broadcast receiver registered to filter for the same action as this intent is specifying, that receiver’s onReceive() method will be called.
17. Explicit and Implicit Intents
17.1. Explicit Intent
... startActivity(new Intent(this, AnotherActivity.class)); ... startService(new Intent(this, ServiceDemo.class)); ...
this is the context from which this intent is being sent, in our case an Activity.
17.2. Implicit Intent
... startService(new Intent("marakana.intent.action.IntentServiceDemo")); ... sendBroadcast(new Intent("marakana.intent.action.ReceiverDemo")); ...
Requires that there’s an intent filter filtering for this particular intent, for example:
... <service android:name=".IntentServiceDemo"> <intent-filter> <action android:name="marakana.intent.action.IntentServiceDemo" /> </intent-filter> </service> ... <receiver android:name=".ReceiverDemo"> <intent-filter> <action android:name="marakana.intent.action.ReceiverDemo" /> </intent-filter> </receiver> ...
18. Intent Filter
Intent filter is a way for us to assign certain action to an activity, service, receiver or similar.
Action is one of system defined actions, or something you come up with.
Intent filter typically goes into Android Manifest file, within <activity>, <service>, or <receiver> elements.
... <intent-filter> <action android:name="some.action.goes.here" /> </intent-filter> ...
19. Action Bar
The action bar, introduced in Honeycomb (API 11) is a title bar that includes:
-
The application icon
-
The activity title
-
A set of user-selectable actions (optional)
-
A set of user-selectable navigation modes (optional)
20. Enabling the Action Bar
Android automatically displays an action bar on an API 11+ system if the <uses-sdk> element of your applications manifest:
-
Sets minSdkVersion to 11 or later, or
-
Sets targetSdkVersion to 11 or later
Either or these settings enable the "holographic look and feel" introduced in Honeycomb, which includes action bar support.
-
If neither minSdkVersion nor targetSdkVersion are set to 11+, then an API 11+ system renders the app in a legacy theme, without action bar support.
With the action bar enabled, legacy option menu items appear automatically in the action bar’s overflow menu. You reveal the overflow menu with:
-
The hardware Menu button (if present), or
-
An additional button in the action bar (for devices without a hardware Menu button)
21. Adding Action Items
To display an option menu item as an action item, in the menu resource file add android:showAsAction="ifRoom" to the <item> element.
-
The device will display the item if there is room available in the action bar, otherwise the item appears in the overflow menu.
-
Devices running API 10 or earlier ignore the showAsAction attribute.
If your menu item supplies both a title and an icon, the action item shows only the icon by default.
-
To display the text title, add withText to the android:showAsAction attribute. For example:
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/menu_save" android:icon="@drawable/ic_menu_save" android:title="@string/menu_save" android:showAsAction="ifRoom|withText" /> </menu>
|
|
The withText value is a hint to the action bar. The action bar will show the title if possible, but might not if an icon is available and the action bar is constrained for space. |
22. ICS Split Action Bars
API 14 introduced split action bar support.
-
Enabling split action bar allows a device with a narrow screen to display a separate action bar at the bottom of the screen containing all action items.
You enable split action bar in the manifest add uiOptions="splitActionBarWhenNarrow":
-
To an <activity> element to enable split action bar for that activity, or
-
To the <application> element to enable split action bar for all activities
|
|
Devices on API 13 or earlier ignore the uiOptions attribute. |
23. Using the App Icon for Navigation
You can enable the application icon — which appears in the action bar on the left side — to behave as an action item. If enabled, your app should respond by:
-
Going to the application "home" activity, or
-
Navigating "up" the application’s structural hierarchy
When the user taps the app icon, the system calls your activity’s onOptionsItemSelected() method. The MenuItem passed as an argument has an ID of android.R.id.home.
|
|
The app icon was enabled by default in APIs 11-13. API 14 disables it be default. To enable it in API 14 or later: if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) { getActionBar().setHomeButtonEnabled(true); } |
24. Part 3 - Services
25. Service Overview
-
Services are code that runs in the background.
-
They can be started and stopped. Services doesn???t have UI.
-
Keep in mind that service runs on the main application thread, the UI thread.
26. Service Lifecycle
-
Service starts and "runs" until it gets a request to stop.
-
Service will run on the main UI thread.
-
To offload work from main thread, use intent service.
-
Intent service uses worker thread, stops when done with work.
-
Services can be bound or unbound.
27. Service Lifecycle Explored
D/ServiceDemo( 7623): onCreate D/ServiceDemo( 7623): onStartCommand
... D/ServiceDemo( 7623): onStartCommand
... D/ServiceDemo( 7623): onDestroy
D/IntentServiceDemo( 7748): onCreate D/IntentServiceDemo( 7748): onHandleIntent for action: marakana.intent.action.IntentServiceDemo D/IntentServiceDemo( 7748): onDestroy
28. Service Template
package com.marakana.android.lifecycle; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log; public class ServiceDemo extends Service { static final String TAG = "ServiceDemo"; @Override public IBinder onBind(Intent arg0) { return null; } @Override public void onCreate() { Log.d(TAG, "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand"); return START_STICKY; } @Override public void onDestroy() { Log.d(TAG, "onDestroy"); } }
29. IntentService Template
package com.marakana.android.lifecycle; import android.app.IntentService; import android.content.Intent; import android.util.Log; public class IntentServiceDemo extends IntentService { static final String TAG = "IntentServiceDemo"; public IntentServiceDemo() { super(TAG); } @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate"); } @Override protected void onHandleIntent(Intent intent) { Log.d(TAG, "onHandleIntent for action: " + intent.getAction()); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy"); } }
30. Service Callbacks
- onBind()
-
Required, but for unbound services, we just return null.
- onCreate()
-
Called when service is first created.
- onStartCommand()
-
Called is called every time service is started.
- onDestroy()
-
Called when service is stopped. It is subsequently destroyed.
31. IntentService Callbacks
- Constructor
-
It needs to pass the name of this service to its super.
- onCreate()
-
Called when service is first created.
- onHandleIntent()
-
This is where the work of the service runs.
- onDestroy()
-
Called when service is stopped. It is subsequently destroyed.
32. Registering Service
... <service android:name=".ServiceDemo"></service> ...
... <service android:name=".IntentServiceDemo"> <intent-filter> <action android:name="marakana.intent.action.IntentServiceDemo" /> </intent-filter> </service> ...
33. Part 4 - Content Providers
34. Content Provider Overview
-
Content Providers share content with applications across application boundaries.
-
Examples of built-in Content Providers are:
-
Contacts
-
MediaStore
-
Settings and more.
-
35. Typical Usage of Content Providers
36. Content Provider Lifecycle
-
Content provider is initiated first time it is used via a call to onCreate().
-
There is no callback for cleaning up after the provider.
-
When modifying the data (insert/update/delete), open/close database atomically.
-
When reading the data, leave database open or else the data will get garbage collected.
37. Content Provider Template
package com.marakana.android.lifecycle; import android.content.ContentProvider; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.util.Log; import android.provider.Settings.System; public class ProviderDemo extends ContentProvider { static final String TAG = "ProviderDemo"; static final String AUTHORITY = "content://com.marakana.android.lifecycle.providerdemo"; public static final Uri CONTENT_URI = Uri.parse(AUTHORITY); static final String SINGLE_RECORD_MIME_TYPE = "vnd.android.cursor.item/vnd.marakana.android.lifecycle.status"; static final String MULTIPLE_RECORDS_MIME_TYPE = "vnd.android.cursor.dir/vnd.marakana.android.lifecycle.status"; @Override public boolean onCreate() { Log.d(TAG, "onCreate"); return true; } @Override public String getType(Uri uri) { String ret = getContext().getContentResolver().getType(System.CONTENT_URI); Log.d(TAG, "getType returning: " + ret); return ret; } @Override public Uri insert(Uri uri, ContentValues values) { Log.d(TAG, "insert uri: " + uri.toString()); return null; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { Log.d(TAG, "update uri: " + uri.toString()); return 0; } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { Log.d(TAG, "delete uri: " + uri.toString()); return 0; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Log.d(TAG, "query with uri: " + uri.toString()); return null; } }
38. Content Provider Callbacks
- onCreate()
-
Used to initialize this content provider. This method runs on UI thread, so should be quick. Good place to instantiate database helper, if using database.
- getType()
-
Returns the mime time for the given uri. Typically, this MIME type will either be something like vnd.android.cursor.item/vnd.marakana.android.lifecycle.status for a single item or vnd.android.cursor.dir/vnd.marakana.android.lifecycle.status for multiple items.
- insert()
-
Inserts the values into the provider returning uri that points to the newly inserted record.
- update()
-
Updates records(s) specified by either the uri or selection/selectionArgs combo. Returns number of records affected.
- delete()
-
Deletes records(s) specified by either the uri or selection/selectionArgs combo. Returns number of records affected.
- query()
-
Queries the provider for the record(s) specified by either uri or`selection`/selectionArgs/grouping/having combo.
39. Registering Content Provider
... <provider android:name=".ProviderDemo" android:authorities="com.marakana.android.lifecycle.providerdemo" /> ...
The authority of this provider must match the uri authority that this provider is responding to.
40. Part 5 - Lists and Adapters
41. Lists and Adapters Overview
Adapters connect potentially large data sets to small views
42. Fragments
43. So, What’s a Fragment?
A fragment is a class implementing a portion of an activity.
-
A fragment represents a particular operation or interface running within a larger activity.
-
Fragments enable more modular activity design, making it easier to adapt an application to different screen orientations and multiple screen sizes.
-
Fragments must be embedded in activities; they cannot run independent of activities.
-
Most fragments define their own layout of views that live within the activity’s view hierarchy.
-
However, a fragment can implement a behavior that has no user interface component.
-
-
A fragment has its own lifecycle, closely related to the lifecycle of its host activity.
-
A fragment can be a static part of an activity, instantiated automatically during the activity’s creation.
-
Or, you can create, add, and remove fragments dynamically in an activity at run-time.
44. Loaders
Loaders make it easy to load data asynchronously in an activity or fragment. Loaders have these characteristics:
-
They are available to every Activity and Fragment.
-
They provide asynchronous loading of data.
-
They monitor the source of their data and deliver new results when the content changes.
-
They automatically reconnect to the last loader’s cursor when being recreated after a configuration change. Thus, they don’t need to re-query their data.
Loaders were introduced in Honeycomb (API 11).
-
The Android Support Package includes support for loaders. By including the support package in your application, you can use loaders even if your application for a minSdkVersion of 4 or later.
45. Using Loaders in an Application
An application that uses loaders typically includes the following:
-
An Activity or Fragment.
-
An instance of the LoaderManager.
-
A CursorLoader to load data backed by a ContentProvider. Alternatively, you can implement your own subclass of Loader or AsyncTaskLoader to load data from some other source.
-
A data source, such as a ContentProvider, when using a CursorLoader.
-
An implementation for LoaderManager.LoaderCallbacks. This is where you create new loader instances and manage your references to existing loaders.
-
A way of displaying the loader’s data, such as a SimpleCursorAdapter.
46. Availability of Fragments and Loaders
46.1. Fragments: Implemented in Honeycomb (3.0) or Later
Fragments were added to the Android API in Honeycomb, API 11.
The primary classes related to fragments are:
- android.app.Fragment
-
The base class for all fragment definitions
- android.app.FragmentManager
-
The class for interacting with fragment objects inside an activity
- android.app.FragmentTransaction
-
The class for performing an atomic set of fragment operations
46.2. Fragments: Implemented in Donut (1.6) or Later
Google provides the Compatibility Package, a Java library that you can include in an application, implementing support for fragments and other Honeycomb features (loaders).
47. Part 6 - Broadcast Receivers
48. Broadcast Receiver Overview
-
An Intent-based publish-subscribe mechanism.
-
Great for listening system events such as SMS messages.
49. Broadcast Receiver Template
package com.marakana.android.lifecycle; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class ReceiverDemo extends BroadcastReceiver { static final String TAG = "ReceiverDemo"; @Override public void onReceive(Context context, Intent intent) { Log.d(TAG, "onReceive action: "+intent.getAction() ); } }
50. Receiver Lifecycle Explained
D/ReceiverDemo( 7964): onReceive action: marakana.intent.action.ReceiverDemo
51. Broadcast Receiver Callbacks
- onReceive()
-
This is the only method you typically care about for a broadcast receiver. It is called when this receiver is invoked.
52. Registering Broadcast Receiver
<receiver android:name=".ReceiverDemo"> <intent-filter> <action android:name="marakana.intent.action.ReceiverDemo" /> </intent-filter> </receiver>
... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // Create the receiver receiver = new TimelineReceiver(); filter = new IntentFilter( UpdaterService.NEW_STATUS_INTENT ); } protected void onResume() { super.onResume(); super.registerReceiver(receiver, filter, "com.marakana.yamba.SEND_TIMELINE_NOTIFICATIONS", null); } @Override protected void onPause() { super.onPause(); unregisterReceiver(receiver); } ...
53. Part 7 - App Widgets
54. App Widgets Overview
-
App widgets are miniature views that can live in other apps, such as Home app.
-
They are a special implementation of Broadcast Receivers.
54.1. Declaring an App Widget
Widgets are essentially Broadcast Receivers
<receiver android:name=".YambaWidget" android:label="@string/app_name" > <intent-filter> <action android:name="com.marakana.broadcast.NEW_STATUS" /> </intent-filter> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/widget" /> </receiver>
54.2. Specifying Meta Data
Meta data specifies the default size of the widget, plus the update period.
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minHeight="72dp" android:minWidth="294dp" android:updatePeriodMillis="10000" > </appwidget-provider>
55. Implementing App Widget
package com.marakana.android.yamba; ... public class YambaWidget extends AppWidgetProvider { private static final String TAG = YambaWidget.class.getSimpleName(); @Override public void onReceive(Context context, Intent intent) { super.onReceive(context, intent); if (intent.getAction().equals(YambaApp.NEW_STATUS_BROADCAST)) { AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(context); this.onUpdate(context, appWidgetManager, appWidgetManager .getAppWidgetIds(new ComponentName(context, YambaWidget.class))); Log.d(TAG, "onReceived"); } } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); // Get the data Cursor cursor = context.getContentResolver().query( StatusProvider.CONTENT_URI, null, null, null, StatusData.SORT_BY); // Do we have any data? if (cursor!=null && cursor.moveToFirst()) { String user = cursor.getString(cursor .getColumnIndex(StatusData.C_USER)); String text = cursor.getString(cursor .getColumnIndex(StatusData.C_TEXT)); // Loop thru all the widget instances for (int appWidgetId : appWidgetIds) { RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.row); views.setTextViewText(R.id.text_user, user); views.setTextViewText(R.id.text_text, text); views.setTextViewText(R.id.text_createdAt, ""); appWidgetManager.updateAppWidget(appWidgetId, views); } } Log.d(TAG, "onUpdated"); } }
56. Yamba App Widget Output
57. Architecting Android Apps Summary
Thank you!
Marko Gargenta & Marakana Team
Special thanks to Ken Jones as well as the rest of Marakana team for research related to Fragments, Loaders, and many other new features of ICS.
Slides & video of this presentation is available at Marakana.com
Yamba source code is available at https://github.com/marakana/yamba
(c) Marakana.com
Mfawa Alfred Onen


Location: San Francisco