headerphoto

Evolution of an Android Application: Day 4

Posted by Tim Freund Mon, 26 Jan 2009 05:07:00 GMT

This is a small detour from the planned discussion the different approaches available for making HTTP requests. I needed an easy topic since it has been so long since I last found time to work on the application.

Those who have seen Rhymote in action know that it is pretty ugly, and I want to change that. In addition to being ugly, the buttons are so small that I can easily hit the wrong one. Right now the application uses text on a standard Android button to crudely imitate the buttons of a media player. Using legitimate media player icons would make the app look much nicer. For those who haven't seen the app in its ugly form, take a look:

http://achievewith.us/images/evolution_of_an_android/day_01_initial_ui.jpg

Find or Design Icons

Android applications can use images as icons. The standard image format is PNG, and images can be placed in the res/drawable directory of your project.

Since I am a programmer and not a designer, I chose to look for appropriate open source icons. The Tango Icon Library is a great looking icon set, the icons are available in a scalable format, and the set is available under a Creative Commons license. Awesome.

I tried to use the SVG icons directly, but that didn't work. Instead, I used Inkscape to export the SVG icons as appropriately sized PNG files. For reference, I made the icons approximately 100 pixels tall.

Update The Layout

The button definitions in the layout file now look something like this:

<ImageButton android:id="@+id/previous"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/media_skip_backward"></ImageButton>

Unfortunately the buttons still had the default white background. When we finally get around to displaying album art, transparent button backgrounds will allow that album art to be seen. To achieve this, I made a small (10x10) transparent PNG file with no content. We can then set the android:background attribute of the ImageButton to achieve the desired effect.

<ImageButton android:id="@+id/previous"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/media_skip_backward"
    android:background="@drawable/transparent"></ImageButton>

For this small amount of work, we get an application that looks much better than before:

http://achievewith.us/images/evolution_of_an_android/day_04_new_buttons.jpg Bookmark and Share

Posted in ,

Evolution of an Android Application: Day 3

Posted by Tim Freund Wed, 07 Jan 2009 04:32:00 GMT

Where we last left off, Rhymote worked, but it was painfully slow. All of the network communication takes place in the UI thread, and that's a big problem. An application has 5 seconds to respond to a UI event, and if the click handler isn't done in that amount of time, the user will be asked if they'd like to terminate the application. The exact error message will look something like this:

Sorry!  Activity Rhymote
(in application Rhymote)
is not responding.

Even though Rhymote is only making one HTTP request per button press, that can put us over the 5 second limit. The HTTP requests return quickly in the emulator, but the phone is a totally different world.

A partial solution to the problem is threading. We want to start a new thread of execution separate from the UI to handle time intensive work. These time intensive tasks can then update the UI if necessary once they are done. If you are at all experienced with Java threading, you will already know how to create a Runnable object and pass that Runnable to a Thread. If you are new here, follow this cheat sheet:

package com.example.androidapp;

public class WebRequestRunnable implements Runnable {
    public void run(){
        // do resource intensive stuff here...
        // create an http request and read the results
    }
}

and

package com.example.androidapp;

public class Application {
    ...
    private OnClickListener mButtonListener = new OnClickListener() {
        public void onClick(View v) {
            WebRequestRunnable wrr = new WebRequestRunnable();
            Thread t = new Thread(wrr);
            t.start();
        }
    }
}

More information about this basic pattern can be found at the following Android resource pages:

Although the application no longer hangs when responding to a button press, there is still a latency problem. The HTTP request that was taking 5+ seconds to execute in the UI thread is now taking 5+ seconds to execute in a second thread. Two potential issues come to mind:

  • The Apache Commons HttpClient code is really slow
  • I'm doing something foolish in my code

I learned long ago to never blame another programmer or library without some very solid evidence. When in doubt, it is probably my own fault. Next time we will attempt to find the most efficient way to deal with the required network requests.

Bookmark and Share

Posted in ,

Evolution of an Android Application: Day 2

Posted by Tim Freund Fri, 19 Dec 2008 06:31:00 GMT

I'm still working on the Rhymote project.

I sat down with the intent of immediately putting the code on my phone, but it was just so ugly that I had to spend about 30 minutes cleaning things up. Let that be a lesson: late night programming can be fun, but it isn't always productive.

Configuring the G1 for Deployment

First I opened the Settings program and choose Applications.

http://achievewith.us/images/evolution_of_an_android/day_02_applications.jpg

I checked the box next to Unknown Sources

http://achievewith.us/images/evolution_of_an_android/day_02_unknown_sources.jpg

While I was still on the Applications screen of the Settings program, I selected Development and checked the box next to USB Debugging.

http://achievewith.us/images/evolution_of_an_android/day_02_usb_debugging.jpg

Configuring my Computer for Deployment

Since I am developing the software on Ubuntu, I also need to configure udev to recognize the phone as more than just a USB drive:

# echo "SUBSYSTEM=="usb", SYSFS{idVendor}=="0bb4", MODE="0666"" > /etc/udev/rules.d/50-android.rules

Developers who are using Windows or OS X do not need to worry about similar changes for their operating system.

Deploying the Application

Then I plugged the phone in and ran:

$ adb -d install ~/workspace/Rhymote/bin/Rhymote.apk

The application works on the real device just like in the emulator, except for one painful difference: it is terribly slow. Slow enough that the system asks if I would like to terminate the application while the OnClickListeners are firing. That's not so great.

Since this is kind of a hack-and-slash project, I just create a BasicHttpRequest object right in the listener, fire it off, and wait for the response. That worked great in the emulator, but I guess I need to make the HTTP request in the background when I am running on a real phone.

I could have learned all about that if took the time to read the docs up front, but that's OK. I'm happy to have something that works, even if it isn't working great, and I know what I can learn next.

Bookmark and Share

Posted in ,

Evolution of an Android Application: Day 1

Posted by Tim Freund Wed, 17 Dec 2008 03:04:00 GMT

Programmers are supposed to be lazy right? Here is the scene for one of my lazy moments: my home office contains a desk and a futon in opposite corners. I often want to change music on my desktop PC while reading on the futon. Most people would just get up to change the music without much thought, but I'd much rather use my G1 as a remote control for Rhythmbox. This will be my first Android project, and it shouldn't be too much to handle for a newbie like myself.

I started the Rhymote project yesterday, and I am writing these posts to keep track of all the hurdles I encounter along the way. Although the project could end up doing all sorts of neat stuff, all I really want to do is a handful of operations:

  • Play/Pause
  • Next track
  • Previous track
  • Volume adjustments

I have a tendency to over-engineer solutions, so I'm going to explicitly avoid anything that smacks of over ambitious design. Quick and dirty is the name of the game. The code will grow up if it holds my interest for long enough.

Here's the gear I am working with:

  • Ubuntu 8.10
  • Rhythmbox 0.11.6
  • Eclipse 3.4
  • Android SDK + Eclipse Plug-in
  • T-Mobile G1

All of the code that I write throughout the series can be found in this repository.

Prerequisite: Put Rhythmbox on the Network

Rhythmbox isn't on the network by default, and I needed to change that if I was going to have any luck with this project. In the spirit of quick and dirty, I wrote a WSGI application that wraps rhythmbox-client. This solution will evolve as the android application evolves, but it is tangential to the process of Android application development. If I do anything particularly interesting with Rhythmbox, I'll be sure to write it up separately.

Start an Android Project

I write Java in Eclipse most of the day, so I felt pretty comfortable starting the Android portion of the Rhymote project. I followed along with the Hello Android tutorial, and it wasn't long before the string "Hello Android" was staring back at me from the emulator.

Create a User Interface

The first thing I needed was a UI, and building fat client user interfaces is foreign territory for me. Oh, what's that? Android uses XML to define screen layouts? Maybe this won't be so bad after all. Remember, I did say I am a Java developer by day.

http://achievewith.us/images/evolution_of_an_android/day_01_initial_ui.jpg

Make no mistakes, my UI is ugly. Did I mention that I don't write much UI code at my day job?

Find the System Log

http://achievewith.us/images/evolution_of_an_android/day_01_exception.jpg

I spotted the commons-httpclient code in the API, and decided to code up a client in an OnClickListener for each of my buttons.

After deploying the code and watching the application crash, I was left scratching my head. Where can I see the logs? Fortunately the command adb logcat is the Android equivalent of tail -f, and I was able to pinpoint the problem with my HTTP client code.

Discover Permissions

The exception I found was a little strange:

E/AndroidRuntime( 6490): Caused by: java.net.SocketException: unknown error E/AndroidRuntime( 6490): at org.apache.harmony.luni.platform.OSNetworkSystem.createSocketImpl(Native Method) E/AndroidRuntime( 6490): at org.apache.harmony.luni.platform.OSNetworkSystem.createSocket(OSNetworkSystem.java:79) E/AndroidRuntime( 6490): at org.apache.harmony.luni.net.PlainSocketImpl2.create(PlainSocketImpl2.java:59) E/AndroidRuntime( 6490): at java.net.Socket.checkClosedAndCreate(Socket.java:763) E/AndroidRuntime( 6490): at java.net.Socket.connect(Socket.java:910) E/AndroidRuntime( 6490): at org.apache.http.conn.scheme.PlainSocketFactory.connectSocket(PlainSocketFactory.java:117) E/AndroidRuntime( 6490): at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:129) E/AndroidRuntime( 6490): at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) E/AndroidRuntime( 6490): at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) E/AndroidRuntime( 6490): at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348) E/AndroidRuntime( 6490): at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555) E/AndroidRuntime( 6490): at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:509) E/AndroidRuntime( 6490): at us.achievewith.Rhymote.Rhymote$1.onClick(Rhymote.java:60) E/AndroidRuntime( 6490): ... 23 more

I was expecting something along the lines of Connection Refused or Unknown Host, but unknown error was a little vague to be of much use. It was already late, and I foolishly struggled with the error for a while before letting Google Search work its magic, and that's when I learned about permissions.

Android applications don't just get free reign over the system without first asking for permissions to do most anything that interacts with the system at large or other systems. The following line cleared up the exception once it was added to the AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET"/>

One more run through the emulator, and I was able to start and stop my music. At this point it was 3:00 AM, and I decided that putting the code on the phone could wait.

Bookmark and Share

Posted in ,