An integral part of the app is saving and deleting a note. This can’t be done on the UI thread because the databse update blocks the calling thread. Blocking the UI thread is a great way to cause an“Android Not Responding” issue.
My options (in no particular order) are:
- AsyncTask
- POJO Thread
- Service
- IntentService
Option 1: AsyncTask
AsyncTasks are an interesting item. They are a generic class that lets you update the UI at the same time as running things on a background thread. The thread used depends on the method.
Methods onPreExecute, onPostExecute, and on onProgressUpdate all run on the UI thread. doInBackground runs on a background thread.
1:
2: private class MyTask extends AsyncTask<String, Float, Boolean> {
3: @Override
4: protected void onPreExecute() {
5: super.onPreExecute();
6: //Runs on UI thread prior to doInBackground.
7: }
8:
9:
10: @Override
11: protected void onPostExecute(final Boolean aBoolean) {
12: super.onPostExecute(aBoolean);
13: //Runs on UI thread after return from doInBackground.
14: }
15:
16:
17: @Override
18: protected void onProgressUpdate(final Float... values) {
19: super.onProgressUpdate(values);
20: //Runs on UI thread as a result of publishProgress calls in doInBackground.
21: }
22:
23:
24: @Override
25: protected Boolean doInBackground(final String... params) {
26: //Runs on background thread from the thread pool.
27: for (int i=0; i<100; ++i) {
28: try {
29: publishProgress((float) i);
30: Thread.sleep(1000);
31: }
32: catch (InterruptedException e) {}
33: }
34:
35: return true;
36: }
37: }
Pros
- It lets you explicit control over when to update the UI.
- Easily cancelled
- Doesn’t require starting data to be Parcellable.
- Does not require a Context to start.
Cons
- By default, Android gives you one background thread in the async thread pool. That means only one AsyncTask can run in your app at a time unless you use a thread pool.
1: new MyTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, "testing...");
- You have to watch the lifecycle when using in fragments because the asynchronous UI update may occur after the fragment UI is destroyed.
- No way to re-attach the async if the activity/fragment recreate.
Option 2: Plain-Old-Java Thread
1:
2: Thread myThread = new Thread(new Runnable() {
3: @Override
4: public void run() {
5: for (int i=0; i<100; ++i) {
6: try {
7: Thread.sleep(1000);
8: }
9: catch (InterruptedException e) {}
10: }
11: }
12: });
13: myThread.start();
Pros
- Easy to implement and well understood
- Doesn’t require starting data to be Parcellable.
- Does not require a Context to start.
Cons
- No great way to update the UI.
- No great way to cancel
- You have to watch the lifecycle when using in fragments because the asynchronous UI update may occur after the fragment UI is destroyed.No way to re-attach the async if the activity/fragment recreate.
Option 3: Service
Services are an application level component that can run long running behaviors. These are one of the most powerful android features. Sorry, I didn’t include any setup for these.
Pros
- Can run for a very long time
- Configurable to automatically restart
- The best option is you need to process items in a prioritized queue on a background thread.
- Can be bound to the activity/fragment for UI updates
- Can run in its own process
- Activity/Fragment can bind to a running service without restarting it.
Cons
- Must be defined in the manifest.
- When not bound, all interactions are indirect and all data must be parcelable.
- Runs on the UI thread by default, so you will need to use either option 1 or 2 for the background behavior.
Option 4: IntentService
IntentServices are Services that are very easy to use and setup.
Pros
- Best for relatively short tasks
- Automatically queues the calls. Only one instance of your IntentService runs at a time. When that one finishes, the next call automatically starts.
- Automatically runs on a background thread.
- Much simpler to setup than a normal Service
- Activity/Fragment can bind to a running service without restarting it.
Cons
- Must be defined in the manifest.
- When not bound, all interactions are indirect and all data must be parcelable.
- No way to terminate a queued call.
And the winner is…
IntentServices. It is the best option for the note save and delete Here is one more list for my rationale:
- It is fast enough and unlikely to fail so I don’t need to track progress on the UI.
- I don’t need to prioritize the note deletions or saves, but I also don’t want to lose any requests either. The IntentService handles all of that for me.
- I don’t need to cancel or kill a note save or delete request.
- The starting data is Parcelable.
All of those points align the best with IntentService. Honestly, I prefer using IntentServices to AsyncTasks whenever possible. A lot of the lifecycle issues just disappear with them. Below is my implementation of the SaveNoteService.
1:
2: /**
3: * A service that saves a note to persistent storage.
4: */
5: public class SaveNoteService extends IntentService {
6:
7: /**
8: * Start the service.
9: *
10: * @param context The context to use for starting the service.
11: * @param note The note to save.
12: */
13: public static void startService(@NonNull Context context, @NonNull Note note) {
14: Preconditions.checkNotNull(context);
15: Preconditions.checkNotNull(note);
16:
17: Intent intent = new Intent(context, SaveNoteService.class);
18: intent.putExtra(NoteExtraKeys.NOTE_KEY, note);
19: context.startService(intent);
20: }
21:
22:
23: /**
24: * Default Constructor.
25: */
26: public SaveNoteService() {
27: super(SaveNoteService.class.getName());
28: }
29:
30:
31: @Override
32: protected void onHandleIntent(final Intent intent) {
33: Note note = intent.getParcelableExtra(NoteExtraKeys.NOTE_KEY);
34: note.save(DatabaseHelper.getDatabase());
35: }
36: }
I always include a static method that starts the service to make them more maintable. Since the service data is based by a bundle, it is easy to miss the addition/removal of a value for the service. By using the static method, the data change is found at compile time rather than a vague runtime error.
And last, but not least here is its definition in the manifest.:
1:
2: <service android:name=".services.SaveNoteService"
3: android:exported="false" />
Commits
The majority of the changes can be found on github at https://github.com/fsk-software/mynotes/commit/ed209572c5e1302ac03288a84b4e2003cb5894e7.
No comments :
Post a Comment