MENU
// //

Android实现音乐播放器

August 15, 2020 • 技术分享

好久没更新博客了,把之前写的一个小项目发出来吧。这几天也有小学弟经常问这个事情

一、项目整体架构

1、博主参考资料

2、项目下载链接

后续会更新GitHub下载地址

下载地址-csdn

二、技术介绍

1、Activity

摘抄自网络---Activity介绍
可以参考博主的另一边文章-

Activity是对用户可见的UI界面,用户与应用程序都是通过Activity来进行交互的。

Activity的生命周期

在这里插入图片描述
我们在书写Activity的时候应该遵守Activity的生命周期,在合理的地方添加合理的代码。

下边是博主的MainActivity的代码,只放了一部分,大家可以去下载代码来仔细查看

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import com.example.applicationmusic.activity.MusicPlayPauseActivity;
import com.example.applicationmusic.model.Music;
import com.example.applicationmusic.service.MusicService;
import com.example.applicationmusic.util.MusicList;
import com.facebook.stetho.Stetho;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    //定义控件
    private ListView listView;                                  //定义List 用于存放当前显示信息

    private ArrayList<Music> listMusic;                         //存放音乐数据
    private ArrayList<HashMap<String, Object>> listItem;        //在数组中存放数据
    private SimpleAdapter mSimpleAdapter;                       //适配器
    private int num = 0;                                        //排序方式
    private MyReceiver myReceiver;                              //主要适用于接收广播信息


    static {
        System.loadLibrary("native-lib");
    }

    //Activity启动时
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.music_list);
        Stetho.initializeWithDefaults(this);        //查看数据库

        myReceiver = new MyReceiver(new Handler());         //新建一个接收类

        //注册广播
        IntentFilter itFilter = new IntentFilter();
        itFilter.addAction(MusicService.MAIN_UPDATE_UI);
        registerReceiver(myReceiver, itFilter);
        requestPermission();

        findViewById(R.id.artistBtn).setOnClickListener(this);
        findViewById(R.id.titleBtn).setOnClickListener(this);
        findViewById(R.id.genreBtn).setOnClickListener(this);
    }


    //onResume 回调时使用
    @Override
    protected void onResume() {
        init();
        super.onResume();
    }


    //初始化函数
    public void init(){
        //设置当前音乐列表
        listView = findViewById(R.id.lv);
        listItem = MusicList.getMusicMap(getApplicationContext(),num);
        listMusic = MusicList.getMusicList(getApplicationContext(),num);

        //new String  数据来源, new int 数据到哪去
        mSimpleAdapter = new SimpleAdapter(this, listItem, R.layout.item_music,
                new String[]{"textTitle", "textArtist", "textGenre"},
                new int[]{R.id.textTitle, R.id.textArtist, R.id.textGenre});
        listView.setAdapter(mSimpleAdapter);    //为listview添加数据

        //列表点击事件
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
                Bundle bundle = new Bundle();
                bundle.putInt("position",position);
                bundle.putInt("num",num);

                Intent intent = new Intent();
                intent.putExtras(bundle);
                intent.setClass(MainActivity.this, MusicPlayPauseActivity.class);
                startActivity(intent);
            }
        });
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.titleBtn: num = 1;break;
            case R.id.artistBtn: num = 2;break;
            case R.id.genreBtn:num = 3;break;
        }
        //执行点击事件之后执行一遍init()方法 目的是为了实现listview的数据初始化
        init();
    }

    //实现一个广播接收器----可以在其中的onReceive方法中去执行各自的方法
    private class MyReceiver extends BroadcastReceiver {
        private final Handler handler;
        // Handler used to execute code on the UI thread
        public MyReceiver(Handler handler) {
            this.handler = handler;
        }

        @Override
        public void onReceive(final Context context, final Intent intent) {
            // Post the UI updating code to our Handler
            handler.post(new Runnable() {
                @Override
                public void run() {
                    init();
                }
            });
        }
    }



    //判断当前是否权限
    @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){
                    init();
                }else{
                    Toast.makeText(this, "拒绝权限,将无法使用程序。", Toast.LENGTH_LONG).show();
                    finish();
                }
                break;
            default:
        }

    }

    //从数据库中更新数据(插入信息)
    public void addSqlLite(){
        ArrayList<Music> arrayList = new ArrayList<>();
        File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music");
        final File[] files = file.listFiles();
        if (files == null) {
            Log.e("error", "空目录");
        } else {
            for (int i = 0; i < files.length; i++) {
                //设置存储音乐到数据库
                String[] strings = ParseOne(files[i].getAbsolutePath());
                Music music = new Music();
                music.setTitle(strings[0]);
                music.setArtist(strings[1]);
                music.setGenre(strings[2]);
                music.setPath(files[i].getAbsolutePath());
                arrayList.add(music);
            }
            Context context = getApplicationContext();
            MusicList.addSqlLite(context,arrayList);
        }
    }

    //是否需要权限
    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 {
            init();
        }
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String[] ParseOne(String input_);

    @Override
    protected void onDestroy() {
        getApplicationContext().unregisterReceiver(myReceiver);
        super.onDestroy();
    }
}

2、Service

Service是用来处理一些后台程序的,对用户不可见,当用户关闭了APP页面后,有些还需要继续执行的任务不希望被关闭就可以交给Service来处理。

Service的生命周期

在这里插入图片描述
在使用Service的时候我们要通过startService来启动当前的Service,博主使用了StartService与BindService。这样在播放的Activity结束以后,我们的Service也会一直存在的。同时,Service中结合了广播,这样我们就可以随时接收到广播信息。

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.SimpleAdapter;
import android.widget.Toast;

import androidx.annotation.Nullable;

import com.example.applicationmusic.R;
import com.example.applicationmusic.model.Music;
import com.example.applicationmusic.util.MusicList;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @name ApplicationMusic
 * @class com.example.applicationmusic.service
 * @description 提供音乐服务(主要包括音乐播放这块)
 * @anthor yinchen
 * @time 20-8-7 上午9:34
 */
public class MusicService extends Service {

    public static MediaPlayer mlastPlayer;
    public static int mPosition;
    public static int mm;

    private String TAG = "ChiHuoZhi-----Service:";
    private Music music;                                        //获得当前点击的音乐
    private String path;                                        //当前播放的音乐的路径
    private MediaPlayer mediaPlayer;                            //定义mediaPlayer来播放音频
    private AudioManager audioManager;                          //定时音乐管理器 主要是后期来获取焦点用的
    private int position;                                        //定义当前所在位置
    private List<Music> listMusic;                              //定义当前音乐列表
    private ArrayList<HashMap<String, Object>> listItem;        //在数组中存放数据
    private SimpleAdapter mSimpleAdapter;                       //适配器
    private RemoteViews remoteView;                             //主要用来处理视图
    private String notificationChannelID = "1";
    private Context context;
    private int num = 0;
    public static int flag = 0;
    private AudioManager mAudioManager;

    public static String ACTION = "to_service";
    public static String KEY_USR_ACTION = "key_usr_action";
    public static final int ACTION_PRE = 0, ACTION_PLAY_PAUSE = 1, ACTION_NEXT = 2;
    public static String MAIN_UPDATE_UI = "main_activity_update_ui";  //Action
    public static String KEY_MAIN_ACTIVITY_UI_BTN = "main_activity_ui_btn_key";
    public static String KEY_MAIN_ACTIVITY_UI_TEXT = "main_activity_ui_text_key";
    public static String ARTIST = "";
    public static final int  VAL_UPDATE_UI_PLAY = 1,VAL_UPDATE_UI_PAUSE =2;

    static {
        System.loadLibrary("native-lib");
    }

  

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

    //Service被创建的时候 --- 主要用来去实例化上述声明的东西
    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate: ");
        super.onCreate();
        //初始化AudioManager对象
        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        remoteView = new RemoteViews(getPackageName(),R.layout.activity_main);


        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION);
        registerReceiver(receiver, intentFilter);

        Log.d(TAG, "onCreate: receiver:"+receiver);

        flag = 1;
    }



    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: ");
        Bundle bundle = intent.getExtras();
        num = bundle.getInt("num");

        Log.d(TAG, "onStartCommand: "+mm+"@@@"+num);

        position = bundle.getInt("position");
        listMusic = MusicList.getMusicList(getApplicationContext(),num);
        if (mlastPlayer == null || mPosition != position || mm != num){
            init();
        }else{
            mediaPlayer = mlastPlayer;
        }
        return super.onStartCommand(intent, flags, startId);
    }


    //实时更新广播中的信息 相当于修改Service的值
    private void postState(Context context, int state,int songid) {
        Log.d(TAG, "postState: ");
        Intent actionIntent = new Intent(MusicService.MAIN_UPDATE_UI);
        actionIntent.putExtra(MusicService.KEY_MAIN_ACTIVITY_UI_BTN,state);
        actionIntent.putExtra(MusicService.KEY_MAIN_ACTIVITY_UI_TEXT, songid);
        context.sendBroadcast(actionIntent);
    }



    public void init(){
        Log.d(TAG, "init: ");
        music = listMusic.get(position);
        path = music.getPath();
        Log.d(TAG, "init: path="+path);
        mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
        mediaPlayer = new MediaPlayer();

        if (mlastPlayer !=null){
            mlastPlayer.stop();
            mlastPlayer.release();
        }
        mlastPlayer = mediaPlayer;
        mPosition = position;
        mm = num;

        try {
            Log.i(TAG,path);
            remoteView.setTextViewText(R.id.artistText,music.getArtist());
            mediaPlayer.setDataSource(path);
            mediaPlayer.prepare();
            mediaPlayer.start();
            Log.i(TAG, "Ready to play music");
        } catch (IOException e) {
            Log.i(TAG,"ERROR");
            e.printStackTrace();
        }
        postState(getApplicationContext(), VAL_UPDATE_UI_PLAY,position);
        mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                position +=1;
                position = (position + listMusic.size())%listMusic.size();
                music = listMusic.get(position);
                Toast.makeText(getApplicationContext(), "自动为您切换下一首:"+music.getTitle(), Toast.LENGTH_SHORT).show();
                init();
            }
        });
    }

    //This method contains operations on music
    public class MyBinder extends Binder {
        public boolean isPlaying(){
            return mediaPlayer.isPlaying();
        }

        public void play() {
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();
                Log.i(TAG, "Play stop");
            } else {
                mediaPlayer.start();
                Log.i(TAG, "Play start");
            }
        }

        //Play the next music
        public void next(int type){
            mPosition +=type;
            mPosition = (mPosition + listMusic.size())%listMusic.size();
            music = listMusic.get(mPosition);
            init();
        }

        //Returns the length of the music in milliseconds
        public int getDuration(){
            return mediaPlayer.getDuration();
        }

        //Return the name of the music
        public String getName(){
            return music.getTitle();
        }

        //Return the name of the music
        public String getArtist(){
            return music.getArtist();
        }

        //Returns the current progress of the music in milliseconds
        public int getCurrenPostion(){
            return mediaPlayer.getCurrentPosition();
        }

        //Set the progress of music playback in milliseconds
        public void seekTo(int mesc){
            mediaPlayer.seekTo(mesc);
        }
    }

    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(TAG, "Service->Receiver");
            String action  = intent.getAction();
            if (ACTION.equals(action)) {
                int widget_action = intent.getIntExtra(KEY_USR_ACTION, -1);

                switch (widget_action) {
                    case ACTION_PRE:
                        mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                        next(-1);
                        Log.d(TAG,"action_prev");
                        break;
                    case ACTION_PLAY_PAUSE:
                        mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                        play();
                        break;
                    case ACTION_NEXT:
                        mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                        next(1);
                        Log.d(TAG,"action_next");
                        break;
                    default:
                        break;
                }
            }
        }
    };

    /*   MediaPlayer的播放方法*/
    public void play() {

        if (mediaPlayer.isPlaying()) {
            mediaPlayer.pause();
            postState(getApplicationContext(), VAL_UPDATE_UI_PAUSE,position);
            Log.i(TAG, "Play stop");
        } else {
            mediaPlayer.start();
            postState(getApplicationContext(), VAL_UPDATE_UI_PLAY,position);
            Log.i(TAG, "Play start");
        }
    }

    public void pause(){
        mediaPlayer.pause();
        postState(getApplicationContext(), VAL_UPDATE_UI_PAUSE,position);
        Log.i(TAG, "Play stop");
    }

    public void next(int type){
        position +=type;
        position = (position + listMusic.size())%listMusic.size();
        music = listMusic.get(position);
        init();
    }



    //从数据库中更新数据(插入信息)
    public void addSqlLite(){
        Log.d(TAG, "addSqlLite: ");
        ArrayList<Music> arrayList = new ArrayList<>();
        File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Music");
        final File[] files = file.listFiles();
        if (files == null) {
            Log.e("error", "空目录");
        } else {
            for (int i = 0; i < files.length; i++) {
                //设置存储音乐到数据库
                String[] strings = ParseTwo(files[i].getAbsolutePath());
                Music music = new Music();
                music.setTitle(strings[0]);
                music.setArtist(strings[1]);
                music.setGenre(strings[2]);
                music.setPath(files[i].getAbsolutePath());
                arrayList.add(music);
            }
            context = getApplicationContext();
            MusicList.addSqlLite(context,arrayList);
        }
    }


    /**
     * 焦点变化监听器
     */
    private AudioManager.OnAudioFocusChangeListener mAudioFocusChange = new AudioManager.OnAudioFocusChangeListener() {
        @Override
        public void onAudioFocusChange(int focusChange) {

            Log.d(TAG, "onAudioFocusChange: "+focusChange);

            switch (focusChange){
                case AudioManager.AUDIOFOCUS_LOSS:
                    //长时间丢失焦点
                    Log.d(TAG, "AUDIOFOCUS_LOSS");
                    pause();
                    //释放焦点
                    mAudioManager.abandonAudioFocus(mAudioFocusChange);
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                    //短暂性丢失焦点
                    pause();
                    Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT");
                    break;
                case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                    //短暂性丢失焦点并作降音处理
                    Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");
                    break;
                case AudioManager.AUDIOFOCUS_GAIN:
                    //重新获得焦点
                    Log.d(TAG, "AUDIOFOCUS_GAIN");
                    play();
                    break;
            }
        }
    };


    //注册广播时间与注销广播 放到一个方法里
    private void bord(){
        if(flag == 0){
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction(ACTION);
            registerReceiver(receiver, intentFilter);
            flag = 1;
        }
    }

    public native String[] ParseTwo(String input);

    @Override
    public void onDestroy() {
        if(flag == 1){

            if(receiver != null)  getApplicationContext().unregisterReceiver(receiver);
        }
        super.onDestroy();
    }
}

3、BroadcastReceiver

概念:Android四大组件之一,没有可视化界面,用于不同组件和多线程之间的通信。

在代码中,Activity与Service中都用到了广播的机制。这种机制的好处是:我们能够拿到广播中的信息,这样为我们的实时更新提供了很大的便捷。具体代码实现已在代码中书写,这里只单独拿出来一部分来展示。

private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(TAG, "Service->Receiver");
            String action  = intent.getAction();
            if (ACTION.equals(action)) {
                int widget_action = intent.getIntExtra(KEY_USR_ACTION, -1);

                switch (widget_action) {
                    case ACTION_PRE:
                        mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                        next(-1);
                        Log.d(TAG,"action_prev");
                        break;
                    case ACTION_PLAY_PAUSE:
                        mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                        play();
                        break;
                    case ACTION_NEXT:
                        mAudioManager.requestAudioFocus(mAudioFocusChange, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
                        next(1);
                        Log.d(TAG,"action_next");
                        break;
                    default:
                        break;
                }
            }
        }
    };

三、项目总结

  • 在项目中还用到了很多的一些类,在这里就不列举出来了,大家可以参考我的代码,代码地址也给出了。
  • 如果大家有什么不懂得,留言即可,看到就会回复哦!
Last Modified: September 12, 2023