Android introduced Loaders in 3.0 to ease asynchronous tasks on Activity and Fragments. It monitor the source of the data and automatically deliver new result when content changes. It's primary usage is to work with a database utilizing the CursorLoader, but it can also be used to do other asynchronous tasks. I want to take a look at how Loaders could be used as a replacement for AsyncTask.
The issue with AsyncTask is well known. If you rotate the device, you have to cancel the AsyncTask or the app will crash. Loaders handles rotation better, it automatically reconnect to the last loader’s cursor when being recreated after a configuration change avoiding the need to re-query their data.
There is an instance of LoaderManager in every Activity and Fragment. You simply call getLoaderManager(). Note that if you're using the support library, you will call getSupportLoaderManager() instead.
To do the actual task, create a class that extends the AsyncTaskLoader and implement the loadInBackground method. The return type will be whatever data you want to send back to the main app. In my example, the work is just to sleep for some time. And my return value is just how many milliseconds I slept.
To trigger the work, the first thing to do is to implement the LoaderCallbacks interface in your Activity or Fragment. You can then call initLoader in the Activity's onCreate method. In my test app, I'm actually going to start the task after clicking a button, so I call initLoader() and forceLoad(). The onCreateLoader method will then be triggered. Notice that it uses an integer ID to identify the Loader. I'm not sure, but my guess is that typically you only have one Loader to handle all the asynchronously tasks for an Activity or Fragment.
Finally, to get feedback on the UI, the onLoadFinished method will be called in the UI thread where you can show the result. In my example, I'm simply making a Toast message, but you could imagine where we can update a table with rows of values or show a text.
Note that while AsyncTask has onProgressUpdate(Integer progress) to show how much data has been fetched, a Loader doesn't have this capability out of the box.
I still have a lot to learn about Loaders after this short prototype. So far, it seems like a lot of work to avoid canceling and restarting tasks. If data fetch performance is a concern, which is always the case to a certain degree, then I would consider using AsyncTaskLoader instead of AsyncTask. But there are alternate solutions, such as RoboSpice, that maybe a better solution. My guess is that Loaders are much more effective when dealing with database, which I intent to try out soon.
My personal blog for sharing knowledge about Android, iOS, and Windows Phone development as I learn them.
Saturday, January 25, 2014
Tuesday, January 14, 2014
Java's CountDownLatch
In our Android phone TV application, we need a few server resources before meaningful data can be fetched. For example, we need to know the template for the playback URL so that when user select a channel, we can construct a full URL to play from the channel ID, the device dimension, etc. We also need to fetch another table that tells us which settings to use when the app is connected to a WiFi spot versus when it's on a cellular signal. We need these and a few other essential resources before the app can start.
All of these resources are asynchronous fetches from the server. And we don't want to navigate to the home page until all resources have been retrieved. A handy class to help track that we've got all those resources is the CountDownLatch. It blocks the calling thread until the count reaches zero. You create the latch, fire off your asynchronous tasks in separate threads. Then when each of the task is finished, you decrement the count by calling countDown().
To block the calling thread, you call the await() method. One mistake I made the first time was call wait() instead of await(), so make sure you call the right method. If you call wait, you'll get an IllegalMonitorException.
It's obvious, but still worth noting. You should not use this on the main thread. Instead I would use this to patch several tasks and make it act like a single operation, then notify the UI that all tasks are done via a handler or broadcast receiver.
To see how it all works, I wrote a simple function and run it to see how it looks:
01-07 10:50:53.758 7809-7809/com.mobitv.refapp I/System.out﹕ DEMO - execWorkerTasksBlocking <<
01-07 10:50:54.018 7809-8129/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task A
01-07 10:50:54.118 7809-8129/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task C
01-07 10:50:54.269 7809-8130/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task B
01-07 10:50:54.369 7809-8130/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task E
01-07 10:50:54.879 7809-8129/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task D
01-07 10:50:54.879 7809-7809/com.mobitv.refapp I/System.out﹕ DEMO - execWorkerTasksBlocking >>
All of these resources are asynchronous fetches from the server. And we don't want to navigate to the home page until all resources have been retrieved. A handy class to help track that we've got all those resources is the CountDownLatch. It blocks the calling thread until the count reaches zero. You create the latch, fire off your asynchronous tasks in separate threads. Then when each of the task is finished, you decrement the count by calling countDown().
To block the calling thread, you call the await() method. One mistake I made the first time was call wait() instead of await(), so make sure you call the right method. If you call wait, you'll get an IllegalMonitorException.
Warning: NEVER block the UI/main thread!
It's obvious, but still worth noting. You should not use this on the main thread. Instead I would use this to patch several tasks and make it act like a single operation, then notify the UI that all tasks are done via a handler or broadcast receiver.
To see how it all works, I wrote a simple function and run it to see how it looks:
Sample code
Sample output
01-07 10:50:53.758 7809-7809/com.mobitv.refapp I/System.out﹕ DEMO - execWorkerTasksBlocking <<
01-07 10:50:54.018 7809-8129/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task A
01-07 10:50:54.118 7809-8129/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task C
01-07 10:50:54.269 7809-8130/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task B
01-07 10:50:54.369 7809-8130/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task E
01-07 10:50:54.879 7809-8129/com.mobitv.refapp I/System.out﹕ DEMO - Completed: task D
01-07 10:50:54.879 7809-7809/com.mobitv.refapp I/System.out﹕ DEMO - execWorkerTasksBlocking >>
Wednesday, January 8, 2014
Quick & Dirty Tips: JavaDoc gradle task
I was trying to publish a JavaDoc for my Android Library. Being an absolute novice in Gradle, I searched the net and found a piece of gem that just worked. The Magic is from line 31 to line 46. It creates 2 Gradle tasks for me:
- generatedebugjavadoc &
- generatereleasejavadoc
WebRep
currentVote
noRating
noWeight
And then the JavaDoc files can be found in <ProjectDir>/build/docs
Sunday, January 5, 2014
Android's IntentService
When I ask my colleagues about IntentService as a way to execute asynchronous tasks, most seems to be unaware of it existence. Perhaps it is because it doesn't interact with the UI/main thread like AsyncTask does, so it's use is limited to background processes that doesn't require UI feedback, and so there are less scenarios where it is appropriate. IntentService is just one of many ways Android provide asynchronous operations. Others include
There are only a few things you have to do:
In order to handle configuration change properly, when the device is rotated for example, the broadcast receiver should be bind and unbinded along with the lifecycle of the Activity or Fragment
Note that the requests are handled serially such that if startService was called by mistake more than once with the same Intent, the work will be done twice, so it's important to either prevent that from happening in the calling side or write the IntentService such that it recognize a duplicate request. This can be accomplished by a runtime cache or input and output. So if the service receive the Intent and it already cached the result, then just broadcast the result again. Finally, if you call stopService all the pending tasks will be lost, which maybe what you want.
12-13 15:44:11.300 19913-19913/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:44:11.370 19913-19913/com.mobitv.demo I/System.out﹕ MyBackgroundService.onCreate
12-13 15:44:11.380 19913-20063/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:44:13.242 19913-20063/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:44:13.252 19913-19913/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:44:13.272 19913-19913/com.mobitv.demo I/System.out﹕ MyBackgroundService.onDestory
12-13 15:46:28.568 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:28.628 20388-20388/com.mobitv.demo I/System.out﹕ MyBackgroundService.onCreate
12-13 15:46:28.638 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:28.688 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:28.838 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:28.989 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:29.129 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:30.520 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:30.530 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:30.550 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:32.422 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:32.422 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:32.442 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:34.324 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:34.324 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:34.354 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:36.246 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:36.256 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:36.286 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:38.168 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:38.168 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:38.248 20388-20388/com.mobitv.demo I/System.out﹕ MyBackgroundService.onDestory
- runOnUIThread
- Executor
- Handler
- AsyncTask
- Service
- Loader
Service vs IntentService
The first question that most developer ask is what's the difference between a normal Android Service and an IntentService. Most folks understand that a Service is an application component that can perform long running tasks in the background without UI feedback. If you read the Class Overview of IntentService, you would read that
"IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand."The first time I read this quote, I interpreted to mean that Service extends IntentService. But the opposite is true.
public abstract class IntentService extends ServiceIntentService does a little bit more than what Service does for you.
- While a regular Service runs in the caller's thread, which in most cases is the main thread, IntentService always run on a background thread.
- IntentService uses a message queue to process requests, so only one intent is handled at any time.
- if you need parallel tasks such as downloading multiple images at the same time, IntentService may not be a good choice.
- If one of the tasks is stuck, then all the pending tasks in the queue will not execute.
- Calling stopService is optional; The IntentService will stop itself once there's no more work on the queue.
- A broadcast receiver is needed if you want to provide UI feedback.
Implementing an IntentSevice
- Declare a few constants for communication between application and IntentService.
- Create a class that extends IntentService; Implements onHandleIntent method.
- Create a BroadcastReciever; Implement onReceive method.
- Trigger the IntentService by calling startService, with input arguments wrapped in an Intent.
Example:
Handling device rotation
Sample output
Calling startService just once
12-13 15:44:11.370 19913-19913/com.mobitv.demo I/System.out﹕ MyBackgroundService.onCreate
12-13 15:44:11.380 19913-20063/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:44:13.242 19913-20063/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:44:13.252 19913-19913/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:44:13.272 19913-19913/com.mobitv.demo I/System.out﹕ MyBackgroundService.onDestory
Calling startService many times in rapid succession.
12-13 15:46:28.628 20388-20388/com.mobitv.demo I/System.out﹕ MyBackgroundService.onCreate
12-13 15:46:28.638 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:28.688 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:28.838 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:28.989 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:29.129 20388-20388/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:46:30.520 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:30.530 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:30.550 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:32.422 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:32.422 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:32.442 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:34.324 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:34.324 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:34.354 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:36.246 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:36.256 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:36.286 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:46:38.168 20388-20494/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:46:38.168 20388-20388/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:46:38.248 20388-20388/com.mobitv.demo I/System.out﹕ MyBackgroundService.onDestory
Calling startService many times in rapid succession, then calling stopService once.
12-13 15:47:19.849 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:19.909 20807-20807/com.mobitv.demo I/System.out﹕ MyBackgroundService.onCreate
12-13 15:47:19.919 20807-20952/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:47:20.009 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:20.209 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:20.409 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:20.590 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:20.940 20807-20807/com.mobitv.demo I/System.out﹕ MyBackgroundService.onDestory
12-13 15:47:21.791 20807-20952/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:47:21.801 20807-20807/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
12-13 15:47:19.909 20807-20807/com.mobitv.demo I/System.out﹕ MyBackgroundService.onCreate
12-13 15:47:19.919 20807-20952/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Find price for GOOG
12-13 15:47:20.009 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:20.209 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:20.409 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:20.590 20807-20807/com.mobitv.demo I/System.out﹕ Button.setOnClickListener calls startService
12-13 15:47:20.940 20807-20807/com.mobitv.demo I/System.out﹕ MyBackgroundService.onDestory
12-13 15:47:21.791 20807-20952/com.mobitv.demo I/System.out﹕ MyBackgroundService.onHandleIntent; Found price: $1200.45
12-13 15:47:21.801 20807-20807/com.mobitv.demo I/System.out﹕ MyBkgSvcResponseReciever.onRecieve
Example Use in projects
To see how others have used IntentService, I went on Github and did a search and found a few examples of open source project that uses IntentService as a background task.
- RingerMuteService uses IntentService to change the volume level of the device
- VBox's EventIntentService polls VirtualBox for events and publish them in a local broadcaster
- WidgetCreatorService uses IntentService to create app shortcut on the device home page
- WotdWidget updates data in an Android widget.
- UploadToJBossServerService upload file to a JBoss Server.
WebRep
currentVote
noRating
noWeight
Subscribe to:
Posts (Atom)