HandlerThread中的NullPointerException [英] NullPointerException in HandlerThread
问题描述
这个错误使我困惑了好几个小时.我收到了 NullPointerException .问题是此错误不一致.当我启动应用程序时会发生这种情况,但偶尔会发生.所以我不确定是什么原因造成的.
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.
错误日志如下:
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);
}
}
}
}
}
如果您想知道,错误日志中的第74行(更具体地说,此at com.myproject.android.ImageDownloaderThread.queueImage(ImageDownloaderThread.java:74)
引用了queueImage()
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()
根据 Loop 答案中的建议,调用queueImage()
时mHandler
为空.因此,在执行任何queueImage()
调用之前,如何保证mHandler
由onLooperPrepared()
初始化?
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?
推荐答案
对我来说,唯一的原因是queueImage()
方法在onLooperPrepared()
之前被调用,因此mHandler
不会被初始化.
The only reason for me would be that queueImage()
method is called before onLooperPrepared()
so mHandler
is not initialize.
更新
HandlerThread
只是实现了run()
方法(其中调用onLooperPrepared()
的实现)的Thread
.
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;
}
因此,何时调用取决于启动此线程.如果启动它,并立即在该线程的引用上调用public方法,则可能会遇到竞争情况,并且mHandler
不能及时初始化.
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.
请注意,您希望在创建HandlerThread
之后立即初始化mHandler
,并且不想在创建HandlerThread
的主活动中明确地进行此操作.
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.
更新2
只需提出以下解决方案.
Just come up with the following solution.
queueImage()
提供简单明了的数据.您可以检查mHandler
是否为null,如果为true,则将queueImage()
的参数添加到该队列中.调用onLoopPrepared()
时,请检查该队列中是否有任何内容并处理该数据.
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);
}
}
这篇关于HandlerThread中的NullPointerException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!