我把我最新的版本可以定时关闭,界面做了很大优化。GitHub上需要的可以下载:
https://github.com/DhyanaCoder/IMusic
————————————————————————————————————————
首先看一下我的项目结构:
MainActivity MusicPlayService ScanMusicUtil Song songApater timing timing timingstopService
文件。
- MainActivity是主活动文件
- MusicPlayService是运行Mediaplayer的服务文件
- ScanMusicUtil是搜索本地音乐的工具类
- Song是音乐的Java Bean类
- songAdpater是显示音乐列表的RecyclerView的适配器
- timing是设置定时的活动文件
- timingstopService是定时服务文件
首先分析 MainActivity 代码如下
public class MainActivity extends AppCompatActivity {
private DrawerLayout mDrawerLayout;
private songAdpater recyclerview_adapter;
private List<Song> mSongList=new ArrayList<>();
public static ImageButton play; //将Image设置为static以便音乐播放服务更改。
public ImageButton last;
public ImageButton next;
public static int state=0;//音乐播放器的状态 1为正在播放 0为处于暂停或者未初始化
public TextView showSongName;
public MusicPlayService.MusicServiceBinder musicServiceBinder;
private ServiceConnection connection=new ServiceConnection() {//1
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
musicServiceBinder=(MusicPlayService.MusicServiceBinder)service;
musicServiceBinder.InitBinder(mSongList);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//以下代码隐藏标题栏
ActionBar actionBar =getSupportActionBar();
if(actionBar!=null){
actionBar.hide();
}
final Intent intent=new Intent(this,MusicPlayService.class);
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("com.example.iMusic.UI_UPDATE");
MyBroadcast my=new MyBroadcast();
registerReceiver(my,intentFilter);
if( bindService(intent,connection,BIND_AUTO_CREATE)){
Log.d("bindservice","success");
}else{
Log.d("bindservice","failed");
}
startService(intent);
if(musicServiceBinder==null)
Log.d("testest!","mbinder is null");
showSongName=(TextView)findViewById(R.id.show_songname);
play=(ImageButton) findViewById(R.id.music_play);
last=(ImageButton) findViewById(R.id.music_left);
next=(ImageButton) findViewById(R.id.music_right);
last.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
musicServiceBinder.last();
}
});
next.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
musicServiceBinder.next();
}
});
play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(state==1){
musicServiceBinder.pause();
play.setBackgroundResource(R.drawable.music_play);
state=0;
}else{
play.setBackgroundResource(R.drawable.music_pause);
musicServiceBinder.play();
state=1;
}
}
});
mDrawerLayout=(DrawerLayout)findViewById(R.id.drawer_layout);
NavigationView navigationView=(NavigationView)findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.setting1);
navigationView.setCheckedItem(R.id.timing);
navigationView.setNavigationItemSelectedListener(new
NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.setting1:
mDrawerLayout.closeDrawers();
break;
case R.id.timing:
Intent timingIntent=new Intent(MainActivity.this,timing.class);
startActivity(timingIntent);
}
return true;
}
});
ImageButton imageButton=(ImageButton) findViewById(R.id.setting);
imageButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDrawerLayout.openDrawer(GravityCompat.START);
}
});
List<String> permissionList=new ArrayList<>();//构建权限申请表,以下就是逐步申请权限
if(ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}else{
mSongList=ScanMusicUtil.scanMusic(this);
}
if(!permissionList.isEmpty() ){
String [] permissions=permissionList.toArray(new String[permissionList.size()]);
ActivityCompat.requestPermissions(MainActivity.this,permissions,1);
}
recyclerview_adapter=new songAdpater(mSongList,this);
RecyclerView recyclerView_song=(RecyclerView)findViewById(R.id.recyclerview_song);
recyclerView_song.setAdapter(recyclerview_adapter);
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView_song.setLayoutManager(layoutManager);
}
public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
switch(requestCode){
case 1:
if(grantResults.length>0){
for(int result:grantResults){
if(result!=PackageManager.PERMISSION_GRANTED){
Toast.makeText(this,"必须同意所有权限才能使用本程序",Toast.LENGTH_SHORT).show();
finish();
return;
}
}
mSongList=ScanMusicUtil.scanMusic(this);
recyclerview_adapter.notifyDataSetChanged();
}else{
Toast.makeText(this,"发生未知错误",Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
private class MyBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent){
showSongName.setText( intent.getStringExtra("SongName"));
}
}
@Override
protected void onDestroy() {
super.onDestroy();
Intent i=new Intent(this,MusicPlayService.class);
stopService(i);
}
}
注释1处的代码构建了活动和服务通信的ServiceConnection。下面的代码是在绑定服务和活动初始知是就利用musicServiceBinder对服务进行一些初始化,这里我传入的是歌曲的list。
public void onServiceConnected(ComponentName name, IBinder service) {
musicServiceBinder=(MusicPlayService.MusicServiceBinder)service;
musicServiceBinder.InitBinder(mSongList);
}
下面这段代码我动态注册了一个广播用于服务去通知活动更新UI界面。
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction("com.example.iMusic.UI_UPDATE");
MyBroadcast my=new MyBroadcast();
registerReceiver(my,intentFilter);
下面就是我的广播类或许你会奇怪我为什么就更新了音乐名字这一条。 因为你在这份主活动代码的初始部分可以看到的一段注释,我把play这个按钮给静态化了。这样就可以在服务这个类里直接使用了,但这里却为何不适用相同的方法去更改歌曲名呢?其实我只是想用两种方法,在一个项目里去练习多种不同的东西。就我个人来看,用staitc这种方法有点奇怪,广播的方法会比较正规。
private class MyBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent){
showSongName.setText( intent.getStringExtra("SongName"));
}
}
下面是上一曲,下一曲,播放按键的点击事件。这里我写的就比较简单了因为大部分工作是放到服务里去对mediaplayer进行操作了。 在代码初始的变量声明我说明了state是个关于播放状态的记录值。1表明现在处于播放状态 0表示处于暂停状态/未初始化状态。这里play的点击事件主要是对state变量的值更改,还有对相应情况下play按钮的背景进行更改。
last.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
musicServiceBinder.last();
}
});
next.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
musicServiceBinder.next();
}
});
play.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(state==1){
musicServiceBinder.pause();
play.setBackgroundResource(R.drawable.music_play);
state=0;
}else{
play.setBackgroundResource(R.drawable.music_pause);
musicServiceBinder.play();
state=1;
}
}
});
下面这段代码是侧拉菜单的一些配置,各位不熟悉的话可以自行百度学习下,也不难,不过说实话,我的侧拉菜单点开其实很慢这是需要改进的地方。我的定时活动入口(timing)就放在了侧拉菜单里了。
mDrawerLayout=(DrawerLayout)findViewById(R.id.drawer_layout);
NavigationView navigationView=(NavigationView)findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.setting1);
navigationView.setCheckedItem(R.id.timing);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.setting1:
mDrawerLayout.closeDrawers();
break;
case R.id.timing:
Intent timingIntent=new Intent(MainActivity.this,timing.class);
startActivity(timingIntent);
}
return true;
}
});
其余中间就是一些权限申请,和recyclerview的一些配置相信各位必然看的懂了 。下面这段代码是是对停止服务的运行,避免服务在活动关闭还一直运行。
protected void onDestroy() {
super.onDestroy();
Intent i=new Intent(this,MusicPlayService.class);
stopService(i);
}
哦,还要说下面这段代码这段代码在确认了已经获得访问存储卡权限,对歌曲list进行了更新。
if(ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}else{
mSongList=ScanMusicUtil.scanMusic(this);
}
下面分析MusicService的代码,代码如下:
public class MusicPlayService extends Service {
private List<Song> mSongList=new ArrayList<>();
public static MediaPlayer mediaPlayer;
public MusicServiceBinder musicServiceBinder=new MusicServiceBinder();
private MainActivity activity;
private int SongPointer=-1;
private int i=0;
class MusicServiceBinder extends Binder {
public void InitMedia(Song music,int pointer){
try {
if(mediaPlayer.isPlaying()){
mediaPlayer.stop();
mediaPlayer.reset();
}else{
mediaPlayer.reset();
}
SongPointer=pointer;
Intent intent=new Intent("com.example.iMusic.UI_UPDATE");
intent.putExtra("SongName",music.getName());
sendBroadcast(intent);
mediaPlayer.setDataSource(music.getPath());
mediaPlayer.prepare();
}catch (Exception e){
e.printStackTrace();
}
}
public void InitBinder(List<Song> mSongList1){
Init(mSongList1);
}
public void start(){mediaPlayer.start();}
public void pause(){
mediaPlayer.pause();
}
public void stop(){
mediaPlayer.stop();
}
public void reset(){mediaPlayer.reset();}
public void setDataSource(String url){try{mediaPlayer.setDataSource(url);}catch (IOException e){e.printStackTrace();}}
public void prepare(){
try{
mediaPlayer.prepare();}
catch (IOException e){
e.printStackTrace();
}
}
public void last(){
if(SongPointer>0)
SongPointer--;
InitMedia(mSongList.get(SongPointer),SongPointer);
mediaPlayer.start();
}
public void next(){
if(SongPointer<mSongList.size()-1){
SongPointer++;
InitMedia(mSongList.get(SongPointer),SongPointer);
mediaPlayer.start();
}
}
public void play(){
if(SongPointer==-1){
InitMedia(mSongList.get(0),SongPointer);
SongPointer=0;}
try{
mediaPlayer.start();}catch (Exception e){
e.printStackTrace();
}
}
}
@Override
public IBinder onBind(Intent intent) {
return new MusicServiceBinder();
}
@Override
public void onCreate() {
super.onCreate();
Log.d("xxx","123");
mediaPlayer=new MediaPlayer();
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
if(SongPointer<mSongList.size()-1){
SongPointer++;
musicServiceBinder.InitMedia(mSongList.get(SongPointer),SongPointer);
mediaPlayer.start();
Log.d("Completion","Song"+SongPointer+" "+mSongList.get(SongPointer).getName() );
}
}
});
}
public MusicPlayService(MainActivity activity){
this.activity=activity;
}
@Override
public int onStartCommand(Intent intent,int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
public void Init(List<Song> mSongList1){
mSongList=mSongList1;
}
@Override
public void onDestroy() {
super.onDestroy();
mediaPlayer.release();
}
}
服务里的代码稍微复杂一些,没有接触过服务的朋友可能会看不懂。我先稍微说下结构:
class MusicServiceBinder 这个是服务和活动的通信类一些需要在活动里调用的方法可以写在这里。
public IBinder onBind 这个起的是绑定作用返回一个IBinder在本项目里就是MusiceService的实例咯。
public void onCreate 这个和活动的onCreate类似做一些UI和控件的初始化。
public int onStartCommand 这个方法我这里没写啥,这个和onCreate的区别是onCreate只在服务的创建时调用,而这个方法每次启动都会调用。
public void Init(List<Song> mSongList1) 这个是初始化服务的歌曲列表的方法供MusicServiceBinder类调用。
public void onDestroy() 销毁方法这里我释放了MediaPlayer的资源。
首先我们分析下onCreate里的东西,从这里开始会比较简单。代码如下,可以看到这里我创建了MediaPlayer的实例。然后设置了它的CompletionListener。这个接口是干嘛的呢。顾名思义,就是在MediaPlayer完成一首音乐的播放时会调用这里。在这里我让歌曲的指针自增,然后接着用MusicServiceBinder的实例的InitMedia()初始化一些下一首音乐的资源。最后让MediaPlayer的实例重新start起来。
public void onCreate() {
super.onCreate();
mediaPlayer=new MediaPlayer();
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
if(SongPointer<mSongList.size()-1){
SongPointer++;
musicServiceBinder.InitMedia(mSongList.get(SongPointer),SongPointer);
mediaPlayer.start();
}
}
});
}
下面开始class MusicServiceBinder的讲解,这部分比较复杂需要耐心看。首先看下刚才我们的讲到的InitMedia()(如下代码注释1处).这里首先判断了mediaPlayer是否在运行,如果在运行的话我们就让他停掉,并且重置它,如果不在运行,就直接重置它。然后让SongPointer=参数里的pointer。接着就发出广播啦,提醒我们的主活动去更新UI界面了。 mediaPlayer.setDataSource设置资源的方法,prepare就让mediaPlayer处于准备状态。
InitBinder()方法是初始化MusiceService里的歌曲list供活动调用的,这里有调用MusicService类的Init()方法去更新MusicService的歌曲list.
然后剩下的一些方法就是一些对meidaPlayer的上一曲,下一曲的操作了,不赘述了。
class MusicServiceBinder extends Binder {
public void InitMedia(Song music,int pointer){//1
try {
if(mediaPlayer.isPlaying()){
mediaPlayer.stop();
mediaPlayer.reset();
}else{
mediaPlayer.reset();
}
SongPointer=pointer;
Intent intent=new Intent("com.example.iMusic.UI_UPDATE");
intent.putExtra("SongName",music.getName());
sendBroadcast(intent);
mediaPlayer.setDataSource(music.getPath());
mediaPlayer.prepare();
}catch (Exception e){
e.printStackTrace();
}
}
public void InitBinder(List<Song> mSongList1){
Init(mSongList1);
}
public void start(){mediaPlayer.start();}
public void pause(){
mediaPlayer.pause();
}
public void stop(){
mediaPlayer.stop();
}
public void reset(){mediaPlayer.reset();}
public void setDataSource(String url){try{mediaPlayer.setDataSource(url);}catch (IOException e){e.printStackTrace();}}
public void prepare(){
try{
mediaPlayer.prepare();}
catch (IOException e){
e.printStackTrace();
}
}
public void last(){
if(SongPointer>0)
SongPointer--;
InitMedia(mSongList.get(SongPointer),SongPointer);
mediaPlayer.start();
}
public void next(){
if(SongPointer<mSongList.size()-1){
SongPointer++;
InitMedia(mSongList.get(SongPointer),SongPointer);
mediaPlayer.start();
}
}
public void play(){
if(SongPointer==-1){
InitMedia(mSongList.get(0),SongPointer);
SongPointer=0;}
try{
mediaPlayer.start();}catch (Exception e){
e.printStackTrace();
}
}
}
下面是timing活动代码如下:
这里就是利用AlarmManager 的一个定时服务了。
public class timing extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timing);
ActionBar actionBar =getSupportActionBar();
if(actionBar!=null)
{
actionBar.hide();
}
final EditText timingText=(EditText) findViewById(R.id.timingText);
Button finishButton=(Button)findViewById(R.id.timingFinishButton);
ImageButton backImgButton=(ImageButton) findViewById(R.id.back);
backImgButton.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
finish();
}
});
finishButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
long time=Long.parseLong(timingText.getText().toString());
long triggerAtTime = SystemClock.elapsedRealtime()+time*1000;
AlarmManager manager =(AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent i=new Intent(timing.this,timingstopService.class);
PendingIntent pi=PendingIntent.getService(timing.this,0,i,0);
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerAtTime,pi);
}
});
}
}
timingstopService的代码:
public class timingstopService extends Service {
public timingstopService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
MusicPlayService.mediaPlayer.pause();
MainActivity.state=0;
MainActivity.play.setBackgroundResource(R.drawable.music_play);
return super.onStartCommand(intent, flags, startId);
}
}
songAdpater代码:
public class songAdpater extends RecyclerView.Adapter<songAdpater.ViewHolder> {
private List<Song> mSongList;
private MainActivity activity;
static class ViewHolder extends RecyclerView.ViewHolder {
TextView songName;
TextView songAuthor;
LinearLayout linearLayout;
public ViewHolder(View v) {
super(v);
songName = (TextView) v.findViewById(R.id.song_name);
songAuthor = (TextView) v.findViewById(R.id.song_author);
linearLayout = (LinearLayout) v.findViewById(R.id.layout);
}
}
public songAdpater(List<Song> mSongList, MainActivity activity) {
this.mSongList = mSongList;
this.activity = activity;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.song_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
holder.linearLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getAdapterPosition();
Song song = mSongList.get(position);
activity.play.setBackgroundResource(R.drawable.music_pause);
activity.state = 1;
activity.musicServiceBinder.InitMedia(song, position);
activity.musicServiceBinder.start();
}
});
return holder;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Song song = mSongList.get(position);
if (song.getAuthor() != null)
holder.songAuthor.setText(song.getAuthor());
holder.songName.setText(song.getName());
}
@Override
public int getItemCount() {
return mSongList.size();
}
}
Song类代码:
public class Song {
private String name;
private String author;
private String path;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
ScanMusicUtil类代码
public class ScanMusicUtil {
public static ArrayList<Song> scanMusic(Context context){
ArrayList<Song> musicList= new ArrayList<Song>();
Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null,
null, MediaStore.Audio.AudioColumns.IS_MUSIC);
if(cursor!=null){
while(cursor.moveToNext()){
Song music=new Song();
music.setName(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)));
music.setPath(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)));
// music.setCoverId(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID)));
musicList.add(music);
Log.d("testest",music.getName()+" "+music.getPath());
}
}
cursor.close();
return musicList;
}
}