Reputation: 1557
I have a fairly tricky situation that I'm trying to determine the best design for. The basics are this:
The difficulty I have is in supporting rotation as well as home button presses etc. The download is currently done with a thread and handler setup.
Instead of this, I'd like the flow to be the following:
Obviously I need some persistent background process that is able to keep downloading and is able to call back to arbitrarily bonded Activities. It seems like the IntentService almost fits my needs as it does its work in a background thread and has the Service (non UI) lifecycle. However, will it work for my other needs?
I notice that common implementations for what I want to do get a Messenger from the caller Activity so that a Message object can be sent back to a Handler in the caller's thread. This is all well and good but what happens in my case when the caller Activity is Stopped or Destroyed and the currently active Activity (the attachment viewer) is showing? Is there some way to dynamically bind a new Activity to a running IntentService so that I can send a Message back to the new Activity?
The other question is on the Message object. Can I send arbitrarily large data back in this package? For instance, rather than send back that "The file was downloaded", I need to send back the byte array of the downloaded file itself since I never want to write it to disk (and yes this needs to be the case).
Any advice on achieving the behavior I want is greatly appreciated. I've not been working with Android for that long and I often get confused with how to best handle asynchronous processes over the course of the Activity lifecycle especially when it comes to orientation changes and home button presses...
Upvotes: 2
Views: 1769
Reputation: 16914
There're a number of ways to solve this. You're right, I think that you obviously need a Service for this scenario to detach the download process from the Activities that may come and go. For this to work, you need to implement some download-queue in this Service, and IntentService gives this behaviour out of the box, so you can go with that. The only drawback that comes to mind with IntentService is that it's not that easy to cancel a job that you previously posted for it.
I wouldn't serialize the downloaded data however, it doesn't sound too efficient, especially not if you're dealing with large amounts of binary data. You could write the downloaded data into an in-memory LRU cache and assign an ID for it by which the Viewer Activity can get a hold of this downloaded data.
You could also use broadcast Intents to signal the Viewer Activity from the Service, that a download with a particular ID is finished. This would ensure loose coupling between the viewing Activity and the downloading Service.
So it would go something like this:
Edit:
As for the cache, you have to make some choices. I think that this is quite a broad topic on its own, so I'll just write some ideas here based on my previous projects.
Above, I only mentioned an in-memory LRU cache. This would live in a custom Application object, or as a Singleton. Which is using the evil global state, but I think it's reasonable in a situation like this. The advantage is that you don't need to serialize the downloaded data anywhere: the download finished, you have the content in memory and you can access it immediately from the Viewer Activity. The drawback is, that this data will be available only as long as it's not removed from this cache OR the system doesn't kill your application's process because of low memory conditions.
To avoid re-downloading data in case you lost the contents of this volatile in-memory cache, you need to add a persistent layer for it. E.g. writing the downloaded data to the application's cache directory. This brings the overhead of writing the downloaded data to disk, but at least you'll be able to access it later, even if the system kills you process. You may combine this persistent cache with the in-memory cache using a 2-level cache approach, or you may choose either one. I recommend to check out some widely used image downloader libraries that implement this behaviour for inspiration, e.g. Universal Image Loader.
Upvotes: 2
Reputation: 8641
You use an IntentService to go off, do work, and then post it in some persistent storage. To communicate with an Activity, you can use a broadcast receiver in the Activity and send broadcast Intents from the IntentService.
Rotation and home button presses have no effect on an IntentService, as long as it is still doing its work, but a rotation will do a reDraw, and the home button will pause the Activity. For those reasons, you should avoid doing asynchronous work in an AsyncTask unless it doesn't have to complete and doesn't have to persist.
Messages are for Handlers, not for components. The basic affordance for transmitting information through the system is Intents and broadcast Intents.
An IntentService simply handles the work of turning an Intent into a Message, creating a new HandlerThread, putting the Message into the HandlerThread's MessageQueue, and firing off the Looper. The run() that gets executed on the background thread is whatever's in onHandleIntent() (this is a bit of a simplification).
Upvotes: 2