【问题标题】:Intermitent NullPointerException when using AlarmManager in Android在 Android 中使用 AlarmManager 时出现间歇性 NullPointerException
【发布时间】:2014-04-03 23:58:08
【问题描述】:

我开发了一个小型 Android 应用程序,它使用 AlarmManager 来调用处理程序线程,该线程将移动设备的一些信息与远程服务器同步。此过程每 15 分钟发生一次。

当我在没有 AlarmManager 帮助的情况下使用 HandlerThread 时,一切总是工作正常。但是,当我尝试将这两者结合在一起时,SOMETIMES 会起作用,SOMETIMES 会出现以下错误:

W/System.err(10557): java.lang.NullPointerException
W/System.err(10557):at Synchronizer.queueSyncEverything(Synchronizer.java:109)
W/System.err(10557): at SyncService.onHandleIntent(SyncService.java:33)
W/System.err(10557): at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
W/System.err(10557): at android.os.Handler.dispatchMessage(Handler.java:99)<BR/>
W/System.err(10557): at android.os.Looper.loop(Looper.java:137)
W/System.err(10557): at android.os.HandlerThread.run(HandlerThread.java:60)

代码非常简单,你可以在下面的 sn-ps 中看到:

//Method from SyncService, a class which extends IntentService    
protected void onHandleIntent(Intent intent) {
    ConnectivityManager cm = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
    if(cm.getActiveNetworkInfo() == null){
        return;
    }
    if(mSynchronizer == null){
        mSynchronizer = new Synchronizer(getApplicationContext(), this);
        mSynchronizer.start();
            mSynchronizer.getLooper();
    }       
    mSynchronizer.queueSyncEverything();

}

//Method from Synchronizer, a class which extends HandlerThread
public void queueSyncEverything(){
    try{
        mHandler.obtainMessage(MESSAGE_SYNC_EVERYTHING).sendToTarget();
    }
    catch(Exception e){
        mListener.onError();
        e.printStackTrace();
    }
}

我已经检查了Handler或Looper是否为null,但它们都很好。

编辑: 正如@DavidWasser 建议的那样,我进行了更多测试,发现有时 mHandler 为空。这个变量是在 Synchronizer(从 HandlerThread 扩展)的 onLooperPrepared 方法中设置的,你可以在这里看到:

@Override
protected void onLooperPrepared(){
    mHandler = new Handler(){

        @Override
        public void handleMessage(Message msg){

            if(msg.what == MESSAGE_SYNC_EVERYTHING){
                handleSyncEverything();
                mListener.onFinished();
            }
        }
    };
}

正如@DavidWasser 所问的,这个类的构造函数只是:

public Synchronizer(Synchronizer.Listener listener){
    super(TAG);
    mListener = listener;       
}

mListener 只是一个类似委托的对象,它接收 Synchronizer 类发送的事件,例如同步完成时。 TAG 只是一个便于调试的名称。也许我的主要困难是理解 HandlerThread 类是如何工作的,什么时候设置它的 Handler 对象合适。

P.S.:这种设置 HandlerThread 的方法是我通过阅读《Android 编程:大书呆子牧场指南》一书得到的。

【问题讨论】:

  • 我假设第 109 行是 mHandler.obtainMessage(MESSAGE_SYNC_EVERYTHING).sendToTarget();mHandler 为空。您在哪里将该变量设置为?还要贴出闹钟响起时调用的代码。
  • @DavidWasser,刚刚做了一些测试,是的,mHandler 为空。它设置在 Synchronizer (HandlerThread) 的 onLooperPrepared 方法中,您可以在此处看到:protected void onLooperPrepared(){ mHandler = new Handler(){ @Override public void handleMessage(Message msg){ if(msg.what == MESSAGE_SYNC_EVERYTHING){ handleSyncEverything(); mListener.onFinished(); } } }; } 据我所知,调用 mSynchronizer.getLooper() 时会调用此方法。闹钟响起时调用的方法太大了,只是对远程服务器的一些调用。
  • 发现很难遵循您的代码。请不要在 cmets 中添加代码 sn-ps。只需编辑您的问题并将代码添加到原始问题。 Synchronizer 来自什么?请显示该类的构造函数
  • 对不起,@David,这是我第一次在 StackOverflow 上发帖,所以我不习惯使用约定和文本编辑器的工作方式。我刚刚编辑了问题并添加了您问我的信息。谢谢!
  • 处理程序在 onLooperPrepared 中初始化,但您没有给它时间准备。你应该打电话给queueSyncEverythingonLooperPrepared

标签: android alarmmanager android-pendingintent android-handler


【解决方案1】:

不要在onLooperPrepared() 中初始化mHandler,因为此方法可能会在调用getLooper() 返回之后调用。这是一个小时间窗口,可能会咬你一口。

改为在Synchronizer 中创建一个方法,如下所示:

public void createHandler() {
    mHander = new Handler(getLooper()) {
        @Override
        public void handleMessage(Message msg){
            if(msg.what == MESSAGE_SYNC_EVERYTHING){
                handleSyncEverything();
                mListener.onFinished();
            }
        }
    };
}

并在onHandleIntent() 中调用此方法,而不是调用mSynchronizer.getLooper()

这将确保mHandler 在您调用queueSyncEverything()之前被初始化。

注意:您根本不需要覆盖 onLooperPrepared()

【讨论】:

  • 它就像一个魅力,谢谢。关于您的注释,我知道我不需要覆盖 onLooperPrepared,但是此方法的文档很清楚:“onLooperPrepared:如果需要在 Looper 循环之前执行某些设置,可以显式覆盖的回调方法。”所以,在我看来,onLooperPrepared 应该在 looper 开始处理它的队列之前完成。在我的问题中,发生了相反的情况。似乎我们在这里发现了一个错误。无论如何,我认为这是另一个问题的主题。这个被钉了。再次感谢您!
  • 在回答您的问题之前,我查看了HandlerThread 的源代码。 onLooperPrepared() is 在 Looper 循环之前调用。这意味着 Looper 已经设置好了。您在该方法中初始化mHandler。没事儿。你的问题是你可能在调用queueSyncEverything() 之前 onLooperPrepared() 被调用,因为这些事情在2个不同的线程中并行发生。我的代码只是重新安排了一些事情,以确保您不会调用 queueSyncEverything() 之前 mHandler 被初始化。
  • 我猜在 UI 线程中使用这个解决方案是不明智的,因为 getLooper() 会阻塞。 This other SO answer建议在处理程序准备好之前排队消息:stackoverflow.com/a/25459322/650894
  • 我发现这个博客解决了你描述的这个问题,除了阻塞getLooper()调用上的UI线程。所以它永远不会成为一个问题? blog.nikitaog.me/2014/10/11/…
  • @JoeLapp 是的,你是对的。在这种情况下,您可以(理论上)阻塞主(UI)线程。但是,设置Looper 并不是一项复杂或耗时的任务,因此不会阻塞很长时间。是的,每个人都这样做。
猜你喜欢
  • 2022-01-21
  • 2017-07-22
  • 1970-01-01
  • 2016-11-11
  • 1970-01-01
  • 1970-01-01
  • 2016-11-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多