NullPointerException in HandlerThread
D

3

5

This bug baffled me for hours. I am getting the NullPointerException. The problem is this error is not consistent. It happens when I launch the app, but only occasionally. So I am not sure what is causing it.

I apologize for the verbose question with the error log, but I could not find another way of asking.

The error log is as follows:

FATAL EXCEPTION: main
Process: com.myproject.android, PID: 22175
java.lang.NullPointerException
    at com.myproject.android.ImageDownloaderThread.queueImage(ImageDownloaderThread.java:74)
    at com.myproject.android.NewsItemPagerActivity$NewsItemFragmentStatePagerAdapter.getItem(NewsItemPagerActivity.java:325)
    at android.support.v13.app.FragmentStatePagerAdapter.instantiateItem(FragmentStatePagerAdapter.java:109)
    at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:832)
    at android.support.v4.view.ViewPager.populate(ViewPager.java:982)
    at android.support.v4.view.ViewPager.populate(ViewPager.java:914)
    at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1436)
    at android.view.View.measure(View.java:16497)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at android.view.View.measure(View.java:16497)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
    at com.android.internal.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:327)
    at android.view.View.measure(View.java:16497)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
    at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2291)
    at android.view.View.measure(View.java:16497)
    at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1912)
    at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1109)
    at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1291)
    at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
    at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
    at android.view.Choreographer.doCallbacks(Choreographer.java:574)
    at android.view.Choreographer.doFrame(Choreographer.java:544)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
    at android.os.Handler.handleCallback(Handler.java:733)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:5001)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:515)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
    at dalvik.system.NativeStart.main(Native Method)

And the code where this is happening is shown below:

package com.myproject.android;

import java.io.IOException;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;

/*
 * This class is used to download images in the background thread
 */
public class ImageDownloaderThread<Token> extends HandlerThread {

    private static final String TAG = "ImageDownloader";
    private static final int MESSAGE_DOWNLOAD = 0;

    // This is the handler attached to the looper
    Handler mHandler; 






    // The is used as a reference to the main UI thread's handler
    Handler mResponseHandler;

    // This is a listener object that is used to update the main UI thread with the image that is downloaded
    Listener mListener;

    // This is the interface needed when a listener is created. It forces an implementation of the callback in the main UI thread
    public interface Listener {
        void onImageDownloaded(Bitmap image, int pos);
    }

    // Set the listener
    public void setListener(Listener listener) {
        mListener = listener;
    }





    // Constructor
    public ImageDownloaderThread(Handler responseHandler) {
        super(TAG);
        mResponseHandler = responseHandler; // Set the response handler to the one passed from the main thread
    }


    // This method executes some setup before Looper loops for each message
    @Override
    protected void onLooperPrepared() {

        // Create a message handler to handle the message queue
        mHandler = new MessageHandler(ImageDownloaderThread.this);
    }


    // This method is used to add a message to the message queue, so that it can be handled later
    // ... this method is called by the main UI thread to add the message to the queue of the current thread to be handled later
    public void queueImage(String url, int pos) {

        mHandler
            .obtainMessage(MESSAGE_DOWNLOAD, pos, 0, url)
            .sendToTarget();
    }





    // This method is used to download the image  
    private void handleRequest(String url, int pos) {

        try {

            // first check if the url is empty. if it is, then return
            if (url == null) {
                return;
            }

            // Download the image
            byte[] bitmapBytes = new NewsItemsFetcher().getUrlBytes(url);

            // Generate a bitmap
            final Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);

            // Set position as 'final'
            final int position = pos;


            // We are using mResponseHandler.post(Runnable) to send a message to the response handler
            // This message will eventually result in the main thread updating the UI with the image
            mResponseHandler.post(new Runnable() {
                @Override
                public void run() {                 
                    mListener.onImageDownloaded(bitmap, position);

                }
            });

        }

        catch (HttpResponseException httpe) {
            // TODO: Handle http response not OK
            Log.e(TAG, "Error in server response", httpe);
        }

        catch (IOException ioe) {
            // TODO: Handle download error
            Log.e(TAG, "Error downloading image", ioe);
        }

    }


    class MessageHandler extends Handler {

        private final ImageDownloaderThread<Token> mImageDownloader;

        MessageHandler(ImageDownloaderThread<Token> imageDownloader) {
            mImageDownloader = imageDownloader;
        }

        // This method is used to process the message that is waiting in the queue 
        @Override
        public void handleMessage(Message msg) {

            // First, check if the message is to download an image
            if (msg.what == MESSAGE_DOWNLOAD) {

                // Call the handleRequest() function which will eventually download the image
                String url = (String)msg.obj;
                int pos = msg.arg1;


                if (mImageDownloader != null) {
                    mImageDownloader.handleRequest(url, pos);
                }

            }
        }

    }

}

In case you are wondering, line 74 in the error log (more specifically, this at com.myproject.android.ImageDownloaderThread.queueImage(ImageDownloaderThread.java:74), references the .obtainMessage(MESSAGE_DOWNLOAD, pos, 0, url) line of code in queueImage()


EDIT

According to a suggestion in Loop's answer, mHandler is null when queueImage() is called. So, how can I guarantee mHandler to be intialized by onLooperPrepared() before executing any queueImage() call?

Deportment answered 23/8, 2014 at 6:41 Comment(0)
D
7

The only reason for me would be that queueImage() method is called before onLooperPrepared() so mHandler is not initialize.

Update

HandlerThread is simply a Thread with implementation of the run() method where onLooperPrepared() is called.

@Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();//It's HERE
    Looper.loop();
    mTid = -1;
}

So when it's called depends on starting this thread. If you start it and immediately call public method on the reference of this thread you may encounter a race condition and mHandler is not initialized on time.

One solution would be a delay for start processing images or playing with synchronization techniques. However, I would use much simpler way.

Just to be clear, you want your mHandler to be initialized just after HandlerThread is created and you don't want to do it explicitly from the Main Activity where HandlerThread is created.

Update 2

Just come up with the following solution.

queueImage() provides simple and light data. You could check if mHandler is null, if it's true add parameters of queueImage() to that queue. When onLoopPrepared() is called check if there is anything is that queue and process that data.

private LinkedBlockingQueue<Pair<String,Integer>> mQueue = new LinkedBlockingQueue<Pair<String,Integer>>();

public void queueImage(String url, int pos) {
    if (mHandler == null) {
        mQueue.put(new Pair<String,Integer>(url, pos));
        return;
    }
    mHandler
        .obtainMessage(MESSAGE_DOWNLOAD, pos, 0, url)
        .sendToTarget();
}

@Override
protected void onLooperPrepared() {

    // Create a message handler to handle the message queue
    mHandler = new MessageHandler(ImageDownloaderThread.this);
    //TODO check the queue here, if there is data take it and process
    //you can call queueImage() once again for each queue item
    Pair<String, Integer> pair = null;
    while((pair = mQueue.poll()) != null) {
        queueImage(pair.first, pair.second);
    }
}
Depth answered 23/8, 2014 at 7:0 Comment(9)
But according to android documentation, onLooperPrepared() is called before the Looper loops. The exact wording is Call back method that can be explicitly overridden if needed to execute some setup before Looper loops. (for more details, here is the link developer.android.com/reference/android/os/… ). Now, why would the queueImage() get called before onLooperPrepared()?Deportment
By the way, I find it cool that your ID is Loop and we are discussing Loopers :)Deportment
Just so you know, I did some testing. And you are right, mHandler IS null. I prefer your second suggested solution, but I am not willing to skip some queueImage() calls. How can I make queueImage() wait for onLooperPrepared() to finish execution so it finishes its intialization?Deportment
Can you show where and when queueImage() is called?Depth
I like update 2, but can you explain it a bit more please? what queue are you taking about when mHandler is null? ThanksDeportment
Also, just FYI, the solution on update 1 would not work. It now causes android.os.NetworkOnMainThreadException.Deportment
Right, I forgot that Handler attach to the looper of the current thread. We could try calling getLooper() of the HandlerThread but in constructor it will return null. I will update my answer shortly removing update 1 and I will give you an example for update 2.Depth
Thanks a lot. You are very helpful. I am so greatful to you. It worked. I would give you ten up votes, but the site only allows one. By the way, I used a LinkedList queue insted of a LinkedBlockingQueue because this queue is accessed through only one thread (the background looper thread). Other than that, all is great. Thanks.Deportment
Be careful, as queueImage and onLooperPrepared are called from different threads normally, so messages can be lost due to race conditions. Proper synchronization is needed in this case.Condonation
P
1

I meet same question. I solved by calling wait() before before queueing message and calling call notifyAll() in onLooperPrepared. This don't need additional variables that store pending messages.

Porker answered 19/10, 2016 at 12:50 Comment(1)
This is a great answer if you can afford to block the thread. I'm experiencing this race condition in the Android UI thread, which I can't block.Vera
A
0

invoke getLooper() after HandlerThread.start() is call.
getLooper() will block until onLooperPrepared() is finish.

Alonaalone answered 15/9, 2022 at 4:4 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Ingaingaberg

© 2022 - 2024 — McMap. All rights reserved.