Android实战:利用service实现简单的音乐播放器

目录

    • 新建工程(Android Studio)
    • 添加、编辑资源文件
    • 添加、编辑布局文件
    • 添加音乐信息类
    • 添加MusicUtils类
    • 添加ListView适配器类
    • 添加接口MyBinderInterface
    • MusicService服务类
    • 编辑MainActivity.java文件
    • 测试、运行

新建工程(Android Studio)

略。

添加、编辑资源文件

  1. 编辑res->values->strings.xml文件,内容如下:

    MusicBox
    本地音乐
    关于
    退出
    确认
    取消
    警告
    文件详情
    开始播放
    歌曲已经在播放了

  1. AndroidManifest.xml文件中添加读取外部储存卡的权限:

  1. 添加menu资源目录,并在其中新建一个menu_item.xml文件,作为选项菜单的布局文件。


    
    

  1. 更改程序图标。(此过程简单,略)

添加、编辑布局文件

  1. 编辑activity_main.xml(主界面布局文件),内容如下:



    

        
        

    

    
        
        
            
            
                
                
                
                
                
            
        
    

  1. 添加item_layout.xml作为ListView每个item的布局文件:



    
    
        
        
    

添加音乐信息类

在工程中新建一个类MusicInfo用于记录音乐文件的信息:

package com.zys.musicbox;

public class MusicInfo {
    private String music_title;
    private String music_name;
    private String music_path;
    private String music_artist;
    private int music_duration;
	//getter and setter
}

添加MusicUtils类

在工程中新建一个类MusicUtils用于操作音乐文件信息(此例中只有一个方法,利用ContentResolver类读取音乐信息):

package com.zys.musicbox;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.provider.MediaStore;
import java.util.ArrayList;
import java.util.List;

public class MusicUtils {
	/*
	* 用于获取本地Music目录下的所有音乐信息,并封装成List后返回
	* 需要一个Context对象
	* */
    public static List ResolveMusicToList(Context context){
        String selection = MediaStore.Audio.Media.IS_MUSIC + "!=0";
        String sortOrder = MediaStore.MediaColumns.DISPLAY_NAME+"";
        List musicList = new ArrayList();

        String[] projection = {
                MediaStore.Audio.Media.TITLE,
                MediaStore.Audio.Media.ARTIST,
                MediaStore.Audio.Media.DISPLAY_NAME,
                MediaStore.Audio.Media.DATA,
                MediaStore.Audio.Media.DURATION
        };
        ContentResolver contentResolver = context.getContentResolver();
        Cursor cursor = contentResolver.query(
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                projection,selection,null,sortOrder);

        if (cursor != null){
            for (cursor.moveToFirst(); cursor.isAfterLast() != true; cursor.moveToNext()){
                MusicInfo musicInfo = new MusicInfo();
                musicInfo.setMusic_title(cursor.getString(0));
                musicInfo.setMusic_artist(cursor.getString(1));
                musicInfo.setMusic_name(cursor.getString(2));
                musicInfo.setMusic_path(cursor.getString(3));
                musicInfo.setMusic_duration(Integer.parseInt(cursor.getString(4)));

                musicList.add(musicInfo);
            }
        }
        return musicList;
    }
}

添加ListView适配器类

在工程中新建一个类ListAdapter作为主界面中ListView的适配器类。

package com.zys.musicbox;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;

class ViewHolder{
    public ImageView itemIcon;
    public TextView itemMusicName;
    public TextView itemMusicSinger;
    public int defaultTextColor;

    View itemView;

    public ViewHolder(View itemView) {
        if (itemView == null){
            throw new IllegalArgumentException("item View can not be null!");
        }
        this.itemView = itemView;
        itemIcon = itemView.findViewById(R.id.rand_icon);
        itemMusicName = itemView.findViewById(R.id.item_music_name);
        itemMusicSinger = itemView.findViewById(R.id.item_music_singer);

        defaultTextColor = itemMusicName.getCurrentTextColor();
    }
}

public class ListAdapter extends BaseAdapter {
    private List musicList;
    private LayoutInflater layoutInflater;
    private Context context;
    private int currentPos = -1;
    private ViewHolder holder = null;

    public ListAdapter(Context context,List musicList) {
        this.musicList = musicList;
        this.context = context;
        layoutInflater = LayoutInflater.from(context);
    }

    public void setFocusItemPos(int pos){
        currentPos = pos;
        notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return musicList.size();
    }

    @Override
    public Object getItem(int position) {
        return musicList.get(position).getMusic_title();
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    public void remove(int index){
        musicList.remove(index);
    }

    public void refreshDataSet(){
        notifyDataSetChanged();
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null){
            convertView = layoutInflater.inflate(R.layout.item_layout,null);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        }
        else {
            holder = (ViewHolder)convertView.getTag();
        }
        //如果是正在播放的音乐 就改变图片、字体颜色
        if (position == currentPos){
            holder.itemIcon.setImageBitmap(BitmapFactory.decodeResource(
                    context.getResources(),R.drawable.arrow));
            holder.itemMusicName.setTextColor(Color.RED);
            holder.itemMusicSinger.setTextColor(Color.RED);
        }
        //否则使用默认图片、字体颜色
        else{
            holder.itemIcon.setImageBitmap(BitmapFactory.decodeResource(
                    context.getResources(),R.drawable.music));
            holder.itemMusicName.setTextColor(holder.defaultTextColor);
            holder.itemMusicSinger.setTextColor(holder.defaultTextColor);
        }
        holder.itemMusicName.setText(musicList.get(position).getMusic_title());
        holder.itemMusicSinger.setText(musicList.get(position).getMusic_artist());
        return convertView;
    }
}

添加接口MyBinderInterface

在工程中新建一个接口MyBinderInterface作为调用Service中方法的媒介、中间人:

package com.zys.musicbox;

public interface MyBinderInterface {
    //暂停
    void Pause();
    //恢复
    void Resume();
    //播放
    void Play();
    //播放下一首
    void PlayNext();
    //播放上一首
    void PlayPrev();
    //释放
    void Release();
    //是否正在播
    boolean isPlaying();
    //获取时长
    int getDuration();
    //当前位置
    int getCurrentPosition();
    //拖动位置
    void seekTo(int length);
    //获取当前索引
    int getCurrentIndex();
    //设置当前索引
    void setCurrentIndex(int currentIdx);
}

MusicService服务类

package com.zys.musicbox;

import android.app.Service;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;

import java.io.IOException;
import java.util.List;

public class MusicService extends Service {

    private MediaPlayer mPlayer;
    private int seekLength = 0;
    private int currentIndex = -1;

    private List musicList;

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

    @Override
    public void onCreate() {
        super.onCreate();
        InitPlayer();
        //通过工具类MusicUtils获取音乐信息列表
        musicList = MusicUtils.ResolveMusicToList(getApplicationContext());
    }

    private void InitPlayer() {
        mPlayer = new MediaPlayer();
        mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    }

    private class MyBinder extends Binder implements MyBinderInterface{

        @Override
        public void Pause() {
            if (mPlayer.isPlaying()){
                mPlayer.pause();
                seekLength = mPlayer.getCurrentPosition();
            }
        }

        @Override
        public void Resume() {
            mPlayer.seekTo(seekLength);
            mPlayer.start();
        }

        @Override
        public void Play() {
            mPlayer.reset();
            Uri path = Uri.parse(musicList.get(currentIndex).getMusic_path());

            try {
                mPlayer.setDataSource(String.valueOf(path));
                mPlayer.prepare();
            } catch (IOException e) {
                e.printStackTrace();
        }
            mPlayer.seekTo(seekLength);
            mPlayer.start();
        }

        @Override
        public void PlayNext() {
            currentIndex += 1;
            if (currentIndex >= musicList.size()){
                currentIndex = 0;
            }
            seekLength = 0;
            Play();
        }

        @Override
        public void PlayPrev() {
            currentIndex -= 1;
            if (currentIndex <= 0){
                currentIndex = musicList.size() - 1;
            }
            seekLength = 0;
            Play();
        }

        @Override
        public void Release() {
            mPlayer.reset();
            mPlayer.stop();
            mPlayer.release();
        }

        @Override
        public boolean isPlaying() {
            return mPlayer.isPlaying();
        }

        @Override
        public int getDuration() {
            return mPlayer.getDuration();
        }

        @Override
        public int getCurrentPosition() {
            return mPlayer.getCurrentPosition();
        }

        @Override
        public void seekTo(int length) {
            seekLength = length;
            mPlayer.seekTo(length);
        }

        @Override
        public int getCurrentIndex() {
            return currentIndex;
        }

        @Override
        public void setCurrentIndex(int currentIdx) {
            currentIndex = currentIdx;
        }
    }

}

编辑MainActivity.java文件

package com.zys.musicbox;

import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List musicList = new ArrayList();
    private ListAdapter mListAdapter;
    private ListView mListView;
    //记录长按的列表项坐标
    private int currentSel;
    //按钮
    private ImageView btnPrevious;
    private ImageView btnNext;
    private ImageView btnPlay;
    //文本
    private TextView listTitle;
    private TextView playingName;
    //进度条
    private SeekBar musicSeekBar;
    //自定义Binder对象 用于调用服务中的方法
    private MyBinderInterface myBinder;
    //自定义服务连接对象
    private MyServiceConnection conn;
    //是否正在播放
    private boolean isPlaying = false;
    private Handler handler = new Handler();
    //更新线程用于更新进度条
    private Runnable updateThread = new Runnable() {
        @Override
        public void run() {
            if (myBinder != null){
                try {
                    if (myBinder.isPlaying()){
                        int duration = myBinder.getDuration();
                        int currentPos = myBinder.getCurrentPosition();
                        musicSeekBar.setMax(duration);
                        musicSeekBar.setProgress(currentPos);

                        int prg_sec = currentPos/1000;
                        int max_sec = duration/1000;
                        if (prg_sec == max_sec){
                            myBinder.PlayNext();
                            updateState();
                        }
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
            handler.post(updateThread);
        }
    };
    
    //定义服务连接
    private class MyServiceConnection implements ServiceConnection{
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (MyBinderInterface)service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
    //更新播放状态
    private void updateState() {
        int index = myBinder.getCurrentIndex();
        mListAdapter.setFocusItemPos(index);
        String currentMusicName = musicList.get(index).getMusic_title();
        playingName.setText(currentMusicName);
        btnPlay.setImageBitmap(BitmapFactory.decodeResource(getResources(),R.drawable.pause));
        isPlaying = true;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //通过工具类MusicUtils获取音乐信息列表
        musicList = MusicUtils.ResolveMusicToList(getApplicationContext());
        //获取视图
        initView();
        //设置列表标题
        String title = getResources().getString(R.string.title_string).toString();
        title += "(总数:"+ musicList.size() + ")";
        listTitle.setText(title);
        //为mListView注册上下文菜单
        registerForContextMenu(mListView);
        conn = new MyServiceConnection();
        //绑定服务
        bindService(new Intent(this,MusicService.class),conn, Context.BIND_AUTO_CREATE);

        handler.post(updateThread);
    }
    //初始化视图
    private void initView(){
        listTitle = (TextView)findViewById(R.id.music_list_title);
        playingName = (TextView)findViewById(R.id.music_name);
        musicSeekBar = (SeekBar)findViewById(R.id.music_seek_bar);

        btnPrevious = (ImageView)findViewById(R.id.btn_previous);
        btnNext = (ImageView)findViewById(R.id.btn_next);
        btnPlay = (ImageView)findViewById(R.id.btn_play);

        mListView = (ListView)findViewById(R.id.music_list);
        mListAdapter = new ListAdapter(MainActivity.this,musicList);
        mListView.setAdapter(mListAdapter);

        setListener();
    }
    //设置监听事件
    private void setListener(){
        mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                mListView.showContextMenu();
                currentSel = position;
                return true;
            }
        });

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView parent, View view, int position, long id) {
                myBinder.setCurrentIndex(position);
                myBinder.Play();

                mListAdapter.setFocusItemPos(position);
                updateState();
            }
        });

        btnPrevious.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isPlaying == true){
                    myBinder.PlayPrev();
                    updateState();
                }
            }
        });

        btnNext.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isPlaying == true){
                    myBinder.PlayNext();
                    updateState();
                }
            }
        });

        btnPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (isPlaying == true){
                    btnPlay.setImageBitmap(BitmapFactory.decodeResource(
                            getResources(),R.drawable.play));
                    isPlaying = false;
                    myBinder.Pause();
                    return;
                }
                if (isPlaying == false){
                    if (myBinder.getCurrentIndex() == -1){
                        myBinder.setCurrentIndex(0);
                        mListAdapter.setFocusItemPos(0);
                        myBinder.Play();
                        updateState();
                    }
                    btnPlay.setImageBitmap(BitmapFactory.decodeResource(
                            getResources(),R.drawable.pause));
                    isPlaying = true;
                    myBinder.Resume();

                }
            }
        });

        musicSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                if (myBinder != null){
                    try {
                        myBinder.seekTo(seekBar.getProgress());
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }
        });
    }
    @Override
    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo){
        menu.add(0,0,0,R.string.menu_detail);
        menu.add(0,1,1,R.string.menu_play);
        super.onCreateContextMenu(menu,view,menuInfo);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case 0:
                StringBuilder msgBuilder = new StringBuilder();
                msgBuilder.append("文件名:" + musicList.get(currentSel).getMusic_name() + "\n");
                msgBuilder.append("路  径:" + musicList.get(currentSel).getMusic_path() + "\n");
                msgBuilder.append("时  长:" + musicList.get(currentSel).getMusic_duration()/1000 + " s\n");
                String title = "文件详情";
                new AlertDialog.Builder(MainActivity.this)
                        .setIcon(R.drawable.note)
                        .setTitle(title)
                        .setMessage(msgBuilder.toString())
                        .setPositiveButton(R.string.btn_confirm, new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                            }}).create().show();
                break;
            case 1:
                //不处于播放状态 或者 选择的歌曲和正在播放的歌曲不是同一首 则更新状态且播放
                if (isPlaying == false || currentSel != myBinder.getCurrentIndex()){
                    myBinder.setCurrentIndex(currentSel);
                    updateState();
                    myBinder.Play();
                }
                //提示选择的歌曲已经在播放了
                else{
                    Toast.makeText(MainActivity.this,R.string.str_playing,Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
        return super.onContextItemSelected(item);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        boolean retValue = super.onCreateOptionsMenu(menu);
        getMenuInflater().inflate(R.menu.menu_item,menu);
        return retValue;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == R.id.item_about){
            StringBuilder msgBuilder = new StringBuilder();
            msgBuilder.append("MusicBox  V1.0.0\n");
            msgBuilder.append("作者:Leo_Elegant\n");
            msgBuilder.append("(C) 2019 ......");
            String title = "关于";
            new AlertDialog.Builder(MainActivity.this)
                    .setIcon(R.drawable.note)
                    .setTitle(title)
                    .setMessage(msgBuilder.toString())
                    .setPositiveButton(R.string.btn_confirm, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                        }}).create().show();
        }
        if (item.getItemId() == R.id.item_exit){
            onBackPressed();
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        String title = "提示";
        new AlertDialog.Builder(MainActivity.this)
                .setIcon(R.drawable.note)
                .setTitle(title)
                .setMessage("确定要退出吗?")
                .setPositiveButton(R.string.btn_confirm, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        myBinder.Release();
                        finish();
                    }
                })
                .setNegativeButton(R.string.btn_cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                    }
                }).create().show();
    }
}

测试、运行

  1. 冷启动模拟器,并上传几首音乐到SD卡的Music目录下。
  2. 在启动程序后会闪退,给程序设好权限。
  3. 先打开模拟器自带的音乐播放软件,帮助我们索引音乐文件并存入数据库,之后我们的程序在读取的到。
  • 启动界面,并点击播放按钮:
    Android实战:利用service实现简单的音乐播放器_第1张图片
  • 下一首:
    Android实战:利用service实现简单的音乐播放器_第2张图片
  • 选项菜单
    Android实战:利用service实现简单的音乐播放器_第3张图片
  • 选项菜单“关于”
    Android实战:利用service实现简单的音乐播放器_第4张图片
  • 长按歌曲弹出上下文菜单
    Android实战:利用service实现简单的音乐播放器_第5张图片
  • 上下文菜单“文件详情”
    Android实战:利用service实现简单的音乐播放器_第6张图片
    注:此例子部分内容为《移动软件开发》课程老师布置实验。
    老师博客:http://www.wanlizhong.com/

你可能感兴趣的:(Android)