Saw this crash in Crashlytics after recent app release. It happens 100% in background; 50% in Sony, some in Huawei / Xiaomi, not in Samsung; 79% in Android 10, 19% in Android 8.
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.os.Handler.sendEmptyMessageDelayed(int, long)' on a null object reference
at xxx.xxx.xxx.MyEventManager$MyHandler.handleMessage(MyEventManager.java:80)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:359)
at android.os.HandlerThread.run(HandlerThread.java:67)
Code snippet:
public class MyEventManager {
private static class SingletonInstance {
private static final MyEventManager INSTANCE = new MyEventManager();
}
public static MyEventManager getInstance() {
return SingletonInstance.INSTANCE;
}
private HandlerThread mHandlerThread;
private Handler mHandler;
private class MyHandler extends Handler {
public MyHandler(#NonNull Looper looper) {
super(looper);
}
#Override
public void handleMessage(#NonNull Message message) {
super.handleMessage(message);
switch (message.what) {
case ACTION_SEND_REQUEST:
if (isThreadReady()) {
queryDatabaseAndSendRequest();
mHandler.sendEmptyMessageDelayed(ACTION_SEND_REQUEST, 600 * 1000);
}
break;
default:
break;
}
}
}
private boolean isThreadReady() {
return mHandler != null && mHandlerThread != null && mHandlerThread.getState() != Thread.State.TERMINATED;
}
private void initHandlerThreadIfNeeded() {
if (!isThreadReady()) {
mHandlerThread = new HandlerThread(TAG_HANDLER);
mHandlerThread.start();
mHandler = new MyHandler(mHandlerThread.getLooper());
}
}
public void stopAll() {
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
mHandler = null;
}
if (mHandlerThread != null) {
mHandlerThread.quit();
mHandlerThread = null;
}
}
}
The class MyEventManager periodically sends some data to server; as you can see from the code snippet, it calls mHandler.sendEmptyMessageDelayed() in handleMessage() to schedule the next trigger.
The method stopAll() is called in MainActivity's onDestroy().
Since it happens 100% in background and mHandler is set to null in stopAll(), so I believe that MainActivity has been destroyed at that moment, but that's strange because any pending messages should have been removed by mHandler.removeCallbacksAndMessages(null) when stopAll() is called, furthermore, there is null-checking before calling mHandler.sendEmptyMessageDelayed().
Can anyone tell me why I'm getting NPE?
Related
My requirements are to poll a web service every fixed interval. After instantiating the handler as a private field of the class, I'm doing this in the onCreate method of the main activity:
if (handler != null) {
handler.post(new Runnable() {
#Override
public void run() {
String sessionId = SharedPreferencesHelper.getSessionId(MainActivity.this);
if (sessionId != null && !sessionId.isEmpty()) {
if (Utils.isNetworkAvailable(MainActivity.this)) {
restHandler.getUnreadMessages(sessionId);
}
handler.postDelayed(this, Constants.CHAT_POLLING_REFRESH_DELAY);
} else {
handler.removeCallbacks(this);
}
}
});
}
Then onDestroy:
if (handler != null) {
handler.removeCallbacksAndMessages(null);
handler = null;
}
The backend is complaing that sometimes the sessionId passed is empty or null. How can this be possible? I'm using retrofit. Bonus question: can I have problems with MainActivity.this context reference when app goes in background?
This crash report only contains system trace, because it happens in the HandlerThread:
java.lang.RuntimeException: Only one Looper may be created per thread
at android.os.Looper.prepare(Looper.java:107)
at android.os.Looper.prepare(Looper.java:102)
at android.os.HandlerThread.run(HandlerThread.java:54)
Seems the looper's prepare is called already for the HandlerThread.
I just don't understand why the HandlerThread's run entering again. It only shows the system's java stacktrace.
I tried calling the HandlerThread's start() method multiple times, it throwed java.lang.IllegalThreadStateException, not the RuntimeException.
So when HandlerThread throws such exception?
========================================================
The key code about the HandlerThread and Handler is below:
class MyDeviceFragment extends BaseFragment {
private HandlerThread mWorkerThread = new HandlerThread(MyDeviceFragment .class.getName());
private WorkerHandler mWorkerHandler;
{
if (mWorkerHandler == null) {
mWorkerThread.start();
mWorkerHandler = new WorkerHandler(mWorkerThread.getLooper());
}
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// xxxxx code
}
public void onDetach() {
super.onDetach();
if (mWorkerThread != null) {
mWorkerThread.quit();
}
}
private class WorkerHandler extends Handler {
WorkerHandler(Looper looper) {
super(looper);
}
#Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_XXXXX1:
synchronized (mLock) {
// CODE
}
break;
case MSG_XXXXX2:
synchronized (mLock) {
// CODE
}
break;
}
}
}
}
And using mWorkerHandler.sendEmptyMessage(MSG_XXX) to send message.
The initialization of mWorkerThread is a little weird, written by third-party, but I don't think it could cause HandlerThread throwing the RuntimeException. Am i right?
I think we can extends HandlerThread
#Override
public void run() {
try {
super.run();
} catch (Exception e) {
LogUtil.e(TAG, "run e =" + e);
}
}
But I have no idea about reason.
I refactoring my threads in order to avoid memory leaks, and I got 2 errors regarding handler and startActivityForResult being called from within the thread ( dealing with GoogleDrive)
I have in my DownloadActivity :
public class DownloadActivity extends Activity {
....
private void getFolderId(){
getFolderIdThread = new GetFolderIdThread();
getFolderIdThread.start();
}
private static class GetFolderIdThread extends Thread {
private Boolean mRunning = false;
#Override
public void run() {
mRunning = true;
fResultList = new ArrayList<File>();
Files f1 = mService.files();
Files.List request = null;
aFolderId = null;
do {
try {
request = f1.list();
String aQuery = "'root' in parents and mimeType='application/vnd.google-apps.folder' and title='"+ aFolderName + "'";
request.setQ(aQuery);
FileList fileList = request.execute();
fResultList.addAll(fileList.getItems());
request.setPageToken(fileList.getNextPageToken());
} catch (UserRecoverableAuthIOException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION); <= THIS RAISES THE ERROR
} catch (IOException e) {
e.printStackTrace();
if (request != null){
request.setPageToken(null);
}
}
} while (request.getPageToken() !=null && request.getPageToken().length() > 0);
if (fResultList.size() == 0) {
Log.d(TAG, "cannot find the training folder at root level");
Message msg = handler.obtainMessage(); <= THIS RAISES THE ERROR
Bundle bundle = new Bundle();
bundle.putInt("msgKey", DownloadActivity.NO_TRAININGS_FOLDER);
msg.setData(bundle);
handler.sendMessage(msg); <= THIS RAISES THE ERROR
} else {
File folder = fResultList.get(0);
aFolderId = folder.getId();
getFolderContents(); <= THIS RAISES THE ERROR
}
}
public void close() {
mRunning = false;
}
}
I have the handler defined in my activity
Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
...
}
}
and the onActivityResult
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_ACCOUNT_PICKER:
....
break;
}
}
what are my options to bypass this error ?
Your GetFolderIdThread class is static and a static nested class cannot reference non-static methods and fields in the instance of the outer class that created it. Such a nested class can only access static methods and fields in your Activity. Remove static from the class definition and I think your problem will resolve.
You also need to post your call to startActivityForResult on the UI thread. Of course you should be able to use your handler for that, something like:
handler.post(new Runnable()
{
public void run()
{
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
}
});
Make sure your thread can gracefully complete as well when you do that because it will continue to run.
I am doing an Android service that gives content to other apps that can register as callback.
I am not 100% sure about how the Android Handler class works, so can someone confirm me that this code is thread safe?
public class MyService extends Service {
private static final String MESSAGE = "message";
private final RemoteCallbackList<IMyCallback> readerCallbacks = new RemoteCallbackList<IMyCallback>();
private static final int REPORT_MSG = 1;
private Thread readerThread;
#Override
public void onCreate() {
readerThread = new Thread(readerRunnable);
readerThread.setDaemon(true);
readerThread.start();
}
private Runnable readerRunnable = new Runnable() {
#Override
public void run() {
while (!Thread.interrupted()) {
// Blocking call
byte[] message = JniCommunicator.readMessage();
if (message == null || message.length == 0) {
continue;
}
Bundle b = new Bundle();
b.putByteArray(MESSAGE, message);
Message m = readHandler.obtainMessage(REPORT_MSG);
m.setData(b);
readHandler.sendMessage(m);
}
}
};
private final Handler readHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REPORT_MSG:
byte[] message = msg.getData().getByteArray(MESSAGE);
// Broadcast the new message to all clients
final int N = readerCallbacks.beginBroadcast();
for (int i = 0; i < N; i++) {
try {
readerCallbacks.getBroadcastItem(i).newMessage(message);
} catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
readerCallbacks.finishBroadcast();
break;
}
}
};
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IService.Stub mBinder = new IService.Stub() {
public void registerCallback(IMyCallback cb) {
if (cb != null)
readerCallbacks.register(cb);
}
public void unregisterCallback(IMyCallback cb) {
if (cb != null)
readerCallbacks.unregister(cb);
}
};
}
In particular, if someone calls unregisterCallback() while the Handler is in the for loop, will it crash?
From my understanding, the Handler run in the same thread, so it is thread safe, but I am not sure.
Thanks
Handlers are thread safe, that is their entire purpose.
I'll agree that the documentation on the thread safety of handlers isn't the best but it would be very ironic if a class designed to communicate between thread weren't thread safe.
About the remote callbacks, they are also designed to be thread safe, you should read the documentation on this, it states clearly:
Performs locking of the underlying list of interfaces to deal with multithreaded incoming calls, and a thread-safe way to iterate over a snapshot of the list without holding its lock
All you have to make sure is that all variables multiple thread access are thread safe (which they are in your case) and that they aren't being changed (yours are final so no worries there either)
I have a thread using handler and messages to send data to the activity. Everything work fine except when the activity is paused :
null sending message to a Handler on a dead thread
java.lang.RuntimeException: null sending message to a Handler on a dead thread
at android.os.MessageQueue.enqueueMessage(MessageQueue.java:196)
at android.os.Looper.quit(Looper.java:173)
at pocket.net.ComD.stopConnection(ComD.java:154)
at pocket.net.ComD.finalize(ComD.java:184)
at dalvik.system.NativeStart.run(Native Method)
In my activity , i have the following code which lets me close all the network connection opened by the thread :
public void onPause()
{
if(this.myThread != null) {
this.myThread.stopConnection();
}
}
In my Thread :
public void run()
{
this.setName("MessagesThread");
if(this.initSocket())
{
Looper.prepare();
this.threadHandler = initHandler();
Looper.loop();
}
else
{
this.timeout();
}
}
public void stopConnection()
{
if(this.threadHandler != null) {
this.threadHandler.removeMessages(ALIVE); // Remove a delayed message
this.threadHandler.getLooper().quit(); // Warning
}
this.connected = false;
if(this.client != null) {
this.client.close();
}
}
private Handler initHandler()
{
return new Handler() {
public void handleMessage(Message msg)
{
switch(msg.what)
{
//Handling messages
}
}
}
}
When i receive the warning "null sending message to a Handler on a dead thread" is that the activity trying to send a message to the thread or the oppposite ?
How can i fix this ?
Thanks
You are getting the error as Looper.quit() has already been called.
So the message queue is basically unusable after Looper.quit() has been called the first time, as it enqueues a Message with a null target, which is the magical identifier for the message queue to stop enqueuing and appear "dead".
You need to do something like:
private boolean stoppedFlag= false;
public void stopConnection()
{
if(this.threadHandler != null) {
this.threadHandler.removeMessages(ALIVE); // Remove a delayed message
if(!stoppedFlag){
this.threadHandler.getLooper().quit(); // Warning
stopFlag = true;
}
}
this.connected = false;
if(this.client != null) {
this.client.close();
}
}
To stop quit() being called multiple times
Ref Looper
Ref Looper SOQ