Как предотвратить остановку медиаплеера при выключении экрана?

У меня есть медиаплеер в классе 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);

Единственными двумя новыми строками в основном действии являются startService(new Intent(getBaseContext(), Music.class)); и stopService(new Intent(getBaseContext(), Music.class)); вместе с соответствующими импортами.

Но теперь я получаю ошибку InstantiationException, потому что can't instantiate class при попытке запустить службу. Что мне не хватает?

Это исключение:

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();
    }
}

person Luis A. Florit    schedule 27.10.2012    source источник
comment
Вы читали документацию о том, как использовать сервис? developer.android.com/reference/android/app/Service.html   -  person deefactorial    schedule 27.10.2012
comment
@deefactorial Конечно нет! Я просто хочу реализовать самый простой медиаплеер из когда-либо созданных! Я не хочу (и, надеюсь, не нужно) знать все ее тонкости. Я просто пытаюсь следовать (знаменитому?) примеру мараканы здесь marakana.com/forums/ android/examples/60.html. Что я делаю не так...   -  person Luis A. Florit    schedule 27.10.2012


Ответы (2)


Эта строка из Logcat является важной:

Caused by: java.lang.InstantiationException: can't instantiate class com.floritfoto.apps.ave.Music; no empty constructor

Вашему сервису нужен еще один конструктор, который не принимает аргументов:

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

ИЗМЕНИТЬ:

Использование службы — правильный подход, если вы хотите, чтобы музыка играла при выключенном экране. Однако телефон попытается перейти в спящий режим, когда экран выключен, и это может прервать ваше MediaPlayer.

Наиболее надежным решением является использование частичный WakeLock, чтобы устройство не переходило в спящий режим во время воспроизведения музыки. Убедитесь, что вы правильно отпустили WakeLock, когда вы не играете музыку активно; иначе батарея разрядится.

Вы также можете использовать startForeground(), что уменьшит риск того, что ваша служба будет убита при нехватке памяти. Это также создаст приятный пользовательский интерфейс, показывая постоянное уведомление, когда ваша служба работает.

Создание экземпляра класса Music с помощью Music track = Music(fileDescriptor);, вероятно, наносит некоторый вред. Лучший подход — передать файловый дескриптор как Extra в Intent, который вы передаете startService():

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

Затем извлеките дескриптор файла из того же Intent, когда он будет передан методу onStartCommand() вашей службы:

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(), поскольку он будет вызываться только на устройствах до версии 2.0. Если вы хотите поддерживать современные версии Android, вместо этого вам нужно использовать onStartCommand(). Наконец, возвращаемое значение START_STICKY гарантирует, что служба будет работать до тех пор, пока вы не вызовете stopService() из своей активности.

ИЗМЕНИТЬ 2:

Использование службы позволяет вашим пользователям переключаться между действиями, не прерывая MediaPlayer. У вас нет большого контроля над тем, как долго Activity будет оставаться в памяти, но активный Service (особенно если вы вызываете startForeground()) не будет уничтожен, если не будет очень сильного нехватки памяти.

Чтобы взаимодействовать с MediaPlayer после запуска службы, у вас есть несколько вариантов. Вы можете передать сервису дополнительные команды, создав Intents и используя строку действия (и/или некоторые дополнения), чтобы сообщить сервису, что вы хотите, чтобы он сделал. Просто снова вызовите startActivity() с новым Intent, и onStartCommand() будет вызываться в службе, после чего вы сможете манипулировать MediaPlayer. Второй вариант — использовать привязанную службу (пример здесь) и привязывать/отвязывать каждый раз, когда вы входите/выходите из действия которому необходимо связаться со службой. Использование связанной службы «ощущается» так, как если бы вы непосредственно манипулировали службой, но это также более сложно, поскольку вам нужно управлять привязкой и отвязкой.

person acj    schedule 27.10.2012
comment
Вы имеете в виду следование по линии public Music(FileDescriptor fileDescriptor){? Я сделал это и получил, что конструктор Service(String) не определен, и попросил меня удалить Music. Я так и сделал, и получил тот самый InstantiationException. :( - person Luis A. Florit; 27.10.2012
comment
Теперь я понял, что вы имели в виду (но мне пришлось удалить "Music" в скобках). Но затем у меня все еще возникает та же проблема: я вижу в логарифме, что медиаплеер отключается, когда экран выключен, и я получаю IllegalStateException при запросе mediaPlayer.isPlaying, например, если служба не выполнялась. :( - person Luis A. Florit; 27.10.2012
comment
Может быть, Сервисы - это не то, что мне нужно для решения этой проблемы, и есть какое-то другое гораздо более простое решение?? Пожалуйста помоги... - person Luis A. Florit; 27.10.2012
comment
Или, может быть, проблема в том, что медиаплеер не создается при создании службы...? :о( - person Luis A. Florit; 27.10.2012
comment
Да, это часть проблемы. Пожалуйста, смотрите мой обновленный ответ. - person acj; 27.10.2012
comment
Ммммм... начинаю понимать! Пытаясь реализовать свое решение, будьте (даже более) любезны ответить на два небольших вопроса: 1) Если проблема была в вейклоке, то зачем вообще пользоваться сервисами, т.е. почему бы просто не добавить вейклок в программу, которую я уже имел? 2) Как взаимодействовать с Music.mediaPlayer? Я пробовал как Music.mediaPlayer.getDuration(), так и Music.getDuration() и получил исключение нулевого указателя. Спасибо!! - person Luis A. Florit; 28.10.2012
comment
Понятно. Я реализую услугу, как вы предлагаете. Огромное спасибо за ваше время и доброту!!! - person Luis A. Florit; 29.10.2012
comment
Извините, что беспокою вас снова, но я все еще не понимаю, как взаимодействовать с сервером. Я запустил службу с Intent, пакетными дополнениями (для имени файла) и startService(). А вот например как узнать что играет плеер, или продолжительность песни? Спасибо! - person Luis A. Florit; 27.12.2012
comment
После того, как вы запустили службу с намерением, вы можете привязаться к ней и вызвать любые общедоступные методы, которые вы определили в своем Service. Например, вы можете определить методы, которые получают статус воспроизведения или продолжительность текущей песни. Пожалуйста, взгляните на ссылку примера в нижней части моего ответа. - person acj; 28.12.2012
comment
Я реализовал связанный сервис, как вы предложили, и все работает хорошо. Если кому-то нужен реальный код, я могу опубликовать его. Спасибо ОГРОМНОЕ за помощь и терпение!!!! :о) - person Luis A. Florit; 14.01.2013
comment
@ LuisA.Florit, да, пожалуйста, опубликуйте код. Спасибо! - person aye2m; 24.12.2017

как вариант, вы можете оставить экран активированным, чтобы MediaPlayer воспроизводил медиафайлы:

 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
person Jorgesys    schedule 28.03.2017