I have a fairly tricky situation that I’m trying to determine the best design for. The basics are this:
- I’m designing a messaging system with a similar interface to email.
- When a user clicks a message that has an attachment, an activity is spawned that shows the text of that message along with a paper clip signaling that there is an additional attachment.
- At this point, I begin preloading the attachment so that when the user clicks on it – it loads more quickly.
- currently, when the user clicks the attachment, it prompts with a loading dialog until the download is complete at which point it loads a separate attachment viewer activity, passing in the bmp byte array.
- I don’t ever want to save attachments to persistent storage.
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:
- User loads message as before, preloading begins of attachment as before (invisible to user).
- When the user clicks on the attachment link, the attachment viewer activity is spawned right away.
- If the download was done, the image is displayed. If not, a dialog is shown in THIS activity until it is done and can be displayed. Note that ideally the download never restarts or else I’ve wasted cycles on the preload.
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…
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.