Ok, I’ve been researching the best way to solve this problem for some time so let me break down my needs and findings for you.
Needs:
- An Activity [SendMsg] composes a message body which may include attachments of images up to 5MB.
- When the user sends this message, the message must be uploaded to my server
- This upload must happen synchronously in the sense that I block the user from using the app until it has completed or failed
- When complete, the SendMsg Activity is destroyed if the send was successful – otherwise an error dialog is displayed
- The upload and user experience must persist given any phone rotation or home button presses / phone calls etc
The last bullet here turns out to be the hardest.
findings:
I thought about just doing the work in a static AsyncTask but I don’t think that works for me – state gets really messy and it seems as though I have no guarantee the OS doesn’t kill the thread when the app is in the background.
I decided to go with a Service in which it is created and bound/unbound from the Activity. The service spawns an AsyncTask, does the work and then signals the Activity according to its result (SUCCEED/FAIL). This paradigm seems to work fine except for my large messages. The Service/Activity communication Message communication is not made for marshalling such large payloads. *NOTE: if it is not clear, when the user hits send, this form data and file must be made available to the Service. In some cases this is an image taken from the camera which DOES not and CAN not exist on disk for purposes of the app.
Clearly what I want here is some sort of shared memory store where I can save the data from the Activity. Then the Activity can signal the Service that it can go ahead and upload that data.
Can such a shared area exists in memory? Can I get the Application object from a Service and store the data there?
The other problem comes about when the upload finishes but no Activity is bound to the Service but I think I may have figured this one out. Please let me know if the following makes sense:
- Service finishes upload
- Service checks to see if an Activity is currently bound
- If yes, return error Message which Activity handles via its Message queue
- If no, store the return code in that shared memory area and flag a boolean there as well
- When a client Activity is created / resumed, it should first always check this boolean to see if the process is already complete. If so, dispatch a Message it its own handler and handle the code as before
To deal with your memory requirement, I would probably use a
Mapof UUID to a holder class that has all of your message details. Store thisMapas a member variable in yourApplicationsubclass, or as a static variable. Make sure to clear this out when your task completes or fails. If you are sure you won’t have multiple requests of this type going at the same time, maybe you can just use a single class instead of a Map or list.Then, you can accomplish your requirements using an IntentService, with help from a WakeLock. I tend to avoid bound Services in Android, because they are difficult to work with due to screen rotation. Just pass in the identifier from the Map in the Intent that starts the
IntentService.In my opinion, the best way to communicate from your Service back to your Activity is to use a
PendingIntent. Basically, you start the Service from your Activity, and pass in aPendingIntentin the Intent that starts the Service. When your Service has finished its work, or failed, you callsend()on the PendingIntent. Then, your Activity will get a callback in itsonActivityResult()method.Doing this has several advantages:
IntentService, and you get notified on the UI thread when it is finished.onActivityResult(). Conversely, even if youfinish()your Activity and start a new one before the PendingIntent is sent, it will not be delivered, which is almost always what you want.I have a demo application that shows this approach. Basically, you have an Activity which stars some work, and blocks the UI with a progress dialog. This dialog will be shown again if the screen gets rotated. The work is done in the Service, which sends the
PendingIntentback to the Activity.The last part of your job is to ensure that the device does not go to sleep while your upload is running. Mark Murphy has written a nice example and Service implementation that uses a
WakeLockto ensure work gets done uninterrupted.