1

I've developed a small Android application which uses an AlarmManager to call a Handler Thread that synchronizes some information from the mobile device with a remote server. This process happens every 15 minutes.

When I use the HandlerThread without the aid of the AlarmManager, everything ALWAYS works fine. But when I try to put these two to work together, SOMETIMES it works and SOMETIMES I get the following error :

W/System.err(10557): java.lang.NullPointerException
W/System.err(10557):at Synchronizer.queueSyncEverything(Synchronizer.java:109)
W/System.err(10557): at SyncService.onHandleIntent(SyncService.java:33)
W/System.err(10557): at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
W/System.err(10557): at android.os.Handler.dispatchMessage(Handler.java:99)<BR/>
W/System.err(10557): at android.os.Looper.loop(Looper.java:137)
W/System.err(10557): at android.os.HandlerThread.run(HandlerThread.java:60)

The code is really simple as you can see in the following snippets:

//Method from SyncService, a class which extends IntentService    
protected void onHandleIntent(Intent intent) {
    ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
    if(cm.getActiveNetworkInfo() == null){
        return;
    }
    if(mSynchronizer == null){
        mSynchronizer = new Synchronizer(getApplicationContext(), this);
        mSynchronizer.start();
            mSynchronizer.getLooper();
    }       
    mSynchronizer.queueSyncEverything();

}

//Method from Synchronizer, a class which extends HandlerThread
public void queueSyncEverything(){
    try{
        mHandler.obtainMessage(MESSAGE_SYNC_EVERYTHING).sendToTarget();
    }
    catch(Exception e){
        mListener.onError();
        e.printStackTrace();
    }
}

I already checked if the Handler or the Looper are null, but they're all fine.

EDIT: As @DavidWasser suggested, I did some more testing and discovered that sometimes mHandler is null. This variable is set inside the method onLooperPrepared from Synchronizer (which extends from HandlerThread) as you can see here:

@Override
protected void onLooperPrepared(){
    mHandler = new Handler(){

        @Override
        public void handleMessage(Message msg){

            if(msg.what == MESSAGE_SYNC_EVERYTHING){
                handleSyncEverything();
                mListener.onFinished();
            }
        }
    };
}

The constructor of this class, as @DavidWasser asked, is just:

public Synchronizer(Synchronizer.Listener listener){
    super(TAG);
    mListener = listener;       
}

mListener is just an delegate-like object who receives events sent by the Synchronizer class, like when the Synchronization is finished. TAG is just a name to make it easier to debug. Maybe my main difficulty is to understand how the class HandlerThread works and when is it the right time to set its Handler object.

P.S.: This way of setting up the HandlerThread I got by reading the book "Android Programming: The Big Nerd Ranch Guide".

  • I'm assuming line 109 is `mHandler.obtainMessage(MESSAGE_SYNC_EVERYTHING).sendToTarget();` and `mHandler` is null. Where do you set that variable to something? Also post the code that is called when the alarm goes off. – David Wasser Mar 03 '14 at 10:41
  • @DavidWasser, just did some more testing and yes, the mHandler is null. It is set inside the method onLooperPrepared from Synchronizer (HandlerThread) as you can see here: `protected void onLooperPrepared(){ mHandler = new Handler(){ @Override public void handleMessage(Message msg){ if(msg.what == MESSAGE_SYNC_EVERYTHING){ handleSyncEverything(); mListener.onFinished(); } } }; }` As far as I know, this method is called when mSynchronizer.getLooper() is called. The method called when the alarm goes off is too big, but it's just some calls to a remote server. – pedropaulosbs Mar 03 '14 at 16:05
  • Finding it difficult to follow your code. Please don't add code snippets in comments. Just edit your question and add the code to the original question. What is `Synchronizer` derived from? Please show the constructor for that class – David Wasser Mar 03 '14 at 17:08
  • Sorry, @David, this is the first time I post here in StackOverflow, so I'm not used with the conventions and the way the text editors work. I've just edited the question and added the information you asked me. Thanks! – pedropaulosbs Mar 03 '14 at 19:11
  • the handler is initialized in onLooperPrepared, but you don't give it time to be prepared. you should call `queueSyncEverything` in `onLooperPrepared` – njzk2 Mar 03 '14 at 19:12
  • @njzk2, maybe you're right about the mHandler not being ready when I call it, but I don't think your solution would solve the problem because it's a responsibility of the Synchronizer (HandlerThread) to add messages to the queue maintained by the Looper and also it is a responsibility of the Handler to execute tasks from that queue. I'm not sure if we should break this framework defined behavior. – pedropaulosbs Mar 03 '14 at 19:47

1 Answers1

0

Don't initialize mHandler in onLooperPrepared() as this method could be called after the call to getLooper() returns. This is a small timing window that could bite you.

Instead create a method in Synchronizer like this:

public void createHandler() {
    mHander = new Handler(getLooper()) {
        @Override
        public void handleMessage(Message msg){
            if(msg.what == MESSAGE_SYNC_EVERYTHING){
                handleSyncEverything();
                mListener.onFinished();
            }
        }
    };
}

and call this method in onHandleIntent() instead of calling mSynchronizer.getLooper().

This will ensure that mHandler is initialized before you call queueSyncEverything().

NOTE: You don't need to override onLooperPrepared() at all.

David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • It worked like a charm, thank you. About your note, I'm aware that I don't need to override onLooperPrepared, however the documentation of this method is clear: "onLooperPrepared: Call back method that can be explicitly overridden if needed to execute some setup **before Looper loops**." So, in my opinion, onLooperPrepared should finish before the looper starts to handle its queue. In my issue, the inverse happened. It seems that maybe we've found a bug here. Anyway, I think this is a subject for another question. This one is nailed. Thank you again! – pedropaulosbs Mar 04 '14 at 00:10
  • I looked at the source code for `HandlerThread` before answering your question. `onLooperPrepared()` **is** called before the Looper loops. That means that the Looper is already set up. You were initializing `mHandler` in that method. That is fine. Your problem is that you were possibly calling `queueSyncEverything()` **before** `onLooperPrepared()` had been called, because these things are happening in parallel in 2 different Threads. My code just rearranged things to ensure that you don't call `queueSyncEverything()` **before** `mHandler` was initialized. – David Wasser Mar 04 '14 at 10:02
  • I'm guessing that it would be unwise to use this solution within the UI thread, because `getLooper()` blocks. This other SO answer suggests queuing messages until the handler is ready: http://stackoverflow.com/a/25459322/650894 – Joe Lapp Feb 05 '17 at 20:38
  • I found this blog solving this problem as you describe, except blocking the UI thread on the `getLooper()` call. So it never takes so long that it's an issue? https://blog.nikitaog.me/2014/10/11/android-looper-handler-handlerthread-i/ – Joe Lapp Feb 05 '17 at 22:58
  • @JoeLapp calling `getLooper()` in the UI thread will not block, because the looper is already set up in the UI thread. – David Wasser Feb 06 '17 at 13:27
  • Right. I'm speaking generally. The UI main looper is already set up. If I create a new thread and want to give it a handler, I can't do it from the new thread because onLooperPrepared() is the place for that but can result in race conditions. So it's set up from another thread -- the main UI thread. But setting it up requires (?) calling getLooper() for the new thread, thus blocking the main thread until the new looper is ready. What am I missing? (In any case, I'm now doing this, so far without negative repercussions.) – Joe Lapp Feb 06 '17 at 21:29
  • 1
    @JoeLapp Yes, you are correct. In this case you can (theoretically) block the main (UI) thread. However, setting up the `Looper` isn't a complex or time-consuming task, so this won't block for any significant amount of time. And yes, everybody does this. – David Wasser Feb 07 '17 at 10:03