源代码在文末给出~ 如有疑问可以qq我哟:517486222
这两天复习安卓四大组件,想起上个星期做的音乐播放器并不是很好,所有又拿这个项目练起了手。上次的项目中,我是通过用static静态量来保存当前播放的歌曲状态的,这样子虽然可以实现功能,但是从设计上来说并不是很好,容易导致APP崩溃和手机发烫~(我不确定是不是static的锅,但是面向对象编程就得少用static ? )
那我这个版本的音乐播放器实现原理是啥呢,请听我细细道来~
首先说明一下,我的音乐列表页面叫做MainActivity,歌曲详情页面叫做DetailActivity,服务是MusicService。
我在MainActivity中要显示音乐列表,在刚进入APP时,只显示歌曲列表,但如果有歌曲正在播放,或者有歌曲播放后被暂停了,在列表中显示当前播放的歌曲是哪一首,也就是用不同的样式突显出正在后台被服务的歌曲。如下图所示(图一后台无正在被服务歌曲,图二是列表突显了正在播放的歌曲):
图一中显示音乐列表比较简单,过会直接贴代码。难点是图二中,如何获取当前正在播放的歌曲呢?
我的上一个版本是在MusicService中用static标记了当前歌曲,而这个版本我没用static了,而是通过绑定服务的方式来获取当前MusicService中是否有正在播放的歌曲的。
其中,与MusicService进行交互的Binder对象mBinder的值是在MyConnection类中得来的。
此处有个大坑???,不可以在
bindService(intent, mConnection, BIND_AUTO_CREATE);
后面直接写成mBinder = mConnection.binder
,因为绑定服务是异步的,如果在bindService方法调用后直接给mBinder赋值,则极有可能得到null,因为还没有完成绑定,此时IBinder的对象还没带过来。解决方法就是在ServiceConnection
子类的成功绑定回调方法onServiceConnected
中赋值,此时可以得到有效的IBinder值。
此外,还有一个需要注意的地方就是当我们打开APP从MainActivity进入DetailActivity播放歌曲,然后再从DetailActivity返回到MainActivity的时候,根据Activity的生命周期可知,此时应该是:
MainActivity::onCreate() -> MainActivity::onStart() -> MainActivity::onResume() ->MainActivity::onPause() -> DetailActivity::onCreate() -> DetailActivity::onStart() -> DetailActivity::onResume() -> MainActivity::onStop() -> DetailActivity::onPause() -> MainActivity::onRestart() -> MainActivity:: onStart() -> MainActivity::onResume() -> DetailActivity::onStop() -> DetailActivity::onDestory()
写得我手好酸,如有手误请见谅??
嘿嘿,我想表达的重点是第二次返回到MainActivity的时候,没有调用MainActivity::onCreate()
方法了,如果我们的initView()只在MainActivity::onCreate()
中调用,那从DetailActivity返回到MainActivity的时候,音乐列表将得不到更新,所以我们应该在MainActivity::onResume()
也要调用initView方法,如下所示:
package com.jal.www.cathy;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
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.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "JalLog::MainActivity";
private MyConnection mConnection;
private MusicService.MyBinder mBinder;
private List<Music> mMusicList;
private ListView mListView;
private Context mContext;
private int mPpositionOfPlaying;
class MyConnection implements ServiceConnection {
private static final String TAG = "LogMyConnection";
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
mBinder = (MusicService.MyBinder) service;
showListView();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected");
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i(TAG, "MainActivity :: onCreate()");
mContext = this;
requestPermission();
}
private void requestPermission() {
List<String> permissionList = new ArrayList<>();
if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
if (ContextCompat.checkSelfPermission(this,Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (!permissionList.isEmpty()){
ActivityCompat.requestPermissions(this,permissionList.toArray(new String[permissionList.size()]),1);
}else {
initView();
}
}
private void initView() {
mListView = findViewById(R.id.listView);
mMusicList = MusicList.getMusicList(this);
if (mBinder == null){//mBinder is null that is what bindService is not finish
bindMusicService();
}else{
showListView();
}
}
private void showListView() {
if ( mBinder == null || mBinder.isNullOfPlayer()){
mPpositionOfPlaying = -1;
} else {
mPpositionOfPlaying = mBinder.getPosition();
}
MusicAdapter adapter = new MusicAdapter(this, mMusicList, mPpositionOfPlaying);
mListView.setAdapter(adapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putInt("position", position);
intent.putExtras(bundle);
intent.setClass(MainActivity.this, DetailActivity.class);
startActivity(intent);
}
});
}
private void bindMusicService() {
Intent intent = new Intent();
intent.setClass(this, MusicService.class);
mConnection = new MyConnection();
bindService(intent, mConnection, BIND_AUTO_CREATE);
Log.i(TAG, "mBinder:"+ mBinder);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0){
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
if (grantResult == PackageManager.PERMISSION_DENIED){
String s = permissions[i];
Toast.makeText(this,s+getResources().getString(R.string.rejectPermission),Toast.LENGTH_SHORT).show();
}else{
initView();
}
}
}
break;
default:
break;
}
}
@Override
protected void onDestroy() {
Log.i(TAG, "MainActivity :: onDestroy()");
super.onDestroy();
}
@Override
protected void onStart() {
Log.i(TAG, "MainActivity :: onStart()");
super.onStart();
}
@Override
protected void onStop() {
Log.i(TAG, "MainActivity :: onStop()");
super.onStop();
}
@Override
protected void onPause() {
Log.i(TAG, "MainActivity :: onPause()");
super.onPause();
}
@Override
protected void onRestart() {
Log.i(TAG, "MainActivity :: onRestart()");
super.onRestart();
}
@Override
protected void onResume() {
Log.i(TAG, "MainActivity :: onResume()");
super.onResume();
initView();
}
}
DetailActivity页面功能比较简单,后期还会继续完善的。目前只有三个按钮和文字显示歌曲名。
代码也比较简单,主要就是先通过启动方式启动MusicService服务(启动方式为的是用户离开APP的时候音乐还可以在后台播放不被关闭),再通过绑定方式绑定MusicService服务(绑定方式是为了可以在这个页面用户可以跟服务交互,如暂停、播放、切换歌曲。)
package com.jal.www.cathy;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.util.List;
public class DetailActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "JalLog::DetailActivity";
private MyConnection mConnection;
private TextView mMusicTitle;
private List<Music> mMusicList;
private Button mBtnPre, mBtnPlayPause, mBtnNext;
private MusicService.MyBinder mBinder;
class MyConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
mBinder = (MusicService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected");
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
Log.i(TAG, "DetailActivity :: onCreate()");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
bindMusicService();
bindView();
}
private void bindMusicService() {
Intent intent = new Intent();
intent.setClass(this, MusicService.class);
intent.putExtras(getIntent().getExtras());
startService(intent);
mConnection = new MyConnection();
bindService(intent, mConnection, BIND_AUTO_CREATE);
Log.i(TAG, "mBinder:"+ mBinder);
}
private void bindView() {
mMusicList = MusicList.getMusicList(this);
mMusicTitle = findViewById(R.id.music_title);
mBtnPre = findViewById(R.id.btn_pre);
mBtnPlayPause = findViewById(R.id.btn_play_pause);
mBtnNext = findViewById(R.id.btn_next);
Bundle bundle = getIntent().getExtras();
String title = mMusicList.get(bundle.getInt("position")).getTitle();
mMusicTitle.setText(title);
mBtnPre.setOnClickListener(this);
mBtnPlayPause.setOnClickListener(this);
mBtnNext.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_pre:
mBinder.pre();
break;
case R.id.btn_play_pause:
mBinder.play_pause();
break;
case R.id.btn_next:
mBinder.next();
break;
}
}
@Override
protected void onDestroy() {
Log.i(TAG, "DetailActivity :: onDestroy()");
super.onDestroy();
}
@Override
protected void onStart() {
Log.i(TAG, "DetailActivity :: onStart()");
super.onStart();
}
@Override
protected void onStop() {
Log.i(TAG, "DetailActivity :: onStop()");
super.onStop();
}
@Override
protected void onPause() {
Log.i(TAG, "DetailActivity :: onPause()");
super.onPause();
}
@Override
protected void onRestart() {
Log.i(TAG, "DetailActivity :: onRestart()");
super.onRestart();
}
@Override
protected void onResume() {
Log.i(TAG, "DetailActivity :: onResume()");
super.onResume();
}
}
自定义了一个Binder类,用来作为onBinder方法的返回对象,给用户通过Binder对象来进行交互
package com.jal.www.cathy;
import android.app.Service;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import java.io.IOException;
import java.util.List;
public class MusicService extends Service {
private static final String TAG = "JalLog::MusicService";
private MediaPlayer mPlayer;
private Music mMusic;
private List<Music> mMusicList;
private int mPosition;
public MusicService() {
}
@Override
public void onCreate() {
Log.i(TAG, "MusicService :: onCreate()");
super.onCreate();
mMusicList = MusicList.getMusicList(getApplicationContext());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "MusicService :: onStartCommand()");
mPosition = intent.getExtras().getInt("mPosition");
playIndex(mPosition);
return super.onStartCommand(intent, flags, startId);
}
public class MyBinder extends Binder {
public boolean isPlaying(){
return mPlayer.isPlaying();
}
public boolean isNullOfPlayer(){
return mPlayer == null;
}
public void play_pause() {
if (mPlayer.isPlaying()) {
mPlayer.pause();
Log.i(TAG, "Play stop");
} else {
mPlayer.start();
Log.i(TAG, "Play start");
}
}
public void pre(){
mPosition = (mPosition - 1 + mMusicList.size()) % mMusicList.size();
playIndex(mPosition);
}
public void next(){
mPosition = (mPosition + 1) % mMusicList.size();
playIndex(mPosition);
}
public int getPosition(){
return mPosition;
}
//Returns the length of the mMusic in milliseconds
public int getDuration(){
return mPlayer.getDuration();
}
//Return the name of the mMusic
public String getName(){
return mMusic.getName();
}
//Returns the current progress of the mMusic in milliseconds
public int getCurrenPostion(){
return mPlayer.getCurrentPosition();
}
//Set the progress of mMusic playback in milliseconds
public void seekTo(int mesc){
mPlayer.seekTo(mesc);
}
}
private void playIndex(int position) {
if (null == mPlayer){
mPlayer = new MediaPlayer();
}
if (mMusic == mMusicList.get(position)){
return;// continue play this mMusic.
}
mPlayer.reset();
mMusic = mMusicList.get(position);
mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mPlayer.setDataSource(mMusic.getUrl());
mPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
}
mPlayer.start();
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "MusicService :: onBind()");
return new MyBinder();
}
@Override
public void onDestroy() {
Log.i(TAG, "MusicService :: onDestroy()");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "MusicService :: onUnbind()");
return super.onUnbind(intent);
}
@Override
public void onRebind(Intent intent) {
Log.i(TAG, "MusicService :: onUnbind()");
super.onRebind(intent);
}
}
https://www.github.com/2604150210/Cathy
https://download.csdn.net/download/jal517486222/11110188
不要问我为什么用sublime打开Android代码,因为截图时色彩好看~??? 我编程的时候肯定用的是AndroidStudio啦