22

I have an Android app with a main tab activity, and several activities within the individual tabs. In my main activity's onCreate(), I have a runnable that creates a list, and in the individual activities, I make use of this list.

In the individual activities's onCreate(), I also have Runnables that operate on the list. However, I need these Runnables to only run when the main tab activity's Runnable completes creating the list, otherwise I'd get a null list. I'm trying to find an elegant way of doing this. Right now, in my main activity's Runnable, I'm setting a global boolean variable isDone, and in my individual activity's Runnable, I'm waiting for isDone to be set via a while loop. This works, but probably isn't the best way of doing so.

Any thoughts?

Thanks.

Edit: I'm trying the following code out, but I'm getting runtime errors:

In my MainActivity's Runnable:

mainRunnable = new Runnable() {
  public void run() {
    try {
      generateList();
      synchronized(this) {
      listDone = true;
      notifyAll();
    }
  } catch (Exception e) {
      Log.e("BACKGROUND_PROC", e.getMessage());
    }
  }
};
Thread thread = new Thread(null, mainRunnable, "Background");
thread.start();

In my OtherActivity's Runnable:

otherRunnable = new Runnable() {
  public void run() {
    synchronized(MainActivity.mainRunnable) {
      if (!MainActivity.getListDone()) {
        try {
          wait();
        } catch (InterruptedException e) {
        }
      }
    }
  }
};
Thread thread = new Thread(null, otherRunnable, "Background");
thread.start();

The mainRunnable seems to run completely, but the otherRunnable seems to cause the app to crash. I get the following error message:

01-10 15:41:25.543: E/WindowManager(7074): Activity com.myapp.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@40539850 that was originally added here
01-10 15:41:25.543: E/WindowManager(7074): android.view.WindowLeaked: Activity com.myapp.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@40539850 that was originally added here
user1118764
  • 9,255
  • 18
  • 61
  • 113

7 Answers7

20

You can use the wait and notify methods.

To do this, there needs to be some globally accessible object whose lock isn't used by anything else in the program at this point in time. I'm assuming that the list-creating Runnable itself can play this role.

So you could add something like this to the list-creating Runnable class:

private boolean listsDone = false;

boolean getListsDone() {
    return listsDone;
}

And something like this to its run() method, immediately after it's done creating the lists:

synchronized (this) {
    listsDone = true;
    notifyAll();
}

And something like this to the other Runnables' run() methods, at the point where they need to wait:

synchronized (listCreatingRunnableObject) {
    if (!listCreatingRunnableObject.getListsDone()) {
        try {
            listCreatingRunnableObject.wait();
        } catch (InterruptedException e) {
            // handle it somehow
        }
    }
}

Update: To clarify, both synchronized blocks need to be synchronized over the same object, and you have to call wait() and notifyAll() on that object. If the object is the Runnable, then it can be implicit for the first one (as in the above code), but if it's the activity, you need to explicitly use the activity object in both cases.

Taymon
  • 24,950
  • 9
  • 62
  • 84
  • Thanks. What exactly is listCreatingRunnableObject? Is it the activity class or the Runnable? – user1118764 Jan 10 '12 at 07:13
  • @user1118764 I'm assuming it's the Runnable, but it can be any globally accessible object. – Taymon Jan 10 '12 at 07:15
  • I get the following error when I try to use your code in the other Runnable's run() method: 01-10 15:24:27.003: W/dalvikvm(5857): threadid=10: thread exiting with uncaught exception (group=0x4001d5a0) 01-10 15:24:27.023: E/AndroidRuntime(5857): FATAL EXCEPTION: Background 01-10 15:24:27.023: E/AndroidRuntime(5857): java.lang.IllegalMonitorStateException: object not locked by thread before wait() – user1118764 Jan 10 '12 at 07:27
  • @user1118764 D'oh! I forgot that you have to call `wait()` on the object that you're using for synchronization. See the update to my answer. – Taymon Jan 10 '12 at 07:42
  • @user1118764 I'm confused. You said there's only one list, so if it's already been created, then there's nothing to wait for and you can immediately do whatever comes next (which is why the `if` clause is there, aside from deadlock prevention). That's what you want, right? – Taymon Jan 10 '12 at 08:34
  • Thanks, that works! Previously I put the stuff I had to do within the try segment. I took it out and now they get executed correctly. – user1118764 Jan 10 '12 at 08:35
  • @user1118764 FYI, if this answer gave you what you were looking for, you can accept it by clicking on the checkmark. – Taymon Jan 10 '12 at 08:36
13

You can use a Queue like this:

public class RunQueue implemements Runnable
{
  private List<Runnable> list = new ArrayList<Runnable>();

  public void queue(Runnable task)
  {
    list.add(task);
  }

  public void run()
  {
    while(list.size() > 0)
    {
      Runnable task = list.get(0);

      list.remove(0);
      task.run();
    } 
  }
}

This allows you to use one thread rather than multiple threads. And you can maintain all your existing "Runnable" objects while simultaneously cleaning up any code they have for waits and joins.

64BitBob
  • 3,110
  • 1
  • 17
  • 23
  • 1
    I was trying to do the same thing, and I thought of doing this but didn't think it would work like I'd wanted. Seeing this answer I tried it out and it works perfectly. By far the simplest way to achieve this. Cheers! – Dave Sep 11 '14 at 00:22
  • Is there a way to get this run in a loop? I want one thread to run over and over again (infinitely), but they can't stack up. IT must run one at a time. @64BitBob – Riptyde4 Jan 07 '15 at 14:03
  • @Riptyde4 - Just replace the while/remove with a while(true) with a for(int i=0; i – 64BitBob Jan 08 '15 at 23:14
  • 1
    Shouldn't is be task.run() ? – Mukul Jul 15 '15 at 04:54
  • @Mukul - Holy cow! I can't believe that bug has gone unnoticed for so long. I've updated the post to fix the bug. Good catch! – 64BitBob Jul 15 '15 at 14:10
  • 1
    `Runnable task = list.get(0);` seems to give an error regarding incompatible types. The result from `list.get()` needs to be casted to a `Runnable` first. Other than that, this seems to work quite nicely! –  Jan 15 '17 at 13:34
9

Set up a CountDownLatch with a value of 1 in the main thread, then have the dependent threads wait on it. When the main thread is done, you Count Down the latch to 0 and the waiters will start right up.

Will Hartung
  • 115,893
  • 19
  • 128
  • 203
3

An active wait using a while loop is not a good idea at all. The simplest thing would be for the first Runnable to just fire up the rest of them as its last step. If that can't be made to work for some reason, take a look at posting a message to a Handler.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • Thanks. I did the first approach and it worked, but it didn't seem like a good idea either. I'm trying to do it via the wait/notify approach now. – user1118764 Jan 10 '12 at 07:07
0

I have created a helper method that contains all the boilerplate code for posting a runnable and waiting until it finishes running.

The logic is similar to what @Taymon describes, but the implementation is more general.

Check it out: https://gist.github.com/Petrakeas/ce745536d8cbae0f0761

Petrakeas
  • 1,521
  • 15
  • 28
0

Maybe you can refer to Looper in Android. Simply, a thead keep running task from queue in a while loop.

Victor Choy
  • 4,006
  • 28
  • 35
0

Is there a reason you are using Runnables and not Threads? If you use Threads, you can use the various thread communication primitives which exist for this exact reason (wait() and join() in particular).

curioustechizen
  • 10,572
  • 10
  • 61
  • 110
  • No reason, other than the fact that the example code I used declared a Runnable myRunnable, and then ran it with calls to: Thread thread = new Thread(null, myRunnable, "background"); thread.start Not sure if this is a Runnable or a thread? – user1118764 Jan 10 '12 at 06:48
  • @user1118764 You might still be able to directly access the Thread objects that way. However, if you use `wait()` (as I suggest in my answer), you don't need to, and you only want to use `join()` if you want to wait for the thread in question to finish executing entirely, rather than just complete some particular task. – Taymon Jan 10 '12 at 06:54
  • If you use new Thread(), then you are dealing with a Thread. So you can go ahead and use the communication mechanism I mentioned (and described in detail by Taymon in this answer - http://stackoverflow.com/a/8799584/570930 ) – curioustechizen Jan 10 '12 at 06:56