NullPointerException in HandlerThread

This mistake baffled me for hours. I get a NullPointerException . The problem is that this error is incompatible. This happens when I launch the application, but only occasionally. So I'm not sure what causes it.

I apologize for the detailed error log question, but I could not find another way to ask.

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 happens 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 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); } } } } } 

If you're interested, line 74 in the error log (more precisely, it is at com.myproject.android.ImageDownloaderThread.queueImage(ImageDownloaderThread.java:74) , refers to the line of code .obtainMessage(MESSAGE_DOWNLOAD, pos, 0, url) in queueImage()


EDIT

As suggested in the Loop answer, mHandler is null when queueImage() called. So, how can I guarantee that mHandler must be initialized onLooperPrepared() before making any call to queueImage() ?

+5
source share
2 answers

The only reason for me is because the queueImage() method is called before onLooperPrepared() , so mHandler not initialized.

Update

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

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

Therefore, when it is called, it depends on the start of this thread. If you run it and immediately call the public method via the link of this thread, you may encounter a race condition and mHandler will not be initialized in time.

One solution would be to delay the start of image processing or playing games using synchronization technologies. However, I would use a much simpler way.

To be clear, you want your mHandler be initialized immediately after creating HandlerThread , and you do not want to do this explicitly from the main operation in which HandlerThread is created.

Update 2

Just come up with the following solution.

queueImage() provides simple and easy data. You can check if mHandler is null, if true, add queueImage() parameters to this queue. When onLoopPrepared() is called, check to see if there is anything in this queue and this data is being processed.

 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); } } 
+7
source

I answer the same question. I decided by calling wait () before sending the message to the queue and calling call notifyAll () in onLooperPrepared. This does not require additional variables that store pending messages.

+1
source

Source: https://habr.com/ru/post/1200970/


All Articles