跨多个活动的 Android 综合防故障音乐服务

问题描述:

我知道这个问题之前已经被问过很多次了,似乎是几个问题的集合,但我觉得它对许多开发者来说是相关且重要的;我需要创建一个背景音乐 Service,它可以在我的 Android 游戏的多个活动中运行,当应用程序终止并在以下所有情况下暂停时结束:

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:

  1. 某个具有自己音乐的 Activity 已启动.(在此 Activity 完成后继续.这恰好是一个 AndEngine 活动.)
  2. 按下主屏幕,应用程序进入后台,或应用程序终止.当应用程序返回前台时恢复.需要使用 onUserLeaveHint().另一个有用的链接.莉>
  3. 手机接到电话并中断了应用程序.呼叫处理完毕后恢复.需要使用类似于 TelephonyManager>这个.
  4. 屏幕被锁定.(屏幕解锁后恢复.)需要使用 ACTION_USER_PRESENT,这似乎 非常 有问题.
  5. 基本上,只要未显示应用或向用户显示 #1 中的特殊活动,音乐就会暂停.
  1. A certain Activity that has its own music is started. (Resume when this Activity finishes. This happens to be an AndEngine activity.)
  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(). 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.
  4. The screen is locked. (Resumes after screen has been unlocked.) Requires use of ACTION_USER_PRESENT, which seems to be very problematic.
  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.

以上是我需要的所有信息以及我拼凑的信息.我当前的代码基本上类似于这个.

Above is all of what I need and the information I have pieced together. My current code basically resembles this.

我觉得奇怪的是 AndEngine 设法在他们的音乐中没有这些问题,所以也许查看源代码会帮助寻找答案的人.我正在使用 Google 代码中的最后一个功能性 GLES1 版本.

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. I'm using the last functional GLES1 version from Google Code.

我还查看了以下有关创建优质音乐的链接Service:

I have taken a look at the following links as well on creating a good music Service:

我希望解决方案 Service:

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

其他注意事项

  • 目前所有需要背景音乐的活动都扩展了一个通用的特殊类.
  • 音乐需要循环播放,但只播放一首曲目.

提前感谢大家!祝你好运!

Thanks to everyone ahead of time! Best of luck!

编辑 - 以下是代码片段,请随时改进或忽略:

Edit - Here are code snippets, feel free to improve or ignore:

媒体播放器包装

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) {}
    }
}

音乐服务

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() {

    }
}

改进的基础活动类

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

}

活动:

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

首先你有一个在后台运行的服务.此服务会像您一样创建 mediaPlayer 对象.使用 localBinder,您可以在您的 Activity(ies) 中绑定服务并像普通 Java 对象一样访问它.我发布的活动绑定了服务.在 onCreate() 方法中,您可以找到一种如何与媒体播放器交互的方法.您可以将任何活动绑定到您的服务.

First of all you got a Service which runs in background. This service creates the mediaPlayer object as you did. With the localBinder you can bind the Service in your Activity(ies) and access it like a normal Java-Object. The Activity I've posted bindes the Service. In it's onCreate() method you can find a way how to interact with your mediaPlayer. You can bind any Activity to your Service.

另一种解决方案:

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) {}
}
}

然后你可以通过调用CarefulMediaPlayer.getInstance().play();来暂停、播放和停止音乐;

Then you can pause, play and stop the music by calling CarefulMediaPlayer.getInstance().play();