简体   繁体   English

跨多种活动的Android全面防故障音乐服务

[英]Android comprehensive failproof music service across multiple activities

I know this question has been asked many times before and might seem to be a conglomeration of several questions, but I feel that it is relevant and important to many developers; 我知道这个问题之前已被问过很多次,似乎是几个问题的集合,但我觉得这对很多开发人员来说都很重要; I need to create a background music Service that can run across multiple activities for my Android game that ends when the application is terminated and pauses in all of the following circumstances: 我需要创建一个背景音乐Service ,它可以运行我的Android游戏的多个活动,在应用程序终止时结束,并在以下所有情况下暂停:

  1. A certain Activity that has its own music is started. 已启动具有自己音乐的某个Activity (Resume when this Activity finishes. This happens to be an AndEngine activity.) (在此Activity完成时恢复。这恰好是AndEngine活动。)
  2. The home screen is pressed and the app is backgrounded, or the application is terminated. 按下主屏幕,应用程序背景,或终止应用程序。 Resumes when the app returns to the foreground. 应用程序返回前台时恢复。 Requires use of onUserLeaveHint() . 需要使用onUserLeaveHint() Another helpful link. 另一个有用的链接
  3. The phone receives a call and interrupts the app. 手机接到电话并中断应用程序。 Resumes when the call has been dealt with. 处理呼叫时恢复。 Requires use of TelephonyManager similar to this . 需要使用类似于TelephonyManager
  4. The screen is locked. 屏幕已锁定。 (Resumes after screen has been unlocked.) Requires use of ACTION_USER_PRESENT , which seems to be very problematic . (简历屏已经被解锁后)需要使用ACTION_USER_PRESENT ,这似乎 非常 有问题的
  5. Basically the music pauses whenever the app is not being shown or when the special activity from #1 is being shown to the user. 基本上,只要没有显示应用程序或者向用户显示来自#1的特殊活动,音乐就会暂停。

Above is all of what I need and the information I have pieced together. 以上是我需要的所有内容以及我拼凑在一起的信息。 My current code basically resembles this . 我目前的代码基本上类似于

I find it curious that AndEngine manages to have none of these issues with their music, so maybe looking in the source code would help someone looking for an answer. 我发现AndEngine设法在他们的音乐中没有AndEngine这些问题,这很奇怪,所以查看源代码可能有助于寻找答案的人。 I'm using the last functional GLES1 version from Google Code . 我正在使用Google Code的最新功能GLES1版本

I have taken a look at the following links as well on creating a good music Service : 我已经看了以下链接以及创建一个好的音乐Service

I would like the solution Service to: 我想解决方案Service

  • Minimize the use of BroadcastReceivers and Android Manifest additions/permissions if possible 尽可能减少BroadcastReceivers和Android Manifest添加/权限的使用
  • Self contained and error checking 自包含和错误检查

Other Notes 其他说明

  • Currently all the activities that require the background music all extend a common special class. 目前,所有需要背景音乐的活动都扩展了一个共同的特殊课程。
  • The music needs to loop but only runs a single track. 音乐需要循环,但只运行一个轨道。

Thanks to everyone ahead of time! 提前感谢大家! Best of luck! 祝你好运!

Edit - Here are code snippets, feel free to improve or ignore: 编辑 - 这是代码片段,随意改进或忽略:

Media Player Wrapper 媒体播放器包装

import android.content.SharedPreferences;
import android.media.MediaPlayer;
import android.preference.PreferenceManager;
import android.util.Log;

public class CarefulMediaPlayer {
    final SharedPreferences sp;
    final MediaPlayer mp;
    private boolean isPlaying = false;

    public CarefulMediaPlayer(final MediaPlayer mp, final MusicService ms) {
        sp = PreferenceManager.getDefaultSharedPreferences(ms.getApplicationContext());
        this.mp = mp;
    }

    public void start() {
        if (sp.getBoolean("com.embed.candy.music", true) && !isPlaying) {
            mp.start();
            isPlaying = true;
        }
    }

    public void pause() {
        if (isPlaying) {
            mp.pause();
            isPlaying = false;
        }
    }

    public void stop() {
        isPlaying = false;
        try {
            mp.stop();
            mp.release();
        } catch (final Exception e) {}
    }
}

Music Service 音乐服务

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

public class MusicService extends Service {
    static CarefulMediaPlayer mPlayer = null;

    @Override
    public IBinder onBind(final Intent arg0) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        final MediaPlayer mp = MediaPlayer.create(this, R.raw.title_music);
        mp.setLooping(true);
        mPlayer = new CarefulMediaPlayer(mp,this);
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags, final int startId) {
        mPlayer.start();
        return 1;
    }

    @Override
    public void onStart(final Intent intent, final int startId) {

    }

    public IBinder onUnBind(final Intent arg0) {
        return null;
    }

    public static void onStop() {
        mPlayer.stop();
    }

    public static void onPause() {
        if (mPlayer!=null) {
            mPlayer.pause();
        }
    }

    public static void onResume() {
        if (mPlayer!=null) {
            mPlayer.start();
        }
    }

    @Override
    public void onDestroy() {
        mPlayer.stop();
        mPlayer = null;
    }

    @Override
    public void onLowMemory() {

    }
}

Improved Base Activity Class 改进的基本活动类

import android.app.Activity;
import android.content.Intent;
import android.os.PowerManager;
import android.telephony.TelephonyManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;

public abstract class BetterActivity extends Activity {

    private boolean isHome = true;

    @Override
    protected void onResume() {
        System.gc();
        super.onResume();
        MusicService.onResume();
        isHome = true;
    }

    @Override
    protected void onPause() {
        if (((TelephonyManager)getSystemService(TELEPHONY_SERVICE)).getCallState()==TelephonyManager.CALL_STATE_RINGING
                || !((PowerManager)getSystemService(POWER_SERVICE)).isScreenOn()) {
            MusicService.onPause();
        }
        super.onPause();
        System.gc();
    }

    @Override
    public boolean onKeyDown (final int keyCode, final KeyEvent ke) {
        switch (keyCode) {
        case KeyEvent.KEYCODE_BACK:
            isHome = false;
        default:
            return super.onKeyDown(keyCode, ke);
        }
    }

    @Override
    public void startActivity(final Intent i) {
        isHome = false;
        super.startActivity(i);
    }

    @Override
    protected void onUserLeaveHint() {
        if (isHome) {
            MusicService.onPause();
        }
        super.onUserLeaveHint();
    }

}

First here is some code. 首先是一些代码。 Below I'll give you an explanation. 下面我会给你一个解释。

public class MusicService extends Service {

    // service binder
    private final IBinder mBinder = new LocalBinder();

    // music player controling game music
    private static CarefulMediaPlayer mPlayer = null;

    @Override
    public void onCreate() {
        // load music file and create player
        MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.title_music);
        mediaPlayer.setLooping(true);
        mPlayer = new CarefulMediaPlayer(mediaPlayer, this);
    }

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

    // =========================
    // Player methods
    // =========================
    public void musicStart() {
        mPlayer.start();
    }

    public void musicStop() {
        mPlayer.stop();
    }

    public void musicPause() {
        mPlayer.pause();
    }

    /**
     * Class for clients to access. Because we know this service always runs in
     * the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder {
        MusicService getService() {
            return MusicService.this;
        }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return mBinder;
    }

}

Activity: 活动:

public class StartupActivity extends Activity {

// bounded service
private static MusicService mBoundService;

// whetere service is bounded or not
private boolean mIsBound;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_startup);
    doBindService();

    // HOW TO WORK WITH THE SERVICE:
    // call the following methods whenever
    // you want to interact with you 
    // music player
    // ===================================

    // call this e.g. in onPause() of your Activities
    StartupActivity.getService().musicPause();

    // call this e.g. in onStop() of your Activities
    StartupActivity.getService().musicStop();

    // call this e.g. in onResume() of your Activities
    StartupActivity.getService().musicStart();
}

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

private final ServiceConnection mServiceConnection = new ServiceConnection() {

    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        setService(((MusicService.LocalBinder) service).getService());
    }

    @Override
    public void onServiceDisconnected(ComponentName className) {
        setService(null);
    }
};

private void doBindService() {
    Intent service = new Intent(getBaseContext(), MusicService.class);
    // start service and bound it
    startService(service);
    bindService(new Intent(this, MusicService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
    mIsBound = true;
}

private void doUnbindService() {
    if (mIsBound) {
        // Detach existing connection.
        unbindService(mServiceConnection);
        mIsBound = false;
    }
}

public static MusicService getService() {
    return mBoundService;
}

private static void setService(MusicService mBoundService) {
    StartupActivity.mBoundService = mBoundService;
}
}

First of all you got a Service which runs in background. 首先,你有一个在后台运行的服务。 This service creates the mediaPlayer object as you did. 此服务像您一样创建mediaPlayer对象。 With the localBinder you can bind the Service in your Activity(ies) and access it like a normal Java-Object. 使用localBinder,您可以在Activity中绑定Service,并像普通的Java-Object一样访问它。 The Activity I've posted bindes the Service. 我发布的活动bindes the Service。 In it's onCreate() method you can find a way how to interact with your mediaPlayer. 在它的onCreate()方法中,您可以找到一种如何与您的mediaPlayer进行交互的方法。 You can bind any Activity to your Service. 您可以将任何活动绑定到您的服务。

Another Solution: 另一种方案:

public class CarefulMediaPlayer {
final SharedPreferences sp;
final MediaPlayer mp;
private boolean isPlaying = false;
private static CarefulMediaPlayer instance;

public CarefulMediaPlayer(final MediaPlayer mp, final MusicService ms) {
    sp = PreferenceManager.getDefaultSharedPreferences(ms.getApplicationContext());
    this.mp = mp;
    instance = this;
}

public static CarefulMediaPlayer getInstance() {
    return instance;
}

public void start() {
    if (sp.getBoolean("com.embed.candy.music", true) && !isPlaying) {
        mp.start();
        isPlaying = true;
    }
}

public void pause() {
    if (isPlaying) {
        mp.pause();
        isPlaying = false;
    }
}

public void stop() {
    isPlaying = false;
    try {
        mp.stop();
        mp.release();
    } catch (final Exception e) {}
}
}

Then you can pause, play and stop the music by calling CarefulMediaPlayer.getInstance().play(); 然后你可以通过调用CarefulMediaPlayer.getInstance()来暂停,播放和停止音乐.play();

I did it this way and I'm pleased with the result: 我是这样做的,我很满意结果:

1st create the service: 1创建服务:

public class LocalService extends Service
{
    // This is the object that receives interactions from clients. See RemoteService for a more complete example.
    private final IBinder mBinder = new LocalBinder();
    private MediaPlayer player;

    /**
     * Class for clients to access. Because we know this service always runs in
     * the same process as its clients, we don't need to deal with IPC.
     */
    public class LocalBinder extends Binder
    {
        LocalService getService()
        {
            return LocalService.this;
        }
    }

    @Override
    public void onCreate()
    {

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId)
    {
        // We want this service to continue running until it is explicitly stopped, so return sticky.
        return START_STICKY;
    }

    @Override
    public void onDestroy()
    {
        destroy();
    }

    @Override
    public IBinder onBind(Intent intent)
    {
        return mBinder;
    }


    public void play(int res)
    {
        try
        {
            player = MediaPlayer.create(this, res);
            player.setLooping(true);
            player.setVolume(0.1f, 0.1f);
            player.start();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }


    public void pause()
    {
        if(null != player && player.isPlaying())
        {
            player.pause();
            player.seekTo(0);
        }
    }


    public void resume()
    {
        try
        {
            if(null != player && !player.isPlaying())
            {
                player.start();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }


    public void destroy()
    {
        if(null != player)
        {
            if(player.isPlaying())
            {
                player.stop();
            }

            player.release();
            player = null;
        }
    }

}

2nd , create a base activity and extend all your activities in witch you wish to play the background music from it: 2 ,创建一个基础活动并扩展您希望播放背景音乐的所有活动:

public class ActivityBase extends Activity
{
    private Context context = ActivityBase.this;
    private final int [] background_sound = { R.raw.azilum_2, R.raw.bg_sound_5 };
    private LocalService mBoundService;
    private boolean mIsBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        doBindService();
    }

    @Override
    protected void onStart()
    {
        super.onStart();

        try
        {
            if(null != mBoundService)
            {
                Random rand = new Random();
                int what = background_sound[rand.nextInt(background_sound.length)];
                mBoundService.play(what);
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    @Override
    protected void onStop()
    {
        super.onStop();
        basePause();
    }



    protected void baseResume()
    {
        try
        {
            if(null != mBoundService)
            {
                mBoundService.resume();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }


    protected void basePause()
    {
        try
        {
            if(null != mBoundService)
            {
                mBoundService.pause();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }



    private ServiceConnection mConnection = new ServiceConnection()
    {
        public void onServiceConnected(ComponentName className, IBinder service)
        {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service. Because we have bound to a explicit
            // service that we know is running in our own process, we can
            // cast its IBinder to a concrete class and directly access it.
            mBoundService = ((LocalService.LocalBinder) service).getService();

            if(null != mBoundService)
            {
                Random rand = new Random();
                int what = background_sound[rand.nextInt(background_sound.length)];
                mBoundService.play(what);
            }
        }

        public void onServiceDisconnected(ComponentName className)
        {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            // Because it is running in our same process, we should never
            // see this happen.
            mBoundService = null;

            if(null != mBoundService)
            {
                mBoundService.destroy();
            }
        }
    };

    private void doBindService()
    {
        // Establish a connection with the service. We use an explicit
        // class name because we want a specific service implementation that
        // we know will be running in our own process (and thus won't be
        // supporting component replacement by other applications).

        Intent i = new Intent(getApplicationContext(), LocalService.class);
        bindService(i, mConnection, Context.BIND_AUTO_CREATE);
        mIsBound = true;
    }

    private void doUnbindService()
    {
        if (mIsBound)
        {
            // Detach our existing connection.
            unbindService(mConnection);
            mIsBound = false;
        }
    }


    @Override
    protected void onDestroy()
    {
        super.onDestroy();
        doUnbindService();
    }
}

And that's it, now you have background sound in all the activities that are extended from ActivityBase. 就是这样,现在您在从ActivityBase扩展的所有活动中都有背景声音。

You can even control the pause / resume functionality by calling basePause() / baseResume(). 您甚至可以通过调用basePause()/ baseResume()来控制暂停/恢复功能。

Don't forget to declare the service in manifest: 不要忘记在清单中声明服务:

<service android:name="com.gga.screaming.speech.LocalService" />

In the startup activity we are binding and Starting Service seperately. 在启动活动中,我们分别绑定和启动服务。 This is wrong since service will keep running after activity exits as we haven't called stopService() anywhere. 这是错误的,因为服务将在活动退出后继续运行,因为我们没有在任何地方调用stopService() So The part ' startService(service) ' should be removed as bind service is already " Auto-Creating " the service too. 所以应该删除部分'startService(service)',因为绑定服务已经“ 自动创建 ”了服务。

Please correct me if anyone got opposite results 如果有人得到相反的结果,请纠正我

startService(service);// remove this part
bindService(new Intent(this, MusicService.class), mServiceConnection, Context.BIND_AUTO_CREATE);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM