Wednesday, September 23, 2015

MyNotes: Saving/Deleting

 

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