Monday, July 25, 2011

Getting strict in an existing Honeycomb application

During the last few months I have been involved in the development of a tablet based application targeting Honeycomb that does pretty heavy use of network and database access. We have been relying on a set of acceptance tests to evaluate its performance, but taking giant leaps of faith is nothing to be proud of. So... people who love to make sure that they are doing things the right way: welcome StrictMode!

What is StrictMode?
It's a developer tool introduced in Gingerbread that is mostly used to detect lengthy operations running on your application's main thread, such as disk reads/writes and network access. It can also catch leaked SQLite cursors and resources that have not been properly closed. You can display an annoying dialog when a violation happens, log it or just crash the entire application. There are several articles describing StrictMode in detail, so go read them first.

The configuration used was the proposed in the reference documentation:
public void onCreate() {
     if (DEVELOPER_MODE) {
      StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
       .detectDiskReads()
       .detectDiskWrites()
       .detectNetwork()
       .penaltyLog()
       .build());
      
      StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
       .detectLeakedSqlLiteObjects()
       .detectLeakedClosableObjects()
       .penaltyLog()
       .penaltyDeath()
       .build());
     }
     
     
     super.onCreate();
}

What did we find?
  • Disk access
Each time a shared preference was read or written from the UI thread a violation got logged.  I don't feel the need to move this to a separate thread, so I've just ignored these violations.

Calls to getReadableDatabase and getWritableDatabase were also considered to be violations. The documentation of SQLiteOpenHelper states that both these methods should not be called from the application main thread, so this was something new.

There were a lot of image files accidentally being read from the filesystem within the main thread. The fix was simple and the impact in performance was huge.
  • Leaked cursors
The database usage is somewhat extensive and there is a lot of code dealing with cursors. Of course we've screwed it in some places. StrictMode showed several unclosed cursors across the application.
  • Network access
There were no violations related to network access. Hooray for the team!
  • Other issues found unrelated to responsiveness
Most of the application fragments don't have a default constructor. This hasn't been an issue until now, but enabling StrictMode started throwing a lot of instantiation exceptions. The application doesn't run on portrait mode for now, but enabling orientation changes did also throw this type of exception. Hence, there seems to be Android code relying on fragments having a default constructor. The solution was to provide a default constructor and handle instantiation parameters from factory methods as shown in the fragments documentation.

Conclusion
StrictMode is an invaluable tool that should be enabled from the beginning of every project. We are currently enabling it in all our ongoing projects and it's helping to make us feel a little less stupid every time we catch something right on the first launch.

Monday, June 6, 2011

The quest for the ultimate feed parser in Android

Last week we started a new project at NASA Trained Monkeys that requires to parse feeds from well-known sources, such as CNN or the BBC. Piece of cake, you say to yourself, I'll do a quick search for the best pure Java RSS/Atom feed parser and get rolling in seconds. Well... not so fast. It is not so straightforward.

The problem
Android uses Dalvik as its virtual machine, which includes a subset of the Apache Harmony Java implementation. As a result, all the software that relies for example on AWT or Swing is not going to run under Dalvik. This software includes one of the most popular feed parsers for Java: rome.

For a full list of the J2SE packages that are not supported go here.

The options
It's a lightweight Android library hosted at Github that can be easily integrated to your project via maven or the standard library management.  The syntax is really nice and simple.

The main drawback is that it only supports RSS 2.0, so it doesn't suit our current needs.

Cool project. Unlike the other parsers you have to register a listener to hook into the different parsing stages. It is fully customizable.

The downside: it used to be in the sandbox of the Apache Commons project, but it has been moved to the Dormant category. This means that there is no active development and that the project is unlikely to be continued anytime soon. The projects in this category have to be built from source.

  • Build your own
There is a nice article covering the basics of working with XML in Android and feed parsers here. If you plan on building your own feed parser you should consider forking the android-rss project and adding support for the missing formats instead.

You can't use the rome feed reader as-is because it makes heavy use of java.bean classes, which are not present in Dalvik. This project is a repackaging of rome and jdom so they work properly on Android. The only thing I don't like about rome is the lack of support for generics and all the goodies introduced in Java 5, as it targets a lower version of the JDK.

The syntax is really straightforward:

URL feedUrl = new URL(url);

SyndFeedInput input = new SyndFeedInput();
SyndFeed feed = input.build(new XmlReader(feedUrl));

List entries = feed.getEntries();
Iterator iterator = entries.listIterator();
   
while (iterator.hasNext()) {
 SyndEntry entry = (SyndEntry) iterator.next();
 // Do stuff with each entry
}

Conclusion
We are sticking to the repackaged rome option for our current project. It would be great to contribute to android-rss to make it fulfill our requirements, as it seems to be the only option that is aimed specifically at Android.

What other tools are you guys using? I'll be glad to hear about some alternatives.

Tuesday, May 3, 2011

The revenge of the SyncAdapter: Synchronizing data in Android

Introduction
Most of the applications that are interesting in some way are connected to live data on the Internet. A common scenario is then how to keep local and remote content synchronized. Android does provide an easy and straightforward mechanism to achieve this synchronization: the SyncAdapter.

What is a SyncAdapter?
Stop anything you are doing right now and watch this excellent video by Virgil Dobjanschi. If you are in a hurry forward to 44' when Virgil introduces the third pattern.


To sum up Virgil's words: a SyncAdapter is just a service that is started by the sync manager, which in turn maintains a queue of SyncAdapters. You delegate the responsibility for choosing when to sync to the system, who knows better than you what's going on and which other applications are synchronizing data. The sync manager does also schedule resyncs according to the errors reported by the SyncAdapter, making your code cleaner and eliminating the need of alarms.

How does this work?
The basic idea is to mirror the remote data in a local database and access it through a ContentProvider. You then access this local ContentProvider from you app. You fetch data from it and impact any changes on it.
The SyncAdapter will be in charge of making the remote content match your local data. It will fetch new data and push the local changes to the server.

Writing a ContentProvider
Writing a ContentProvider is outside the scope of this entry, so go read it anywhere else, like here, here or here. There are some other players that we need to take care of.

Adding an account
You need to add an account in order to use a SyncAdapter. This account will appear in the Accounts & sync section of the settings application. From this section the user is able to add, modify or remove his account, as well as choosing what content to synchronize. Your application will also play fairly when the user enables automatic synchronization.
The AccountManager is in charge of managing user credentials. The user enters his credentials just once and all the applications that have the USE_CREDENTIALS permission can query the manager to obtain an authentication token.
In order to add an account to your application you have to extend the AbstractAccountAuthenticator class. It is okay to return null values from the methods you are not going to use. The important ones are addAcount and getAuthToken.

@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType,
String[] requiredFeatures, Bundle options)
throws NetworkErrorException {

final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
authTokenType);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
You have probably noticed that there is a reference to an activity named AuthenticatorActivity. This is just an activity that asks for the user credentials. It will be prompted when the user adds a new account from settings. You can of course use the same activity than when your application is launched.
The other important method to override is getAuthToken. You will call this method from your SyncAdapter if you need to do any authenticated requests to the server. It will return the same token until it is explicitly invalidated in the account manager. Here is where the logic to get an auth token from the server will be placed.

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle options)
throws NetworkErrorException {
if (!authTokenType.equals(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE)) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ERROR_MESSAGE,
"invalid authTokenType");
return result;
}
final AccountManager am = AccountManager.get(mContext);
final String password = am.getPassword(account);
if (password != null) {
boolean verified = callSomeLoginServiceThatReturnsTrueIfValid(
account.name, password);

if (verified) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE,
AuthenticatorActivity.PARAM_ACCOUNT_TYPE);
result.putString(AccountManager.KEY_AUTHTOKEN,
hereGoesTheReceivedToken);
return result;
}
}
// Password is missing or incorrect. Start the activity to add the missing data.
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
// ...
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}

Android provides a handy class called AccountAuthenticatorActivity that has a method to set the authentication result back to the account authenticator. Here is the relevant code of our AuthenticatorActivity that adds the account and sets the result.
private void finishLogin(String token) {
final Account account = new Account(mUsername, ACCOUNT_TYPE);
if (mRequestNewAccount) {
mAccountManager.addAccountExplicitly(account, mPassword, null);
// Extension point. Here we will set up the auto sync for different
// services
// Example: ContentResolver.setSyncAutomatically(account,
// ContactsContract.AUTHORITY, true);
} else {
mAccountManager.setPassword(account, mPassword);
}
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
if (token != null && token.equals(AUTHTOKEN_TYPE)) {
intent.putExtra(AccountManager.KEY_AUTHTOKEN, token);
}
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}

There is still one step remaining. Android does not call our implementation of the AbstractAccountAuthenticator directly. It must be wrapped in a service that returns a subclass of AbstractAccountAuthenticator from its onBind method.
public class AuthenticationService extends Service {
private static final String TAG = "AuthenticationService";
private Authenticator mAuthenticator;

@Override
public void onCreate() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Authentication Service started.");
}

mAuthenticator = new Authenticator(this);
}

@Override
public void onDestroy() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Authentication Service stopped.");
}
}

@Override
public IBinder onBind(Intent intent) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getBinder() ... returning AccountAuthenticator binder");
}

return mAuthenticator.getIBinder();
}
}

Finally, register the service in the AndroidManifest.xml file and filter the action named android.accounts.AccountAuthenticator. The permissions required for account management are GET_ACCOUNTS, USE_CREDENTIALS, MANAGE_ACCOUNTS and AUTHENTICATE_ACCOUNTS.

Writing the SyncAdapter
First of all throw a file in the res/xml folder that describes the SyncAdapter and what kind of content it is going to synchronize.
<sync-adapter   xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.nasatrainedmonkeys.app.providers.SomeContentProvider"
android:accountType="com.nasatrainedmonkeys.account" />

In order to write a SyncAdapter you have to extend the AbstractThreadedSyncAdapter class and override its onPerformSync method. This method will be run in a newly spawned thread when no other sync operation is running. One of the parameters is an instance of the SyncResult class which is used to inform the SyncManager about any errors in the sync process. With this result the SyncManager can decide whether to reschedule the sync in the future or not.
public class SampleSyncAdapter extends AbstractThreadedSyncAdapter {
private AccountManager mAccountManager;
private ContentResolver mContentResolver;

public SampleSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);

mAccountManager = AccountManager.get(context);
mContentResolver = context.getContentResolver();
}

@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {

String authtoken = null;
try {
authtoken = mAccountManager.blockingGetAuthToken(account,
AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, true);

// Dummy sample. Do whatever you want in this method.
List data = fetchData(authtoken);

syncRemoteDeleted(data);
syncFromServerToLocalStorage(data);
syncDirtyToServer(authtoken, getDirtyList(mContentResolver));
} catch (Exception e) {
handleException(authtoken, e, syncResult);
}
}

private void handleException(String authtoken, Exception e,
SyncResult syncResult) {
if (e instanceof AuthenticatorException) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "AuthenticatorException", e);
} else if (e instanceof OperationCanceledException) {
Log.e(TAG, "OperationCanceledExcepion", e);
} else if (e instanceof IOException) {
Log.e(TAG, "IOException", e);
syncResult.stats.numIoExceptions++;
} else if (e instanceof AuthenticationException) {
mAccountManager.invalidateAuthToken(
AuthenticatorActivity.PARAM_ACCOUNT_TYPE, authtoken);
// The numAuthExceptions require user intervention and are
// considered hard errors.
// We automatically get a new hash, so let's make SyncManager retry
// automatically.
syncResult.stats.numIoExceptions++;
Log.e(TAG, "AuthenticationException", e);
} else if (e instanceof ParseException) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "ParseException", e);
} else if (e instanceof JsonParseException) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "JSONException", e);
}
}

// ...
}

The only missing step is to return the sync adapter from the onBind method of a wrapping service and register the service in the AndroidManifest.xml to filter the intents with action android.content.SyncAdapter.
public class SampleSyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static SampleSyncAdapter sSyncAdapter = null;

@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new SampleSyncAdapter(getApplicationContext(), true);
}
}
}

@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}
}
Conclusion
Use SyncAdapters. They take away the pain of manually managing alarms and awkward synchronization retries. Let the system be responsible for choosing the best time to sync your data and play nicely with the rest of the applications.

References