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?
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.
The only reason for me would be that
queueImage()
method is called beforeonLooperPrepared()
somHandler
is not initialize.Update
HandlerThread
is simply aThread
with implementation of therun()
method whereonLooperPrepared()
is called.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 afterHandlerThread
is created and you don't want to do it explicitly from the Main Activity whereHandlerThread
is created.Update 2
Just come up with the following solution.
queueImage()
provides simple and light data. You could check ifmHandler
is null, if it's true add parameters ofqueueImage()
to that queue. WhenonLoopPrepared()
is called check if there is anything is that queue and process that data.