如何防止媒体播放器在屏幕关闭时停止?

8
我有一个媒体播放器在一个名为Music的类中,该类从另一个次要的Activity中调用。它工作得很好。
但是当屏幕关闭(无论是超时还是按按钮)时,音乐停止播放,当回到并尝试关闭活动时,程序会进入“应用程序未响应”状态,因为查询时出现了IllegalStateException,如mediaplayer.isPlaying()
如何防止媒体播放器在屏幕关闭时停止播放?
它必须通过服务来完成吗?
假设答案是肯定的,我尝试将Music类转换为服务(见下文)。我还将<service android:enabled="true" android:name=".Music" />添加到Manifest.xml中,并像这样调用Music类:
startService(new Intent(getBaseContext(), Music.class));
Music track = Music(fileDescriptor);

在主 Activity 中,唯一新增的两行代码是 startService(new Intent(getBaseContext(), Music.class));stopService(new Intent(getBaseContext(), Music.class));,以及相应的导入。
但现在我在尝试启动服务时遇到了 InstantiationException 错误,因为无法实例化类。我漏掉了什么?
以下是异常信息:
E/AndroidRuntime(16642): FATAL EXCEPTION: main
E/AndroidRuntime(16642): java.lang.RuntimeException: Unable to instantiate service com.floritfoto.apps.ave.Music:                                                                             java.lang.InstantiationException: can't instantiate class com.floritfoto.apps.ave.Music; no empty constructor                                                                   
E/AndroidRuntime(16642):    at android.app.ActivityThread.handleCreateService(ActivityThread.java:2249)
E/AndroidRuntime(16642):    at android.app.ActivityThread.access$1600(ActivityThread.java:127)
E/AndroidRuntime(16642):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1213)
E/AndroidRuntime(16642):    at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime(16642):    at android.os.Looper.loop(Looper.java:137)
E/AndroidRuntime(16642):    at android.app.ActivityThread.main(ActivityThread.java:4507)
E/AndroidRuntime(16642):    at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(16642):    at java.lang.reflect.Method.invoke(Method.java:511)
E/AndroidRuntime(16642):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:980)
E/AndroidRuntime(16642):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:747)
E/AndroidRuntime(16642):    at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime(16642): Caused by: java.lang.InstantiationException: can't instantiate class com.floritfoto.apps.ave.Music; no empty constructor
E/AndroidRuntime(16642):    at java.lang.Class.newInstanceImpl(Native Method)
E/AndroidRuntime(16642):    at java.lang.Class.newInstance(Class.java:1319)
E/AndroidRuntime(16642):    at android.app.ActivityThread.handleCreateService(ActivityThread.java:2246)
E/AndroidRuntime(16642):    ... 10 more

这是Music.class:

package com.floritfoto.apps.ave;

import java.io.FileDescriptor;
import java.io.IOException;

import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.IBinder;
import android.widget.Toast;

public class Music extends Service implements OnCompletionListener{
    MediaPlayer mediaPlayer;
    boolean isPrepared = false;

    //// TEstes de servico
    @Override
    public void onCreate() {
        super.onCreate();
        info("Servico criado!");
    }
    @Override
    public void onDestroy() {
        info("Servico fudeu!");
    }
    @Override
    public void onStart(Intent intent, int startid) {
        info("Servico started!");
    }   
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    public void info(String txt) {
        Toast toast = Toast.makeText(getApplicationContext(), txt, Toast.LENGTH_LONG);
        toast.show();
    }
    //// Fim testes de servico

    public Music(FileDescriptor fileDescriptor){
        mediaPlayer = new MediaPlayer();
        try{
            mediaPlayer.setDataSource(fileDescriptor);
            mediaPlayer.prepare();
            isPrepared = true;
            mediaPlayer.setOnCompletionListener(this);
        } catch(Exception ex){
            throw new RuntimeException("Couldn't load music, uh oh!");
        }
    }

    public void onCompletion(MediaPlayer mediaPlayer) {
        synchronized(this){
            isPrepared = false;
        }
    }

    public void play() {
        if(mediaPlayer.isPlaying()) return;
        try{
            synchronized(this){
                if(!isPrepared){
                    mediaPlayer.prepare();
                }
                mediaPlayer.seekTo(0);
                mediaPlayer.start();
            }
        } catch(IllegalStateException ex){
            ex.printStackTrace();
        } catch(IOException ex){
            ex.printStackTrace();
        }
    }

    public void stop() {
        mediaPlayer.stop();
        synchronized(this){
            isPrepared = false;
        }
    }

    public void switchTracks(){
        mediaPlayer.seekTo(0);
        mediaPlayer.pause();
    }

    public void pause() {
        mediaPlayer.pause();
    }

    public boolean isPlaying() {
        return mediaPlayer.isPlaying();
    }

    public boolean isLooping() {
        return mediaPlayer.isLooping();
    }

    public void setLooping(boolean isLooping) {
        mediaPlayer.setLooping(isLooping);
    }

    public void setVolume(float volumeLeft, float volumeRight) {
        mediaPlayer.setVolume(volumeLeft, volumeRight);
    }

    public String getDuration() {
        return String.valueOf((int)(mediaPlayer.getDuration()/1000));
    }
    public void dispose() {
        if(mediaPlayer.isPlaying()){
            stop();
        }
        mediaPlayer.release();
    }
}

你有阅读如何使用服务的文档吗? http://developer.android.com/reference/android/app/Service.html - deefactorial
1
@deefactorial 当然不是!我只想实现有史以来最基本的媒体播放器!!我不想(也不需要)知道它所有的细节。我只是在尝试遵循这里的(著名的?)marakana示例http://marakana.com/forums/android/examples/60.html。我做错了什么... - Luis A. Florit
2个回答

6
这来自 Logcat 的一行是关键所在:
Caused by: java.lang.InstantiationException: can't instantiate class com.floritfoto.apps.ave.Music; no empty constructor

您的服务需要另一个不带参数的构造函数:

public Music() {
    super("Music");
}

编辑:

如果您想在屏幕关闭时保持音乐播放,则使用服务是正确的方法。然而,当屏幕关闭时,手机会尝试进入睡眠状态,这可能会中断您的MediaPlayer

最可靠的解决方案是使用部分唤醒锁定(partial WakeLock)来防止设备在播放音乐时进入休眠状态。确保在您不主动播放音乐时正确释放WakeLock,否则电池将耗尽。

您还可以使用startForeground(),这将减少您的服务在出现内存压力时被杀死的风险。它还将通过在您的服务运行时显示持久通知来创建良好的用户体验。

使用Music track = Music(fileDescriptor);实例化Music类可能会造成一些损害。更好的方法是将文件描述符作为Intent中的一个Extra传递给startService()

Intent serviceIntent = new Intent(this, Music.class);
serviceIntent.putExtra("ServiceFileDescriptor", fileDescriptor);
startService(serviceIntent);

接下来,在传递给您的服务的 onStartCommand() 方法时,从同一 Intent 中检索文件描述符:

public int onStartCommand(Intent intent, int flags, int startId) {
    super.onStart();

    Bundle bundle = intent.getExtras();
    // NOTE: The next line will vary depending on the data type for the file
    // descriptor. I'm assuming that it's an int.
    int fileDescriptor = bundle.getIntExtra("ServiceFileDescriptor");
    mediaPlayer = new MediaPlayer();
    try {
        mediaPlayer.setDataSource(fileDescriptor);
        ...
    ...
    return START_STICKY;
}

这里有几个需要注意的地方。我已经将代码从您原来的构造函数(应该被删除)移动到了onStartCommand()中。您也可以删除onStart()方法,因为它只会在早期版本的设备上调用。如果您想支持现代Android版本,您需要改用onStartCommand()。最后,START_STICKY返回值将确保服务一直运行,直到您从活动中调用stopService()编辑2: 使用服务可以使您的用户在不中断MediaPlayer的情况下在活动之间移动。您无法控制Activity停留在内存中的时间,但是一个活跃的Service(特别是如果您调用了startForeground())除非存在非常强的内存压力,否则不会被杀死。
启动服务后与MediaPlayer交互有几种选择。您可以通过创建Intent并使用操作字符串(和/或一些额外信息)告诉服务您想要执行什么操作,向服务传递附加命令。只需再次使用新的Intent调用startActivity(),服务中的onStartCommand()将被调用,在此时您可以操作MediaPlayer。第二个选择是使用绑定服务(示例在这里),每次进入/离开需要与服务通信的活动时进行绑定/解绑。使用绑定服务“感觉”就像直接操作服务,但它也更复杂,因为您需要管理绑定和解绑。

你的意思是要遵循public Music(FileDescriptor fileDescriptor){}这行代码吗?我试过了,但是出现了"The constructor Service(String) is undefined",并让我删除 "Music"。我已经这样做了,但仍然得到相同的InstantiationException错误。 :( - Luis A. Florit
也许服务并不是我解决这个问题所需要的,还有其他更简单的解决方案吗?请帮忙... - Luis A. Florit
或许问题在于当服务被创建时,mediaPlayer 没有被创建...? :o( - Luis A. Florit
2
一旦您使用 Intent 启动了服务,您就可以绑定到它并调用在您的“Service”上定义的任何公共方法。例如,您可以定义获取播放状态或当前歌曲的持续时间的方法。请查看我回答底部附近的示例链接。 - acj
我按照你的建议实现了一个绑定服务,一切都运作良好。如果有人需要实际代码,我可以发布它。非常感谢你的所有帮助和耐心!!!:o) - Luis A. Florit
显示剩余6条评论

4

作为一种选项,您可以保持屏幕激活以继续播放媒体。

 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

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