Welcome Android Developpers

Welcome Android Developpers
My name is Mathias Séguy, I am an Android Expert and Trainer and created the Android2EE company.

For full information, it’s here : Android2EE's Training
You can meet me on Google+, follow me on Twitter or on LinkedIn

Thursday, December 15, 2011

Handler And LifeCycle Part III

Bandeau Conference

Hello again,
To continue with the issue of Handler and activity's life cycle, there is a solution, advocated by some, that is to use the public method Object onRetainNonConfigurationInstance () to return a pointer to an object in the activity.
Uh, let me explain, when your activity is to be destroyed and immediately recreates the method onRetainNonConfigurationInstance can send an object from the instance of the dying activity to the instance of new activity.
Example adapted to our problem:


@Override
public Object onRetainNonConfigurationInstance() {
                //Save the thread
                return backgroundThread;
}

to retrieve the object in the onCreate method:

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
//Do something
                backgroundThread = (Thread) getLastNonConfigurationInstance();
//Do something again
}


It is clear that with this method everything seems solved (at least for the change in orientation of the device). And yes, BUT NOT!!!, it's a serious mistake to think that. Indeed, what happens if your activity is killed (without being recreated immediately)? Well, then your thread is an orphan, your handler and your activity become ghosts (because the garbage collector detects them as used). And here, again, Darth Vader mocks.

The solution, and yes, there is one, is somewhat more complex than that. We must re-implement an atomic boolean to kill the thread if your activity is not restarted immediately.

And then, well, I invite you to get the tutorials that I dropped on Android2EE section Example/Tutorials/Handler's Tutorials. I give you 3 tutorials, one with the Atomic booleans, one with the onRetainNonConfigurationInstance instance and the last is a demonstration of the memory leaks.

But if your want the code, here it is:


The code of the activity:

public class HandlerActivityBindingThreadTutoActivity extends Activity {
/******************************************************************************************/
/** Managing the Handler and the Thread *************************************************/
  /******************************************************************************************/
  /**
   * The Handler
   */
  private Handler handler;
  /**
   * The thread that update the progressbar
   */
  Thread backgroundThread;
  /**
   * The runnable that have atomic booleans to be managed (and killed)
   */
  ManagedRunnable runnable;

  /******************************************************************************************/
  /** Others attributes **************************************************************************/
  /******************************************************************************************/
  /**
   * The string for the log
   */
  private final static String TAG = "HandlerActivityBindingThreadTutoActivity";
  /**
   * The ProgressBar
   */
  private ProgressBar progressBar;
  /**
   * The way the progress bar increment
   */
  private boolean reverse = false;
  /**
   * The activity name
   */
  private String activityName;

  /******************************************************************************************/
  /** Managing the activity **************************************************************************/
  /******************************************************************************************/

  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    backgroundThread = (Thread) getLastNonConfigurationInstance();
   
    setContentView(R.layout.main);
    // Instantiate the progress bar
    progressBar = (ProgressBar) findViewById(R.id.progressbar);
    progressBar.setMax(100);
    // use a random double to give a name to the thread, the handler and the activity
    double randomD = Math.random();
    final int randomName = (int) (randomD * 100);
    activityName = "Activity" + randomName;
    Log.e(TAG, "The activity," + activityName + " is created");

    // handler definition
    handler = new Handler() {
      /**
       * The handler name
       */
      String handlerName = "HandlerName" + randomName;

      /*
       * (non-Javadoc)
       *
       * @see android.os.Handler#handleMessage(android.os.Message)
       */
      @Override
      public void handleMessage(Message msg) {
        super.handleMessage(msg);
        // retrieve the calling thread's name
        int threadId = (Integer) msg.getData().get("ThreadId");
        Log.e(TAG, "The handler," + handlerName + " receives a message from the thread n°" + threadId);
        // Launch treatment
        updateProgress();
      }
    };
    // Check if the thread is already running (if the activity have been destroyed and recreated
    // immediately after your thread should be alive)
    // The List contains the Runnable and the Thread
    @SuppressWarnings("unchecked")
    List objects = (List) getLastNonConfigurationInstance();
    // if the thread and the runnable are there:
    if (objects != null) {
      // load the runnable
      runnable = (ManagedRunnable) objects.get(0);
      // set the new handler to the runnable
      runnable.runnableHandler = handler;
      // load the thread
      backgroundThread = (Thread) objects.get(1);
      // then tell the thread it should not die
      runnable.isThreadShouldDie.set(false);
      // and do a log
      Log.e(TAG, "runnable and thread are restore");
    } else {
      // if the thread and the runnable are not there: create them
      // Define and implement the runnable
      runnable = new ManagedRunnable() {
        /**
         * The message exchanged between this thread and the handler
         */
        Message myMessage;

        /*
         * (non-Javadoc)
         *
         * @see java.lang.Runnable#run()
         */
        public void run() {
          try {
            Log.e(TAG, "NewThread " + randomName);
            while (!setThreadDead.get()) {
              Log.e(TAG, "Thread is running (thread name" + randomName + ")");
              // For example sleep 0.1 second
              Thread.sleep(100);
              // Send the message to the handler (the
              // handler.obtainMessage is more
              // efficient that creating a message from scratch)
              // create a message, the best way is to use that
              // method. (take care here the runnableHandler is a field within the
              // ManagedRunnable class)
              myMessage = runnableHandler.obtainMessage();
              // put the thread id in the message to show which thread send it:
              Bundle data = new Bundle();
              data.putInt("ThreadId", randomName);
              myMessage.setData(data);
              // then send the message to the handler
              runnableHandler.sendMessage(myMessage);
              // Here is the smart element of the management
              // if the activity is not re-create immediately then kill your thread.
              if (isThreadShouldDie.get()) {
                // wait to see if the isThreadShouldDie boolean changes its value
                // wait 5s (for example)
                //ok, do 10 loops of 0.5s and while isThreadShouldDie is true
                for (int i = 0; (i < 10 )&& (isThreadShouldDie.get()); i++) {
                  Thread.sleep(500);                 
                }
                // then test again:
                if (isThreadShouldDie.get()) {
                  //if it's still true, kill the thread
                  setThreadDead.set(true);
                }
              }
            }
            Log.e(TAG, "Thread " + randomName + " dead");
          } catch (Throwable t) {
            // just end the background thread
          }
        }
      };
      //then link the Handler with the handler of the runnable
      runnable.runnableHandler = handler;
      //link the thread and the handler
      backgroundThread = new Thread(runnable);
      //set thread name
      backgroundThread.setName("HandlerTutoActivity " + randomName);
      // start the thread
      backgroundThread.start();
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see android.app.Activity#onRetainNonConfigurationInstance()
   */
  @Override
  public Object onRetainNonConfigurationInstance() {
    // Save the Thread and the runnable
    List objects = new ArrayList();
    objects.add(runnable);
    objects.add(backgroundThread);
    return objects;
  }

  /*
   * (non-Javadoc)
   *
   * @see android.app.Activity#onDestroy()
   */
  protected void onStop() {
    Log.i(TAG, "onStop called");
    // If the thread is not immediately restore then kill it
    runnable.isThreadShouldDie.set(true);
    super.onStop();
  }

  /*
   * (non-Javadoc)
   *
   * @see android.app.Activity#onSaveInstanceState(android.os.Bundle)
   */
  protected void onSaveInstanceState(Bundle outState) {
    // Save the state of the reverse boolean
    outState.putBoolean("reverse", reverse);
    // then save the others GUI elements state
    super.onSaveInstanceState(outState);
  }

  /*
   * (non-Javadoc)
   *
   * @see android.app.Activity#onRestoreInstanceState(android.os.Bundle)
   */
  protected void onRestoreInstanceState(Bundle savedInstanceState) {
    // Restore the state of the reverse boolean
    reverse = savedInstanceState.getBoolean("reverse");
    // then restore the others GUI elements state
    super.onRestoreInstanceState(savedInstanceState);
  }

  /******************************************************************************************/
  /** Private methods **************************************************************************/
  /******************************************************************************************/
  /**
   * The method that update the progressBar
   */
  private void updateProgress() {
    Log.e(TAG, "updateProgress called  (on activity n°" + activityName + ")");
    // get the current value of the progress bar
    int progress = progressBar.getProgress();
    // if the max is reached then reverse the progressbar's progress
    // if the 0 is reached then set the progressbar's progress normal
    if (progress == progressBar.getMax()) {
      reverse = true;
    } else if (progress == 0) {
      reverse = false;
    }
    // increment the progress bar according to the reverse boolean
    if (reverse) {
      progressBar.incrementProgressBy(-1);
    } else {
      progressBar.incrementProgressBy(1);
    }
  }
}

And the ManageRunnable class:
public abstract class ManagedRunnable implements Runnable {
  /**
   * The atomic boolean to wait to know if the thread should died or not
   */
  public AtomicBoolean isThreadShouldDie = new AtomicBoolean(false);
  /**
   * The atomic boolean to set the thread dead
   */
  public AtomicBoolean setThreadDead = new AtomicBoolean(false); 
  /**
   * The Handler targeted by the runnable (the runnable talks with this handler)
   */
  public Handler runnableHandler;
}





So, Thanks who?
Thanks, Android2ee, the Android Programming Ebooks :o)

Mathias Séguy
mathias.seguy.it@gmail.com
Auteur Android2EE
Ebooks to learn Android Programming.
AndroidEvolution
Retrouvez moi sur Google+
Suivez moi sur Twitter
Rejoignez mon réseau LinkedIn ou Viadeo

No comments:

Post a Comment