在Android中,服务(Service)泄漏了IntentReceiver。

11
我正在制作一个安卓应用,我正在创建一个后台服务,当用户勾选 checkbox 时,该服务将一直运行,并在用户取消勾选时停止。所以当我勾选选框时它可以工作,也会显示“Service start”的Toast,但是当我取消勾选选框时,它会显示“Service stopped”的Toast,但不会停止播放声音,该声音在服务启动时开始播放。唯一停止声音的方法是关闭并重新启动我的手机。
以下是我用Java编写的启动和停止服务的代码:
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "Service Started", Toast.LENGTH_LONG).show();

    this.registerReceiver(this.batteryInfoReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

    return START_STICKY;
}
public void onDestroy() {
    super.onDestroy();
    Toast.makeText(this, "Service Stopped", Toast.LENGTH_LONG).show();
}

这是我的 XML 文件

<CheckBoxPreference
    android:title="Enable/Disable Alarm"
    android:defaultValue="true"
    android:key="cbAlarm"
    android:summary="Enable or disable alarm" />

这是我的logcat日志

05-06 09:13:53.230: D/dalvikvm(5351): GC_EXTERNAL_ALLOC freed 39K, 50% free  2725K/5379K, external 0K/0K, paused 124ms
05-06 09:13:53.355: D/dalvikvm(5351): GC_EXTERNAL_ALLOC freed 21K, 49% free 2779K/5379K, external 504K/518K, paused 18ms
05-06 09:13:53.420: D/CLIPBOARD(5351): Hide Clipboard dialog at Starting input: finished by someone else... !
05-06 09:13:57.975: I/MediaPlayer(5351): uri is:content://media/internal/audio/media/44
05-06 09:13:57.975: I/MediaPlayer(5351): inside  getAudioFilePath: content://media/internal/audio/media/44
05-06 09:13:57.980: I/MediaPlayer(5351): The actual path is:/system/media/audio/ringtones/S_A_cricket_chirps.ogg
05-06 09:13:57.980: I/MediaPlayer(5351): path is: /system/media/audio/ringtones/S_A_cricket_chirps.ogg
05-06 09:13:57.980: I/MediaPlayer(5351): file path found for DRM file:path is: /system/media/audio/ringtones/S_A_cricket_chirps.ogg
05-06 09:13:57.980: E/MediaPlayer-JNI(5351): setDataSource: outside path in JNI is ?x@
05-06 09:14:20.525: E/ActivityThread(5351): Service com.zafar.test.BatteryService has leaked IntentReceiver com.zafar.test.BatteryService$1@40536548 that was originally registered here. Are you missing a call to unregisterReceiver()?
05-06 09:14:20.525: E/ActivityThread(5351): android.app.IntentReceiverLeaked: Service com.zafar.test.BatteryService has leaked IntentReceiver com.zafar.test.BatteryService$1@40536548 that was originally registered here. Are you missing a call to unregisterReceiver()?
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.LoadedApk$ReceiverDispatcher.<init>(LoadedApk.java:756)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.LoadedApk.getReceiverDispatcher(LoadedApk.java:551)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:866)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.ContextImpl.registerReceiver(ContextImpl.java:853)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.ContextImpl.registerReceiver(ContextImpl.java:847)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:318)
05-06 09:14:20.525: E/ActivityThread(5351):     at com.zafar.test.BatteryService.onStartCommand(BatteryService.java:35)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:2043)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.ActivityThread.access$2800(ActivityThread.java:117)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:998)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.os.Handler.dispatchMessage(Handler.java:99)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.os.Looper.loop(Looper.java:130)
05-06 09:14:20.525: E/ActivityThread(5351):     at android.app.ActivityThread.main(ActivityThread.java:3691)
05-06 09:14:20.525: E/ActivityThread(5351):     at java.lang.reflect.Method.invokeNative(Native Method)
05-06 09:14:20.525: E/ActivityThread(5351):     at java.lang.reflect.Method.invoke(Method.java:507)
05-06 09:14:20.525: E/ActivityThread(5351):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:907)
05-06 09:14:20.525: E/ActivityThread(5351):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:665)
05-06 09:14:20.525: E/ActivityThread(5351):     at dalvik.system.NativeStart.main(Native Method)
05-06 09:14:37.810: D/CLIPBOARD(5351): Hide Clipboard dialog at Starting input: finished by someone else... !

请帮忙,我在哪里犯了错误。

编辑

private BroadcastReceiver batteryInfoReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
        int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);

        if(plugged == 2) {

                SharedPreferences getAlarms = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
                String alarms = getAlarms.getString("ringtone", "default ringtone");
                //getting uri from MediaStore via filepath i.e. content://media/internal/audio/media/29
                Uri uri = Uri.parse(alarms);

                mMediaPlayer = new MediaPlayer();
                try {
                    mMediaPlayer.setDataSource(context, uri);
                    final AudioManager audioManager = (AudioManager) context
                            .getSystemService(Context.AUDIO_SERVICE);
                    if (audioManager.getStreamVolume(AudioManager.STREAM_ALARM) != 0) {
                        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
                        mMediaPlayer.prepare();
                        mMediaPlayer.start();
                    }
                } catch (IOException e) {
                    //System.out.println("OOPS");
                    e.getStackTrace();
                }
        } else if(plugged == 0) {
            mMediaPlayer.stop();
        }

    }
};

1
在我的手机上,即使应用程序完全退出,播放的声音仍将继续播放。也许这只是 Android 的一个问题。您尝试从服务中停止声音了吗? - user377628
Hassan,我没有听懂你的意思,“你尝试过从服务中停止声音吗?” 你能详细解释一下吗? - Om3ga
我的意思是,在实际播放声音的代码中,你如何暂停/停止声音?你会暂停/停止它吗? - user377628
Hassan,请看我的更新,那里是我开始和停止声音的地方。 - Om3ga
有谁能解决我的问题吗? - Om3ga
抱歉,我在你发信息之前就已经睡觉了。不过allprog的回答很好! - user377628
3个回答

8
请确保在 onDestroy 完成之前调用 unregisterReceiver。在 onDestroy 之后,服务的上下文将无效,因此所有已注册的意图接收器都将停止操作。Android 使用“泄漏的意图接收器”异常警告您的应用程序行为不当。
音乐没有停止是因为接收器不再被调用。您应该确保在从 onDestroy 返回之前调用 mMediaPlayer.stop。我建议您将所有这些功能提取到单独的方法中,这样组合应用程序将更加容易。
private void startMusic(int level);
private void stopMusic();

此外,注意如果由于错误导致您连续收到两个插入 == 2 的意图,请小心不要泄漏mMusicPlayer。始终以防御性编码为佳,而不是依赖调用者的额外知识。
异常处理在您的代码中也非常危险。如果音乐启动失败,当您想停止音乐时可能会出现空指针异常。
希望这有所帮助。

谢谢allprog。这很有帮助,你提供了很好的信息。由于我是Android新手,所以我的代码不像专业人士的那样。你能告诉我在我的代码中哪里和如何注销我的接收器吗? - Om3ga
我在onDestroy()方法中使用了"this.unregisterReceiver(this.batteryInfoReceiver);",但它没有起作用。 - Om3ga
我认为这是Android最糟糕的部分。你需要使用的API非常模糊,而且你需要了解服务和广播接收器的内部工作原理,才能知道应该如何做。你尝试过在super.onDestroy()之前放置unregisterReceiver调用吗?你得到了什么错误? - allprog
我正在使用通过蓝牙发送意图的外部设备进行工作。即使我取消注册接收器,仍然会出现异常。这可能是因为在接收器被取消注册后,数据到达需要时间。你那边也可能是同样的情况。 - Omri374
@allprog 我遇到了相同的错误,并且在调用super之前我已经注销了。你们解决这个问题了吗? - hushed_voice
@free_style 我认为原帖的问题已经解决了。我建议你发布一个新的问题,因为你的问题可能是由其他原因引起的。不过,在继续之前,我建议你将意图接收器设置为“const”字段,以确保一旦初始化就不会丢失。然后检查事件序列,可能通过在方法中记录信息和使用调试器来实现。 - allprog

2

也许您错过了 @Override 注释

@Override
public void onDestroy() {
    unregisterReceiver(batteryInfoReceiver);
    super.onDestroy();
}

1

可能有点晚了,但似乎你需要在 onDestroy() 方法被调用之前立即调用 unregisterReceiver()。这解决了我的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接