【问题标题】:How to make WakefulService (IntentService) wait until MediaPlayer finishes?如何让 WakefulService (IntentService) 等到 MediaPlayer 完成?
【发布时间】:2012-12-29 20:56:35
【问题描述】:

我正在尝试制作一个应用程序,该应用程序使用 MediaPlayer 在预定时间播放一系列声音。为了正确处理唤醒锁定并安排播放,我使用了CommonsWare's WakefulIntentService

不幸的是,IntentService 的工作线程在我调用MediaPlayer.play() 后立即退出,并且没有调用 MediaPlayer 注册的侦听器。相反,会记录异常:

W/MessageQueue(6727): Handler (android.media.MediaPlayer$EventHandler) {4160d820} sending message to a Handler on a dead thread
W/MessageQueue(6727): java.lang.RuntimeException: Handler (android.media.MediaPlayer$EventHandler) {4160d820} sending message to a Handler on a dead thread
W/MessageQueue(6727):   at android.os.MessageQueue.enqueueMessage(MessageQueue.java:294)
W/MessageQueue(6727):   at android.os.Handler.sendMessageAtTime(Handler.java:473)
W/MessageQueue(6727):   at android.os.Handler.sendMessageDelayed(Handler.java:446)
W/MessageQueue(6727):   at android.os.Handler.sendMessage(Handler.java:383)
W/MessageQueue(6727):   at android.media.MediaPlayer.postEventFromNative(MediaPlayer.java:2063)
W/MessageQueue(6727):   at dalvik.system.NativeStart.run(Native Method)

据我了解,这是由于 MediaPlayer 完成时工作线程已经死了。如果我通过调试器暂停线程并让播放器完成,一切正常。

在我的监听器中,我不仅释放 MediaPlayer 的资源,还使用 ​​OnCompletionListener 进行连续的MediaPlayer.play() 调用,直到声音队列为空。

我尝试在初始 play() 调用之后立即放置一个等待循环,检查自定义完成标志,但它似乎冻结了,因为 MediaPlayer 的回调是在调用 play() 的同一线程上调用的。

问题是,我怎样才能让工作线程在我让它退出之前不退出(即已经处理了并且最后一次调用了onCompletion() 方法?

这是我的服务代码:

public class SoundService extends WakefulIntentService {
    private static final TAG = "SoundService";
    private final Queue<SoundDescriptor> soundQueue = new ConcurrentLinkedQueue<SoundDescriptor>();

    private final OnCompletionListener onComediaPlayerletionListener = new OnComediaPlayerletionListener() {
        @Override
        public void onCompletion(MediaPlayer mediaPlayer) {
            mediaPlayer.reset();
            try {
                if (!playNextFromQueue(mediaPlayer)) {
                    Log.v(TAG, "Reached end of queue. Cleaning up.");
                    release();
                }
            } catch (Exception e) {
                Log.e(TAG, "Exception!", e)
                release();
            }
        }
    };
    private final OnErrorListener onErrorListener = new OnErrorListener() {
        @Override
        public boolean onError(MediaPlayer mediaPlayer, int what, int extra) {
            Log.v(TAG, "Error!!");
            release();
            return false;
        }
    };

    public SoundService() {
        // populate soundQueue
    }

    protected void doWakefulWork(Intent intent) {
        MediaPlayer mediaPlayer = new MediaPlayer();
        mediaPlayer.setOnComediaPlayerletionListener(onComediaPlayerletionListener);
        mediaPlayer.setOnErrorListener(onErrorListener);
        playNextFromQueue(mediaPlayer);
    }

    private boolean playNextFromQueue(MediaPlayer mediaPlayer) throws IllegalArgumentException, IllegalStateException, IOException {
        SoundDescriptor descriptor = soundQueue.poll();
        if (descriptor != null) {
            mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());
            descriptor.close();
            mediaPlayer.prepare();
            mediaPlayer.start();
            return true;
        } else {
            return false;
        }
    }
}

【问题讨论】:

    标签: android media-player intentservice


    【解决方案1】:

    为了正确处理唤醒锁定并安排播放,我使用了 CommonsWare 的 WakefulIntentService。

    这不是一个合适的选择。

    不幸的是,IntentService 的工作线程在我调用 MediaPlayer.play() 后立即退出,并且没有调用 MediaPlayer 注册的侦听器。

    这就是为什么它不是一个合适的选择。 :-)

    问题是,我怎样才能让工作线程在我让它退出之前不退出(即已经处理过并且最后一次调用了 onCompletion() 方法?

    不要使用WakefulIntentService。不要使用IntentService。使用Service。自己管理WakeLock,音频结束后在服务上调用stopSelf()

    【讨论】:

    • 谢谢,这似乎是一个正确的答案。我希望 WakefulIntentService 能帮助我解决 ICS 的问题,因为我的应用程序被系统杀死了,警报被取消注册。
    • @ohaleck:“由于我的应用程序被系统杀死,警报被取消注册”——在任何版本的 Android 上,警报都不会“因为我的应用程序被系统杀死而被取消注册”。如果用户从设置应用程序强制停止您的应用程序,警报“取消注册”。
    • 我的应用已经在 Google Play 上,并且我不断收到投诉说它在不同的时间段(几小时或几天)后停止工作。它始终在 Android 4.0+ 上运行,不涉及应用杀手或手动强制停止。
    • @ohaleck:“我不断收到投诉说它在不同时间段(数小时或数天)后停止工作”——从技术上讲,这并不意味着您的警报被取消注册。除非大多数用户都遇到这个问题,否则您的问题不是您的“应用程序被系统杀死”,因为您的进程将以某种频率终止。它不会在许多设备上持续数天。
    • @ohaleck:“有没有办法让 AlarmManager 的警报真正可靠?” -- AFAIK,它“非常可靠”。如果您感觉警报没有在您可以访问的某些设备上发生,请使用adb shell dumpsys alarm 转储未完成的警报并查看您的应用是否出现。
    【解决方案2】:

    MediaPlayer 的问题是它在实例化时缓存了一个 Handler 到当前线程。如果当前线程没有 Looper,则默认为主线程。它将使用此处理程序来执行侦听器回调。

    假设您实例化播放器并在服务线程中使用它。如果您通过服务调用播放器方法,然后该服务线程死亡,播放器将尝试使用指向已死亡线程的处理程序调用已注册侦听器的回调,并将抛出问题中显示的异常。

    我发现 MediaPlayer 非常棘手且不可靠。最简单的解决方案是在主线程中使用播放器。它们是空头跟注,不应加注 ANR。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-16
      • 2021-08-10
      • 1970-01-01
      • 2010-11-07
      • 2018-09-26
      相关资源
      最近更新 更多