0

I have a Service which is performing a data update. I have an activity which attaches a listener to the service (via a local binding). The listener receives progress updates. Upon receiving a progress update, it schedules a runnable to be run on the UI thread. Here's the code (updated to show the full listing):

public class MyActivity extends Activity {

  static final int UPDATE_DIALOG = 0;
  ProgressDialog updateDialog;

  private TaskService taskService;

  private ServiceConnection taskServiceConnection = new ServiceConnection() {

    private final TaskServiceObserver taskServiceObserver = new TaskServiceObserver() {

      public void updateProgress(final int progress, final int total) {
        runOnUiThread(new Runnable() {
          public void run() {          
            if (updateDialog == null || !updateDialog.isShowing()) {
              showDialog(UPDATE_DIALOG);
            }
            updateDialog.setProgress(progress);
          }
        });
      }

      public void updateCompleted() {
        runOnUiThread(new Runnable() {
          public void run() {  
            dismissDialog(UPDATE_DIALOG);
            startNextActivity();
          }
        });
      }
    };

    public void onServiceConnected(ComponentName name, IBinder binder) {
      taskService = ((LocalBinder) binder).getService();

      taskService.addObserver(taskServiceObserver);
    }

    public void onServiceDisconnected(ComponentName name) {
      taskService.removeObserver(taskServiceObserver);
      taskService = null;
    }

  };

  protected void onStart() {
    super.onStart();

    Intent intent = new Intent(this, TaskService.class);
    startService(intent);
    bindService(intent, taskServiceConnection, Context.BIND_AUTO_CREATE);
  }

  protected void onStop() {
    super.onStop();

    if (taskService != null) {
      unbindService(taskServiceConnection);
    }
  }

  protected Dialog onCreateDialog(int id) {
    switch (id) {
    case UPDATE_DIALOG:
      updateDialog = new ProgressDialog(this);
      updateDialog.setTitle("My App");
      updateDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
      updateDialog.setMessage("Preparing to run for the first time...");
      return updateDialog;
    default:
      return null;
    }
  }

}

If I tap the home button while the dialog is showing, then return to the app, I get a crash on the showDialog line. With the debugger I was able to determine that the activity is in the finished state.

What would be an appropriate check to put in my runnable which would determine whether it is safe to call showDialog?

Hilton Campbell
  • 6,065
  • 3
  • 47
  • 79

3 Answers3

0

I would personnally dismiss the progress dialog when the activity goes to pause (override onPause) and recreate it if necessary when the activity is resumed (override onResume). You could be leaking memory by keeping references to your activity in other separate objects (your dialog)

Vincent Mimoun-Prat
  • 28,208
  • 16
  • 81
  • 124
0

You should detach the listener in the onPause method so that since your activity is going into the background, the listener won't fire and try to update the UI.

Robby Pond
  • 73,164
  • 16
  • 126
  • 119
  • I'm currently only detaching the listener in `onStop`, so I'll be sure to do so in `onPause` as well. Nonetheless, it is still possible to have this interleaving of events: 1. service notifies listener 2. listener adds a runnable to update progress in UI 3. `onPause` detaches listener 4. runnable tries to show dialog in a paused activity – Hilton Campbell Mar 21 '11 at 16:45
  • Definitely but it would help to see how your service is communicating with the Activity. – Robby Pond Mar 21 '11 at 16:47
  • I'll post more complete source code this evening. Thanks for the help. – Hilton Campbell Mar 21 '11 at 17:08
  • I've added the full code listing. I found that detaching the listener in `onPause` did not help, only surfaced the crash earlier. I tried adding a flag that I toggle when I detach the listener, and checking for that flag in the Runnable and exiting early if set. This works great, but I'd like to avoid maintaining a flag if the Activity already effectively has one. – Hilton Campbell Mar 22 '11 at 00:28
  • @Hilton You may want to check my answer on this question. If you use a local IntentService you can really simplify your code. And it shows how to properly handle your activity going away while the service is still running. http://stackoverflow.com/questions/3197335/android-restful-api-service/3197456#3197456 – Robby Pond Mar 22 '11 at 02:28
0

The solution I ended up going with was to create a flag taskServiceBound which was set to true after binding to the service in onStart and set to false before unbinding from the service in onStop. Because the flag is updated on the UI thread, I can use it to gate the Runnables in updateProgress and updateCompleted. For example:

  public void updateCompleted() {
    runOnUiThread(new Runnable() {
      public void run() {  
        if (taskServiceBound) {
          dismissDialog(UPDATE_DIALOG);
          startNextActivity();
        }
      }
    });
  }
Hilton Campbell
  • 6,065
  • 3
  • 47
  • 79