Friday, October 29, 2010

Android: implementing a notification service the right way

One of the key features of Android is the ability for apps to provide notifications that pop up in the status bar. I'll try to explain in this post how you should implement a Service component that polls a web service regularly to check for updates.

The first thing you need is a preferences activity, where the user can set what notifications they want and how often they want your app to check for them. This is easily achieved using a PreferenceActivity, and is not the subject of this post. If you want to learn more on how to create such an activity, click on the aforementioned link.

Once you have your preferences activity, it's time to write your Service. Here are a few key points that you should always respect. I will go into more detail on each one shortly.

  • Respect the global background data setting (I don't think a lot of apps do this).
  • Do your work in a separate thread. It can happen that your Service will run on the UI thread as one of your activities.
  • Obtain a partial wake lock when starting, and release it when you're done. If you don't do this, the system might (and will) kill your service while your background thread is running.
  • Call stopSelf() when you're done, in order to free resources.
  • Tell the system to not start your Service again if it's killed for more resources.

To explain all of these, I'll provide a skeleton Service that does everything mentioned above. All you have to do is fill in the work specific to your project:

public class NotificationService extends Service { private WakeLock mWakeLock; /** * Simply return null, since our Service will not be communicating with * any other components. It just does its work silently. */ @Override public IBinder onBind(Intent intent) { return null; } /** * This is where we initialize. We call this when onStart/onStartCommand is * called by the system. We won't do anything with the intent here, and you * probably won't, either. */ private void handleIntent(Intent intent) { // obtain the wake lock PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Const.TAG); mWakeLock.acquire(); // check the global background data setting ConnectivityManager cm = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); if (!cm.getBackgroundDataSetting()) { stopSelf(); return; } // do the actual work, in a separate thread new PollTask().execute(); } private class PollTask extends AsyncTask<Void, Void, Void> { /** * This is where YOU do YOUR work. There's nothing for me to write here * you have to fill this in. Make your HTTP request(s) or whatever it is * you have to do to get your updates in here, because this is run in a * separate thread */ @Override protected Void doInBackground(Void... params) { // do stuff! return null; } /** * In here you should interpret whatever you fetched in doInBackground * and push any notifications you need to the status bar, using the * NotificationManager. I will not cover this here, go check the docs on * NotificationManager. * * What you HAVE to do is call stopSelf() after you've pushed your * notification(s). This will: * 1) Kill the service so it doesn't waste precious resources * 2) Call onDestroy() which will release the wake lock, so the device * can go to sleep again and save precious battery. */ @Override protected void onPostExecute(Void result) { // handle your data stopSelf(); } } /** * This is deprecated, but you have to implement it if you're planning on * supporting devices with an API level lower than 5 (Android 2.0). */ @Override public void onStart(Intent intent, int startId) { handleIntent(intent); } /** * This is called on 2.0+ (API level 5 or higher). Returning * START_NOT_STICKY tells the system to not restart the service if it is * killed because of poor resource (memory/cpu) conditions. */ @Override public int onStartCommand(Intent intent, int flags, int startId) { handleIntent(intent); return START_NOT_STICKY; } /** * In onDestroy() we release our wake lock. This ensures that whenever the * Service stops (killed for resources, stopSelf() called, etc.), the wake * lock will be released. */ public void onDestroy() { super.onDestroy(); mWakeLock.release(); } }

You now have a Service that works as it should, awesome. But how do you start it? You use the AlarmManager, of course (I suggest you read it's documentation if you haven't already). But how and when do you schedule it? How do you handle app updates? How do you handle device reboots? Well, here are some guidelines that I hope will make answering these questions much easier:

  • Use repeating alarms so that you don't have to always re-schedule yourself in your Service.
  • Use inexact repeating alarms so that the system can group together multiple alarms in order to preserve battery life.
  • Always cancel an alarm before resetting it.
  • When receiving the BOOT_COMPLETED signal, don't immediately start your Service. This would slow down the device too much (it already has a lot of work to do when it boots). Instead, set an alarm.

What I personally do is set an alarm whenever onResume() is called on my main Activity (the one that is launched when the user clicks on the app icon in their launcher), and in a BroadcastReceiver that handles the BOOT_COMPLETED event. Here's some code to help you understand:

MainActivity.java

public void onResume() { super.onResume(); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); int minutes = prefs.getInt("interval"); AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE); Intent i = new Intent(this, NotificationService.class); PendingIntent pi = PendingIntent.getService(this, 0, i, 0); am.cancel(pi); // by my own convention, minutes <= 0 means notifications are disabled if (minutes > 0) { am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + minutes*60*1000, minutes*60*1000, pi); } }

AndroidManifest.xml

<receiver android:name=".BootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> </application> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

BootReceiver.java

public void onReceive(Context context, Intent intent) { // in our case intent will always be BOOT_COMPLETED, so we can just set // the alarm // Note that a BroadcastReceiver is *NOT* a Context. Thus, we can't use // "this" whenever we need to pass a reference to the current context. // Thankfully, Android will supply a valid Context as the first parameter SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); int minutes = prefs.getInt("interval"); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent i = new Intent(context, NotificationService.class); PendingIntent pi = PendingIntent.getService(context, 0, i, 0); am.cancel(pi); // by my own convention, minutes <= 0 means notifications are disabled if (minutes > 0) { am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + minutes*60*1000, minutes*60*1000, pi); } }

Finally, you've got yourself a fully functional notifications service that stops and starts whenever it should, that behaves the way the user wants it to behave and that plays nicely with the system. This took some researching, so if I saved a bit of your time, leave a comment :)

Friday, August 13, 2010

Save image in instance state (Android)

While working on an app, I had an activity that downloaded an image from the Internet and displayed it to the user (amongst other things). Because all of the data presented to the user is downloaded from the Internet, it's mandatory for me to save this data in onSaveInstanceState() and restore it in onCreate().

At first, I had a problem. I was downloading the image to a Drawable, using Drawable.createFromStream(). Now, because a Drawable is dependent on its Context, it can't be serialized or parceled.

The solution was to use a Bitmap instead, which thankfully implements Parcelable. Downloading a Bitmap is easy:

URL url = new URL("http://www.example.com/pic.jpg"); Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());

Then saving it in the instance state is even easier:

public void onSaveInstanceState(Bundle outState) { outState.putParcelable("bitmap", bitmap); }

Restoring:

if (savedInstanceState != null) bitmap = (Bitmap) savedInstanceState.getParcelable("bitmap");

And there you have it, simple as pie.

Friday, July 30, 2010

Android Title Marquee

I recently wanted the title of my activity to scroll like a marquee whenever it was too long to fit in its space (because it is fetched from the net and can be pretty much anything). I didn't want to use a custom multi-line title view because that would waste screen space and because I want to preserve the native look and feel. The solution I've found is rather hackish, but it works. Here goes the code, with explanatory comments and all:

// make the title scroll! // find the title TextView TextView title = (TextView) findViewById(android.R.id.title); // set the ellipsize mode to MARQUEE and make it scroll only once title.setEllipsize(TruncateAt.MARQUEE); title.setMarqueeRepeatLimit(1); // in order to start strolling, it has to be focusable and focused title.setFocusable(true); title.setFocusableInTouchMode(true); title.requestFocus();

Saturday, June 5, 2010

Java vs. Python: fetching URLs

Here we go, another Java vs. Python comparison (I just can't help myself). This time it's about standard library usefulness in doing certain tasks. Fetching the contents of a URL should be a trivial one, but in Java, it's not. Especially if the contents of that URL are gzipped and use a nice charset such as UTF-8.

Java:

URL url = new URL("http://www.example.com/"); URLConnection conn = url.openConnection(); conn.connect(); InputStream in; if (conn.getContentEncoding().equals("gzip")) { in = new GZIPInputStream(conn.getInputStream()); } else { in = conn.getInputStream(); } String charset = conn.getContentType(); BufferedReader reader; if (charset.indexOf("charset=") != -1) { charset = charset.substring(charset.indexOf("charset=") + 8); reader = new BufferedReader(new InputStreamReader(in, charset)); } else { charset = null; reader = new BufferedReader(new InputStreamReader(in)); } StringBuilder builder = new StringBuilder(); String line = reader.readLine(); while (line != null) { builder.append(line + '\n'); line = reader.readLine(); } String content = builder.toString(); // FINALLY!

Python:

content = urllib2.urlopen("http://www.example.com/").read()

At first I though yeah, well, Java is probably older and wasn't designed to do such things very often. I was wrong. Java appeared in 1995, Python in 1991.

Tuesday, June 1, 2010

Java "Hello, World!" 6x slower than Python

Yup. I know, micro-benchmarks, but I find this one quite interesting. Take two small programs, one Java, one Python:

package com.test; public class TestJava { public static void main(String[] args) throws Exception { System.out.println("Hello, World!"); } } print "Hello, World!"

Running these two through time clearly shows that Java is 6 (six) times slower:

[felix@the-machine bin]$ time java com.test.TestJava Hello, World! real 0m0.142s user 0m0.080s sys 0m0.017s [felix@the-machine bin]$ time java com.test.TestJava Hello, World! real 0m0.142s user 0m0.077s sys 0m0.020s [felix@the-machine bin]$ time java com.test.TestJava Hello, World! real 0m0.154s user 0m0.070s sys 0m0.023s [felix@the-machine python]$ time python test.py Hello, World! real 0m0.024s user 0m0.013s sys 0m0.007s [felix@the-machine python]$ time python test.py Hello, World! real 0m0.025s user 0m0.020s sys 0m0.003s [felix@the-machine python]$ time python test.py Hello, World! real 0m0.026s user 0m0.013s sys 0m0.007s

Not only is Java slower, it's also a lot more code. And I'm not only talking about lines of code here (which, still, are 6 times more; really it's 1 line of Python and 6 of Java -- if you remove the whitespace), but compiled code, too. Look:

[felix@the-machine bin]$ ls -lh com/test/TestJava.class -rw-r--r-- 1 felix felix 595 Jun 1 21:50 com/test/TestJava.class [felix@the-machine python]$ ls -lh test.pyc -rw-r--r-- 1 felix felix 117 Jun 1 21:59 test.pyc

The compiled Java code is 595 bytes, while the compiled Python code is 117 bytes. Five times bigger. Say it with me: Java == BLOAT !

Monday, May 24, 2010

Server 'kitty' shut down

I used to have a home server which I playfully named kitty, mainly because it was not very powerful, yet it was very fun to play with. Today, I shut it down to replace it with a newer machine. These were its last words:

Broadcast message from felix@kitty (/dev/pts/0) at 12:00 ... The system is going down for power off NOW! Meow? :( felix@kitty:~$ Connection to kitty closed by remote host.

:(

Thursday, April 22, 2010

Android YouTube Intent

For a long time I've wondered how to show a YouTube video to the user in an Android application. There's this awesome post published by KeyesLabs on how to create your own Activity that plays YouTube videos. It's great, and you should definitely use it. But I think you can improve on that. It would be very useful for the user to view that video in the default YouTube player installed on the device because this way they can save it (like it, rate it, save it to their profile) plus enjoy other improvements and features the official YouTube app provides (plus probably better error checking for unavailable videos and so on).

While I was playing around with the emulator, I noticed that if you try to view a YouTube video in it th browser gives an error similar to Cannot open the page at vnd.youtube:VIDEO_ID?some=other¶meters=here. This way, I learned that a VIEW intent with a data URI like vnd.youtube:VIDEO_ID will launch the official YouTube app (this was confirmed by some nice folks on IRC, as I don't have an Android device). Basically:

Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("vnd.youtube:VIDEO_ID")); startActivity(i);

Will launch the YouTube app and watch the video with ID VIDEO_ID. Couple this with the Activity on KeyesLabs' blog and the Can I use this Intent? article and you've got a winner. My final solution is:

private void startVideo(String videoID) { // default youtube app Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("vnd.youtube:" + videoID)); List<ResolveInfo> list = getPackageManager().queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY); if (list.size() == 0) { // default youtube app not present or doesn't conform to the standard we know // use our own activity i = new Intent(getApplicationContext(), YouTube.class); i.putExtra("VIDEO_ID", videoID); } startActivity(i); }

Wednesday, April 14, 2010

How-to: Android Favorite Button (the right way, this time)

My last blog post is about how to make a favorite button in an Android application. It is wrong, those drawables should not be used independently. Instead, the @android:drawable/btn_star resource should be used, as it is a state-drawable and contains all the possible states (checked, unchecked, checked and focused, checked and clicked, ...). The proper way to use this is:

<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:button="@android:drawable/btn_star"/>

The result looks exactly like the button used in Android's Contacts application when viewing a contact (upper-right corner).

Man, that took some digging.

Monday, April 12, 2010

Android Favorite Button

I love Android. What I hate about it, though, is that they don't tell you how to make standard-looking UIs. For example, I had to dig through source codes to find the @android:style/ButtonBar style that you can use as the style on pretty much any layout (RelativeLayout, LinearLayout, etc.) which provides a nice background graphic for your button bars. Anyway, today I discovered another such hidden feature – a star drawable that can be used for favorite buttons. However, I still don't understand a thing: there's btn_star, btn_star_big_off and btn_star_big_on. One would think "how do I get btn_star_on then?", but then one would realize btn_star and btn_star_big_* are the same size (all of these are in @android:drawable/). In short, to create a favorite button/image, use: <ImageView android:src="@android:drawable/btn_star_big_off"/> <!-- or, if you want the button-like background and borders (although I prefer the ImageView version) --> <ImageButton android:src="@android:drawable/btn_star_big_on"/>

Friday, April 2, 2010

How to properly write JavaScript libraries

I've seen JavaScript code written in all the possible ways. The reason there are so many ways to do it is because JavaScript is incredibly flexible. But that flexibility makes people write really ugly code. Examples of ugly, unreadable, error-prone code:

(function(window, undefined) { var myLibrary = (function () { var someProp = "..."; function someFunc() { ... var myLibrary = (function () { var someProp = "..."; function someFunc() { ...

If you write JavaScript like that, stop. Here's the proper way to write JavaScript libraries:

function Example() { if (!(this instanceof Example)) { // in case whoever used this forgot to use the `new' keyword return new Example(); } Example.staticMethod(); this.instanceMethod(); // for any timeouts/events, do this: var instance = this; setTimeout(function() { instance.instanceMethod(); }, 200); } Example.staticMethod = function() { // do something here; } Example.prototype.instanceMethod = function() { // do something here } // instantiate the Example class: var example = new Example();

Isn't this much more beautiful? The reason for the instance variable needed in timeouts and events is that, in JavaScript, when handlers are called, they are called in the window context. That means that inside the handler, the keyword this refers to the global object window. By using the instance variable we are working around this, calling the handler with the correct context.

Inspired by this stackoverflow question.

Tuesday, March 16, 2010

Rhythmbox "Rock" EQ Preset

I recently helped a friend beta test a Winamp plugin, so I had to spend time in Windows. While listening to my music in Winamp, I chose the Rock preset in the equalizer. And my, I felt enlightened. The sound was at least ten times better than the flat sound Rhythmbox produces on my usual Ubuntu system. My music had been revived. When I went back to Ubuntu, I hated the sound. So I spent some time trying to get it sound good, as it did with Winamp. To cut to the chase, use the RBEQ plugin and set your equalizer like this:

Monday, March 15, 2010

How to properly use @font-face

I've spent some time playing around with some of the new features in CSS3. Thankfully, these are pretty well implemented in modern browsers (read: not in IE). One feature that I really like is the @font-face rule. Finally, no more cross-platform rendering problems, with buttons that appear wider / narrower than they should and so on. However, I couldn't find anywhere how to specify different variants of the same font family. What I mean by that is that, for example, for the font "Liberation Sans" I have 4 variants (each in its separate file): Regular, Bold, Italic and BoldItalic. How can I specify all these under the same font family? Because if I don't, and use just one @font-face rule with just the regular variant, the browser won't know what to use for, for example, <strong&gr; tags, because it has no default "bold" font defined. The solution I've found is to define multiple @font-face rules, under the same font-family. Example: @font-face { font-family: "MyCustomFont"; src: url("fonts/LiberationSans-Regular.ttf"); } @font-face { font-family: "MyCustomFont"; src: url("fonts/LiberationSans-Bold.ttf"); font-weight: bold; } @font-face { font-family: "MyCustomFont"; src: url("fonts/LiberationSans-Italic.ttf"); font-style: italic; } @font-face { font-family: "MyCustomFont"; src: url("fonts/LiberationSans-BoldItalic.ttf"); font-weight: bold; font-style: italic; } body { font-family: "MyCustomFont", sans-serif; } div.really_important { font-weight: bold; /* this works! */ }
Syntax Highlighting by Pygmentool

This way, you are defining multiple variants for the font family MyCustomFont. Finally, I understand why they call it font-family.

Saturday, February 20, 2010

Another Internet Explorer annoyance that you might not be aware of

Recently, I've been using display: inline-block in CSS a lot. It's a very useful feature -- it allows you to have a fix-sized (e.g. 100x20px, or just fixed width / height) element that is inline, incredibly useful when doing precision design work. However! Internet Explorer 7 cannot handle display: inline-block on an element that is natively a block element. So, for instance, if you try to use inline-block on a <div>, it will work fine in any browser except IE7 which will consider it display: block. Now I'm stuck with using <span> everywhere. Ugh.

Thursday, February 18, 2010

Sleeping with Linux is dangerous

Seriously, using the sleep function with Linux is dangerous. I just put my laptop on sleep for a while, woke it up and apparently everything was fine. But only apparently, because the system fan (you know, the one that keeps your laptop from melting) lost its "medium" speed. That means that it wouldn't run unless it had to go on max. So, after half an hour of peace and quiet, the fan started running like hell, and the laptop was really heated up (it was like being near a radiator). Reboot the system and the "medium" speed works again. What if the max speed wouldn't work either? I wouldn't have noticed it and might have ended up with a brick. Sleeping with Linux is dangerous!!