Batista R. Harahap

Android Speedy Apps

The first time I coded for Android was with Eclair (2.1). Bought a Nexus One to develop some apps with it. Nowadays, Android has gone a long way but still, most fundamentals to make your apps snappy still applies. I'm going down memory lane to post some of the optimizations producing effective and efficient codes.

Static Method & Properties

Like in any platform, memory is the key to performance. The better we utilize memory, the better the performance. With Android, utilizing memory is as simple as defining the right static class properties and methods. When you set a static property or method, it will always be referenced once at run time. Hence, all the references are already in the memory before you call any of them.

I usually have a Utils class defined with all my Android apps. Every property and method are static. Android comes with a pretty handy Log class to log to stdout. The first parameter for most of the methods is a String tag. It gets in the way for me. So here's an example.

  
public class Utils {  
    public static String TAG = "APP_NAME";
    public static void logger(String msg) {
        Log.v(TAG, msg);
    }
}

Interface (Listeners)

When I first learned about Java Interfaces, I fell in love at first sight. It allows me to create a very neat way of communicating between classes, activities, etc. I am building a Finance application to talk with Yahoo Finance through YQL Queries.

Here's an example from an unfinished code I'm working on.

  
package com.bango.yql;

import java.util.ArrayList;  
import java.util.List;

import org.json.JSONArray;  
import org.json.JSONObject;

import android.app.Activity;  
import android.content.Context;

import com.bango.stocksquotes.DBHelper;  
import com.loopj.android.http.AsyncHttpClient;  
import com.loopj.android.http.JsonHttpResponseHandler;  
import com.loopj.android.http.RequestParams;

public class YQLFinance {

    private static String BASE_URL = "http://query.yahooapis.com/v1/public/yql";
    private static String QUERY = "select * from yahoo.finance.quote where symbol in (%s)";

    private static AsyncHttpClient http = new AsyncHttpClient();

    private static QuotesResponseListener caller;

    public static interface QuotesResponseListener {
        void onQuotesResponseSuccess(List ids);

        void onQuotesResponseFailure(Throwable e);

        Context getContext();
    }

    public static void attach(Activity act) {
        if (act instanceof QuotesResponseListener) {
            caller = (QuotesResponseListener) act;
        } else {
            throw new ClassCastException("The calling activity must implement YQLFinance.QuotesResponseListener");
        }
    }

    public static List getQuoteSymbols() {
        if (caller  null)
            throw new ClassCastException("The calling activity must implement YQLFinance.QuotesResponseListener");

        DBHelper helper = new DBHelper(caller.getContext());
        return helper.getQuoteSymbols();
    }

    public static void getQuotes(List quotes) throws
            IllegalArgumentException, ClassCastException {
        if (caller  null)
            throw new ClassCastException("The calling activity must implement YQLFinance.QuotesResponseListener");

        RequestParams params = new RequestParams();
        params.put("format", "json");
        params.put("env", "store://datatables.org/alltableswithkeys");

        if (quotes.size()  0)
            throw new IllegalArgumentException("Quotes must have some value please.");
        else {
            String q = "";
            for (String quote : quotes) {
                q += "'" + quote + "',";
            }
            q = q.substring(0, (q.length() - 1));
            String yql = String.format(QUERY, q);
            params.put("q", yql);
        }

        http.get(BASE_URL, params, quotesHandler);
    }

    private static JsonHttpResponseHandler quotesHandler = new JsonHttpResponseHandler() {
        @Override
        public void onSuccess(JSONObject res) {
            if (caller  null)
                throw new ClassCastException("The calling activity must implement YQLFinance.QuotesResponseListener");

            DBHelper helper = new DBHelper(caller.getContext());
            List ids = new ArrayList();

            JSONArray quotes = res.optJSONObject("query").optJSONObject("results").optJSONArray("quote");
            for (int i = 0; i < quotes.length(); i++) {
                JSONObject row = quotes.optJSONObject(i);
                if (row != null) {
                    String symbol = row.optString("symbol", "");
                    float price = Float.parseFloat(row.optString("LastTradePriceOnly", "0"));
                    ids.add(helper.addNewQuote(symbol, price));
                }
            }

            caller.onQuotesResponseSuccess(ids);
        }

        @Override
        public void onFailure(Throwable e, JSONObject res) {
            if (caller == null)
                throw new ClassCastException("The calling activity must implement YQLFinance.QuotesResponseListener");

            caller.onQuotesResponseFailure(e);
        }
    };

}

YES, I also hate having to do the same code over and over like what I'm doing to always null-check the caller property. Could've done a static method to do this but verbosity wins this time. I want to be verbose more than not being redundant because it train my mind to always watch out for danger.

Use Native UI

A few days ago, Android Jellybean 4.3 was launched at Google I/O 2013. With every new Android release, Google updates their Android Support Library to include certain UI Components needing backward compatibilities. Of all the latest UI components I used, only the ActionBar component is still needing other alternative (hint: ActionBarSherlock).

Native UI components offer you consistent codes throughout all Android versions meaning that you can worry more about what your app do than what versions of Android support component X. This is fresh air for developers, thanks to Google for making lives easier.

Here are a few Native UI Components I used before with Android Support Library:

AsyncTask

For any task that your codes do that will block the UI in any way, delegate to AsyncTask. With every subclass, you can always override onPreExecute() and onPostExecute to meddle the UI a bit before and after your task is executed.

I have a few tutorials here for that matter:

Final Words

So to wrap things up, by adhering to these design patterns when developing on Android, I always get that speedy and snappy Android app illusion for users. Speed like in web development is an expensive illusion but you can pay it by doing some vertical scaling. It's hard to scale horizontally because Android devices hardware is on the mercy of manufacturers.

29 July 2013 by Batista Harahap
comments powered by Disqus