Android學(xué)習(xí)整理 - 6 -Service
Android學(xué)習(xí)整理- 8 -MediaPlayer 放歌
為了實(shí)現(xiàn)后臺(tái)放歌觉鼻,可以轉(zhuǎn)移到Service中,具體的邏輯和在Activity中是差不多的
功能需求:
- 播放邻遏,暫停,停止
- 歌曲進(jìn)度條(SeekBar)
- 后臺(tái)
實(shí)現(xiàn)過程
下載4首歌曲改名a1,a2,a3,a4放進(jìn)sd卡目錄下Sounds文件夾
新建服務(wù)MediaService
private static final String TAG = "MediaService";
private MyBinder mBinder = new MyBinder();
//標(biāo)記當(dāng)前歌曲的序號(hào)
private int i = 0;
//歌曲路徑
private String[] musicPath = new String[]{
Environment.getExternalStorageDirectory() + "/Sounds/a1.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a2.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a3.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a4.mp3"
};
//這里要是路徑有問題,就加上getAbsolutePath(),像下面這樣
//Environment.getExternalStorageDirectory().getAbsolutePath() + "/Sounds/a1.mp3",
//初始化MediaPlayer
public MediaPlayer mMediaPlayer = new MediaPlayer();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class MyBinder extends Binder {
}
在MyBinder類里面添加邏輯功能暫停捉腥,播放氓拼,下一首你画,上一首,等
/**
* 播放音樂
*/
public void playMusic() {
if (!mMediaPlayer.isPlaying()) {
//如果還沒開始播放桃漾,就開始
mMediaPlayer.start();
}
}
/**
* 暫停播放
*/
public void pauseMusic() {
if (mMediaPlayer.isPlaying()) {
//如果還沒開始播放坏匪,就開始
mMediaPlayer.pause();
}
}
/**
* 下一首
*/
public void nextMusic() {
if (mMediaPlayer != null && i < 4 && i >= 0) {
//切換歌曲reset()很重要很重要很重要,沒有會(huì)報(bào)IllegalStateException
mMediaPlayer.reset();
iniMediaPlayerFile(i + 1);
//這里的if只要是為了不讓歌曲的序號(hào)越界撬统,因?yàn)橹挥?首歌
if (i == 2) {
} else {
i = i + 1;
}
playMusic();
}
}
/**
* 上一首
*/
public void preciousMusic() {
if (mMediaPlayer != null && i < 4 && i > 0) {
mMediaPlayer.reset();
iniMediaPlayerFile(i - 1);
if (i == 1) {
} else {
i = i - 1;
}
playMusic();
}
}
/**
* 關(guān)閉播放器
*/
public void closeMedia() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
}
}
注意
在下一首和上一首的功能里重新調(diào)用 setDataSource時(shí)适滓,要先reset再重新加載資源,不然會(huì)爆java.lang.IllegalStateException錯(cuò)誤恋追,
下面這段有點(diǎn)多余凭迹,也是MyBinder里面的,因?yàn)榭梢栽贏ctivity中直接獲取到MediaPlayer的對(duì)象而不用像下面這樣拐彎苦囱,嗯嗅绸,暫時(shí)先這樣寫了
/**
* 獲取歌曲長度
**/
public int getProgress() {
return mMediaPlayer.getDuration();
}
/**
* 獲取播放位置
*/
public int getPlayPosition() {
return mMediaPlayer.getCurrentPosition();
}
/**
* 播放指定位置
*/
public void seekToPositon(int msec) {
mMediaPlayer.seekTo(msec);
}
接著載服務(wù)中MediaService中
/**
* 添加file文件到MediaPlayer對(duì)象并且準(zhǔn)備播放音頻
*/
private void iniMediaPlayerFile(int dex) {
//獲取文件路徑
try {
//此處的兩個(gè)方法需要捕獲IO異常
//設(shè)置音頻文件到MediaPlayer對(duì)象中
mMediaPlayer.setDataSource(musicPath[dex]);
//讓MediaPlayer對(duì)象準(zhǔn)備
mMediaPlayer.prepare();
} catch (IOException e) {
Log.d(TAG, "設(shè)置資源,準(zhǔn)備階段出錯(cuò)");
e.printStackTrace();
}
}
加入權(quán)限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
注冊(cè)服務(wù)
<service android:name=".service.MediaService"/>
布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.minminaya.mediaservice.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/play"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="paly"/>
<Button
android:id="@+id/pause"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="pause"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/precious"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="precious"/>
<Button
android:id="@+id/next"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:text="next"/>
</LinearLayout>
<SeekBar
android:layout_marginTop="20dp"
android:id="@+id/seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<TextView
android:text="當(dāng)前進(jìn)度:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
Activity中撕彤,首先先實(shí)現(xiàn)播放的4大功能鱼鸠,聲明
private MediaService.MyBinder mMyBinder;
private Button playButton;
private Button pauseButton;
private Button nextButton;
private Button preciousButton;
//“綁定”服務(wù)的intent
Intent MediaServiceIntent;
服務(wù)與活動(dòng)的紐帶ServiceConnection
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMyBinder = (MediaService.MyBinder) service;
Log.d(TAG, "Service與Activity已連接");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
綁定id,這里所有view都綁了,button事件
private void iniView() {
playButton = (Button) findViewById(R.id.play);
pauseButton = (Button) findViewById(R.id.pause);
nextButton = (Button) findViewById(R.id.next);
preciousButton = (Button) findViewById(R.id.precious);
mSeekBar = (SeekBar) findViewById(R.id.seekbar);
mTextView = (TextView) findViewById(R.id.text1);
playButton.setOnClickListener(this);
pauseButton.setOnClickListener(this);
nextButton.setOnClickListener(this);
preciousButton.setOnClickListener(this);
}
button事件詳細(xì)
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.play:
mMyBinder.playMusic();
break;
case R.id.pause:
mMyBinder.pauseMusic();
break;
case R.id.next:
mMyBinder.nextMusic();
break;
case R.id.precious:
mMyBinder.preciousMusic();
break;
}
}
接下來是onCreate方法蚀狰,針對(duì)Android6.0以上的運(yùn)行時(shí)權(quán)限愉昆,動(dòng)態(tài)申請(qǐng)權(quán)限
iniView();
MediaServiceIntent = new Intent(this, MediaService.class);
//判斷權(quán)限夠不夠,不夠就給
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
} else {
//夠了綁定播放音樂的服務(wù)
bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
運(yùn)行時(shí)權(quán)限的回調(diào)麻蹋,在Activity中的
//獲取到權(quán)限回調(diào)方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[]permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
} else {
Toast.makeText(this, "權(quán)限不夠獲取不到音樂跛溉,程序?qū)⑼顺?, Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
onDestroy方法種添加
mMyBinder.closeMedia();
unbindService(mServiceConnection);
這個(gè)時(shí)候可以播放了,進(jìn)度條當(dāng)然沒有動(dòng)
接下來實(shí)現(xiàn)進(jìn)度條的功能
在Activity中實(shí)例化handler
private Handler mHandler = new Handler();
private SeekBar mSeekBar;
private TextView mTextView;
//進(jìn)度條下面的當(dāng)前進(jìn)度文字扮授,將毫秒化為m:ss格式
private SimpleDateFormat time = new SimpleDateFormat("m:ss");
在ServiceConnection中添加
mSeekBar.setMax(mMyBinder.getProgress());
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//這里很重要倒谷,如果不判斷是否來自用戶操作進(jìn)度條,會(huì)不斷執(zhí)行下面語句塊里面的邏輯糙箍,然后就會(huì)卡頓卡頓
if(fromUser){
mMyBinder.seekToPositon(seekBar.getProgress());
// mMediaService.mMediaPlayer.seekTo(seekBar.getProgress());
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
mHandler.post(mRunnable);
注意
- 這里的seekbar回調(diào)里渤愁,如果不判斷fromUser,播放會(huì)一直卡頓
還有一個(gè)runnable
/**
* 更新ui的runnable
*/
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
mSeekBar.setProgress(mMyBinder.getPlayPosition());
mTextView.setText(time.format(mMyBinder.getPlayPosition()) + "s");
mHandler.postDelayed(mRunnable, 1000);
}
};
onDestroy中 mMyBinder.closeMedia();前添加
//我們的handler發(fā)送是定時(shí)1000s發(fā)送的深夯,如果不關(guān)閉抖格,MediaPlayer release掉了還在獲取getCurrentPosition就會(huì)爆IllegalStateException錯(cuò)誤
mHandler.removeCallbacks(mRunnable);
注意關(guān)閉handle的隊(duì)列,不然咕晋,轉(zhuǎn)入后臺(tái)播放時(shí)程序崩潰
貼全部代碼環(huán)節(jié)
全部Activity代碼如下
package com.minminaya.mediaservice;
import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.format.Time;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import com.minminaya.mediaservice.service.MediaService;
import java.text.SimpleDateFormat;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Handler mHandler = new Handler();
private static final String TAG = "MainActivity";
private MediaService.MyBinder mMyBinder;
// private MediaService mMediaService;
private Button playButton;
private Button pauseButton;
private Button nextButton;
private Button preciousButton;
private SeekBar mSeekBar;
private TextView mTextView;
//進(jìn)度條下面的當(dāng)前進(jìn)度文字雹拄,將毫秒化為m:ss格式
private SimpleDateFormat time = new SimpleDateFormat("m:ss");
//“綁定”服務(wù)的intent
Intent MediaServiceIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iniView();
MediaServiceIntent = new Intent(this, MediaService.class);
//判斷權(quán)限夠不夠,不夠就給
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE
}, 1);
} else {
//夠了就設(shè)置路徑等掌呜,準(zhǔn)備播放
bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
}
}
//獲取到權(quán)限回調(diào)方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[]permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
bindService(MediaServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
} else {
Toast.makeText(this, "權(quán)限不夠獲取不到音樂滓玖,程序?qū)⑼顺?, Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
break;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMyBinder = (MediaService.MyBinder) service;
// mMediaService = ((MediaService.MyBinder) service).getInstance();
mSeekBar.setMax(mMyBinder.getProgress());
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//這里很重要,如果不判斷是否來自用戶操作進(jìn)度條质蕉,會(huì)不斷執(zhí)行下面語句塊里面的邏輯势篡,然后就會(huì)卡頓卡頓
if(fromUser){
mMyBinder.seekToPositon(seekBar.getProgress());
// mMediaService.mMediaPlayer.seekTo(seekBar.getProgress());
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
mHandler.post(mRunnable);
Log.d(TAG, "Service與Activity已連接");
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private void iniView() {
playButton = (Button) findViewById(R.id.play);
pauseButton = (Button) findViewById(R.id.pause);
nextButton = (Button) findViewById(R.id.next);
preciousButton = (Button) findViewById(R.id.precious);
mSeekBar = (SeekBar) findViewById(R.id.seekbar);
mTextView = (TextView) findViewById(R.id.text1);
playButton.setOnClickListener(this);
pauseButton.setOnClickListener(this);
nextButton.setOnClickListener(this);
preciousButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.play:
mMyBinder.playMusic();
break;
case R.id.pause:
mMyBinder.pauseMusic();
break;
case R.id.next:
mMyBinder.nextMusic();
break;
case R.id.precious:
mMyBinder.preciousMusic();
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//我們的handler發(fā)送是定時(shí)1000s發(fā)送的,如果不關(guān)閉模暗,MediaPlayer release掉了還在獲取getCurrentPosition就會(huì)爆IllegalStateException錯(cuò)誤
mHandler.removeCallbacks(mRunnable);
mMyBinder.closeMedia();
unbindService(mServiceConnection);
}
/**
* 更新ui的runnable
*/
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
mSeekBar.setProgress(mMyBinder.getPlayPosition());
mTextView.setText(time.format(mMyBinder.getPlayPosition()) + "s");
mHandler.postDelayed(mRunnable, 1000);
}
};
}
服務(wù)的
package com.minminaya.mediaservice.service;
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.IOException;
/**
* Created by NIWA on 2017/3/17.
*/
public class MediaService extends Service {
private static final String TAG = "MediaService";
private MyBinder mBinder = new MyBinder();
//標(biāo)記當(dāng)前歌曲的序號(hào)
private int i = 0;
//歌曲路徑
private String[] musicPath = new String[]{
Environment.getExternalStorageDirectory() + "/Sounds/a1.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a2.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a3.mp3",
Environment.getExternalStorageDirectory() + "/Sounds/a4.mp3"
};
//初始化MediaPlayer
public MediaPlayer mMediaPlayer = new MediaPlayer();
public MediaService() {
iniMediaPlayerFile(i);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class MyBinder extends Binder {
// /**
// * 獲取MediaService.this(方便在ServiceConnection中)
// *
// * *//*
// public MediaService getInstance() {
// return MediaService.this;
// }*/
/**
* 播放音樂
*/
public void playMusic() {
if (!mMediaPlayer.isPlaying()) {
//如果還沒開始播放禁悠,就開始
mMediaPlayer.start();
}
}
/**
* 暫停播放
*/
public void pauseMusic() {
if (mMediaPlayer.isPlaying()) {
//如果還沒開始播放,就開始
mMediaPlayer.pause();
}
}
/**
* reset
*/
public void resetMusic() {
if (!mMediaPlayer.isPlaying()) {
//如果還沒開始播放兑宇,就開始
mMediaPlayer.reset();
iniMediaPlayerFile(i);
}
}
/**
* 關(guān)閉播放器
*/
public void closeMedia() {
if (mMediaPlayer != null) {
mMediaPlayer.stop();
mMediaPlayer.release();
}
}
/**
* 下一首
*/
public void nextMusic() {
if (mMediaPlayer != null && i < 4 && i >= 0) {
//切換歌曲reset()很重要很重要很重要碍侦,沒有會(huì)報(bào)IllegalStateException
mMediaPlayer.reset();
iniMediaPlayerFile(i + 1);
//這里的if只要是為了不讓歌曲的序號(hào)越界,因?yàn)橹挥?首歌
if (i == 2) {
} else {
i = i + 1;
}
playMusic();
}
}
/**
* 上一首
*/
public void preciousMusic() {
if (mMediaPlayer != null && i < 4 && i > 0) {
mMediaPlayer.reset();
iniMediaPlayerFile(i - 1);
if (i == 1) {
} else {
i = i - 1;
}
playMusic();
}
}
/**
* 獲取歌曲長度
**/
public int getProgress() {
return mMediaPlayer.getDuration();
}
/**
* 獲取播放位置
*/
public int getPlayPosition() {
return mMediaPlayer.getCurrentPosition();
}
/**
* 播放指定位置
*/
public void seekToPositon(int msec) {
mMediaPlayer.seekTo(msec);
}
}
/**
* 添加file文件到MediaPlayer對(duì)象并且準(zhǔn)備播放音頻
*/
private void iniMediaPlayerFile(int dex) {
//獲取文件路徑
try {
//此處的兩個(gè)方法需要捕獲IO異常
//設(shè)置音頻文件到MediaPlayer對(duì)象中
mMediaPlayer.setDataSource(musicPath[dex]);
//讓MediaPlayer對(duì)象準(zhǔn)備
mMediaPlayer.prepare();
} catch (IOException e) {
Log.d(TAG, "設(shè)置資源隶糕,準(zhǔn)備階段出錯(cuò)");
e.printStackTrace();
}
}
}
Github源代碼:MediaService
高級(jí)播放器瓷产。。枚驻。人類用的
上面的播放是固有的幾個(gè)文件(連音頻文件名都訂好了濒旦,真坑)
有個(gè)博主,做的畢業(yè)項(xiàng)目测秸,是一系列文章疤估,感覺不錯(cuò)灾常,
Android開源音樂播放器之播放器基本功能
注意事項(xiàng),
幾個(gè)注意點(diǎn)在上面了