Android 音乐播放器制作(带有通知栏、Widget小挂件)

Android 音乐播放器制作(带有通知栏显示、Widget小挂件)

我用的开发工具是AndroidStudio,我的手机是Android7.1.2,我的另一个测试手机是Android8.0. 整个项目完整代码放在文章末尾。
本项目已同步上传到我的GitHub:https://github.com/2604150210/JalMusic

进入公司的第一个任务就是写一个音乐播放器(给定时间是两个星期)
对我来说还挺难的?毕竟之前还从没写过这么复杂的APP呢,不过只有挑战自己不会的东西才有意义嘛?才能得到进步。
在第二周的时候,我突然明白了为啥来公司前两星期就让我做一个播放器了,因为一个音乐播放器就要用到好多知识,Android的四大组件全都用到了,还学习了怎样实现进程间通信和异步更新UI,以及Widget的使用。

一、项目演示

1. 录屏演示

我录制了三个视频,可是CSDN里面居然不支持上传视频,唉唉唉???我决定把演示视频和代码放到压缩包里一起放在文末链接中。

2. 截屏显示

Android 音乐播放器制作(带有通知栏、Widget小挂件)_第1张图片Android 音乐播放器制作(带有通知栏、Widget小挂件)_第2张图片Android 音乐播放器制作(带有通知栏、Widget小挂件)_第3张图片Android 音乐播放器制作(带有通知栏、Widget小挂件)_第4张图片Android 音乐播放器制作(带有通知栏、Widget小挂件)_第5张图片Android 音乐播放器制作(带有通知栏、Widget小挂件)_第6张图片

3. 项目结构

Android 音乐播放器制作(带有通知栏、Widget小挂件)_第7张图片

二、使用技术

1. Activity

Activity是对用户可见的UI界面,用户与应用程序都是通过Activity来进行交互的。Activity的生命周期如下:

我的这个音乐播放器项目中只用到了两个Activity,因为我的APP比较简单,只有两个页面,一个是音乐列表MainActivity,一个是详情页DetailActivity。其中onCreate()方法是Activity的入口方法,每一个Activity的实例创建时都会调用onCreate()方法,在这个方法中我们可以做一些初始化操作,如申请权限,初始化UI等。

我在MainActivity初始化的过程中,还不争气地入了坑,由于我刚开始是用我的手机测试的,我的手机是不需要动态获取权限的,直接在manifest中写的,但是在Android8.0的手机上运行却会闪退,是因为Android8.0需要动态获取权限,而我在MainActivity中调用了MusicList类中的静态方法getMusicData(context),这个方法是用来读取手机里面的mp3文件的,但是我刚开始没有动态申请权限,所以在8.0的手机上就闪退了。

Activity的UI初始化和申请权限是两个异步的事件,如果你的UI必须依赖于成功申请权限的话,建议你把UI初始化语句写在成功申请权限后的回调函数onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)中。 如我的UI显示依赖获取READ_EXTERNAL_STORAGE权限,要不然就无法读取到手机的mp3文件,主页面就是空白了。
Android 音乐播放器制作(带有通知栏、Widget小挂件)_第8张图片
我的初始化UI的代码都写在了initView()中了,initView()在申请权限成功时得到调用。
由于我的MainActivity刚打开的时候,如果没有打开的歌曲则底部不显示东西,如果有已暂停或正在播放的歌曲,则在底部显示当前打开的歌曲名,所以我的MainActivity刚开始的时候如果点ListView其中的某一个Item进入到DetailActivity中后,当用户从DetailActivity返回出来时,MainActivity应该在底部显示刚才点击的歌曲,而歌曲列表还要显示当前正在播放的歌曲,所以ListView的布局就应该改变了。我在此处的处理是重新加载UI布局,也就是在onResume()中重新调用initView(),之所以在onResume()中调用initView是和Activity的生命周期有关的,由于退回到MainActivity中的时候,这个页面重新被激活,应该是从onStop()->onRestart()->onStart()->onPause(),而不经过onCreate()了,所以写在onCreate中是不可能会重新初始化UI的,而写在onPause()中的话,每次进入音乐列表都会先初始化UI,通过判断当前有无打开的歌曲来决定调用哪一套Item布局。

  • MainActivity.java
package com.jal.www.jalmusic;

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.Handler;
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.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

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

public class MainActivity extends AppCompatActivity {
    private ListView listView;
    private LinearLayout cur_music;
    private TextView tv_main_title;
    private ArrayList<Music> listMusic;
    private String TAG = "MainActivityLog";
    private MyReceiver myReceiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myReceiver = new MyReceiver(new Handler());
        IntentFilter itFilter = new IntentFilter();
        itFilter.addAction(MusicService.MAIN_UPDATE_UI);
        registerReceiver(myReceiver, itFilter);
        requestPermission();
     }
    private void initView() {
        listView = this.findViewById(R.id.listView1);
        listMusic = MusicList.getMusicData(getApplicationContext());
        Log.i(TAG, "listMusic.size()=="+listMusic.size());
        MusicAdapter adapter = new MusicAdapter(this, listMusic);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Bundle bundle = new Bundle();
                bundle.putParcelableArrayList("listMusic",listMusic);
                bundle.putInt("position", position);
                Intent intent = new Intent();
                intent.putExtras(bundle);
                intent.setClass(MainActivity.this, DetailsActivity.class);
                startActivity(intent);
            }
        });
        cur_music = findViewById(R.id.cur_music);
        tv_main_title = findViewById(R.id.tv_main_title);
        if(MusicService.mlastPlayer != null){
            tv_main_title.setText(listMusic.get(MusicService.mPosition).getName());
        }
    }

    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() {
                    initView();
                }
            });
        }
    }
    @Override
    protected void onResume() {
        initView();
        if (MusicService.mlastPlayer != null){
            cur_music.setVisibility(View.VISIBLE);
            tv_main_title = findViewById(R.id.tv_main_title);
            tv_main_title.setText(listMusic.get(MusicService.mPosition).getName());
            cur_music.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Bundle bundle = new Bundle();
                    int position = MusicService.mPosition;
                    bundle.putInt("position", position);
                    Intent intent = new Intent();
                    intent.putExtras(bundle);
                    intent.setClass(MainActivity.this, DetailsActivity.class);
                    startActivity(intent);
                }
            });
        }else{
            cur_music.setVisibility(View.GONE);
        }
        super.onResume();
    }


    @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+"权限被拒绝了",Toast.LENGTH_SHORT).show();
                        }else{
                            initView();
                        }
                    }
                }
                break;
            default:
                break;
        }
    }
    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();
        }
    }
}
  • DetailActivity.java
package com.jal.www.jalmusic;

import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
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.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

import java.util.ArrayList;

public class DetailsActivity extends AppCompatActivity implements View.OnClickListener {
    private MyConnection conn;
    private String TAG = "DetailsActivity";
    private Button btn_pre;
    private Button btn_play;
    private Button btn_next;
    private ImageView btn_return;
    private SeekBar seekBar;
    private MusicButton imageView;
    private TextView tv_title,tv_cur_time,tv_total_time;
    private MusicService.MyBinder musicControl;
    private static final int UPDATE_UI = 0;
    private ArrayList<Music> listMusic;

    MyReceiver myReceiver;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_UI:
                    updateUI();
                    break;
            }
        }
    };

    public DetailsActivity() {
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_details);
        listMusic = MusicList.getMusicData(this);
        Intent intent = new Intent(this, MusicService.class);
        Bundle bundle = getIntent().getExtras();
        intent.putExtras(bundle);
        conn = new MyConnection();
        startService(intent);
        bindService(intent, conn, BIND_AUTO_CREATE);

        myReceiver = new MyReceiver(new Handler());
        IntentFilter itFilter = new IntentFilter();
        itFilter.addAction(MusicService.MAIN_UPDATE_UI);
        getApplicationContext().registerReceiver(myReceiver, itFilter);

        bindViews();
        //Mixed mode binding service
    }
    public class MyReceiver extends BroadcastReceiver {
        private final Handler handler;
        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() {
                    int play_pause = intent.getIntExtra(MusicService.KEY_MAIN_ACTIVITY_UI_BTN, -1);
                    int songid = intent.getIntExtra(MusicService.KEY_MAIN_ACTIVITY_UI_TEXT, -1);
                    tv_title.setText(listMusic.get(songid).getName());
                    switch (play_pause) {
                        case MusicService.VAL_UPDATE_UI_PLAY:
                            btn_play.setText(R.string.pause);
                            imageView.play();
                            break;
                        case MusicService.VAL_UPDATE_UI_PAUSE:
                            btn_play.setText(R.string.play);
                            imageView.pause();
                            break;
                        default:
                            break;
                    }
                }
            });
        }
    }


    private void bindViews() {
        btn_pre = findViewById(R.id.btn_pre);
        btn_play = findViewById(R.id.btn_play);
        btn_next = findViewById(R.id.btn_next);
        btn_return = findViewById(R.id.btn_return);
        seekBar =  findViewById(R.id.sb);
        tv_title = findViewById(R.id.tv_title);
        tv_cur_time =findViewById(R.id.tv_cur_time);
        tv_total_time = findViewById(R.id.tv_total_time);
        imageView = findViewById(R.id.imageview);
        btn_pre.setOnClickListener(this);
        btn_play.setOnClickListener(this);
        btn_next.setOnClickListener(this);
        imageView.setOnClickListener(this);
        btn_return.setOnClickListener(this);
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                //Progress bar change
                if (fromUser) {
                    musicControl.seekTo(progress);
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                //Start touching the progress bar
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                //Stop touching the progress bar
            }
        });
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_play:
            case R.id.imageview:
                play(v);
                break;
            case R.id.btn_next:
                next(v);
                break;
            case R.id.btn_pre:
                pre(v);
                break;
            case R.id.btn_return:
                finish();
                break;
        }
    }

    private class MyConnection implements ServiceConnection {

        //This method will be entered after the service is started.
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "::MyConnection::onServiceConnected");
            //Get MyBinder in service
            musicControl = (MusicService.MyBinder) service;
            //Update button text
            updatePlayText();
            updateUI();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "::MyConnection::onServiceDisconnected");

        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        //Start the update UI bar after entering the interface
        if (musicControl != null) {
            handler.sendEmptyMessage(UPDATE_UI);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //Unbind from the service after exiting
        unbindService(conn);
        getApplicationContext().unregisterReceiver(myReceiver);
    }

    @Override
    protected void onStop() {
        super.onStop();
        //Stop the progress of the update progress bar
        handler.removeCallbacksAndMessages(null);
    }

    //Update progress bar
    private void updateProgress() {
        int currenPostion = musicControl.getCurrenPostion();
        seekBar.setProgress(currenPostion);
    }


    //Update button text
    public void updatePlayText() {
        if(MusicService.mlastPlayer!=null &&MusicService.mlastPlayer.isPlaying()){
            imageView.play();
            btn_play.setText(R.string.pause);
        }else{
            imageView.pause();
            btn_play.setText(R.string.play);
        }
    }

    public void play(View view) {
        Intent intent = new Intent(MusicService.ACTION);
        Bundle bundle = new Bundle();
        bundle.putInt(MusicService.KEY_USR_ACTION,MusicService.ACTION_PLAY_PAUSE);
        intent.putExtras(bundle);
        sendBroadcast(intent);
        updatePlayText();
    }

    public void next(View view) {
        Intent intent = new Intent(MusicService.ACTION);
        Bundle bundle = new Bundle();
        bundle.putInt(MusicService.KEY_USR_ACTION,MusicService.ACTION_NEXT);
        intent.putExtras(bundle);
        sendBroadcast(intent);
        updatePlayText();
    }

    public void pre(View view) {
        Intent intent = new Intent(MusicService.ACTION);
        Bundle bundle = new Bundle();
        bundle.putInt(MusicService.KEY_USR_ACTION,MusicService.ACTION_PRE);
        intent.putExtras(bundle);
        sendBroadcast(intent);
        updatePlayText();
    }

    public void updateUI(){

        //Set the maximum value of the progress bar
        int cur_time = musicControl.getCurrenPostion(), total_time = musicControl.getDuration();
        seekBar.setMax(total_time);
        //Set the progress of the progress bar
        seekBar.setProgress(cur_time);

        String str = musicControl.getName();
        tv_title.setText(str);
        tv_cur_time.setText(timeToString(cur_time));
        tv_total_time.setText(timeToString(total_time));

        updateProgress();

        //Update the UI bar every 500 milliseconds using Handler
        handler.sendEmptyMessageDelayed(UPDATE_UI, 500);
    }

    private String timeToString(int time) {
        time /= 1000;
        return String.format("%02d:%02d",time/60,time%60);
    }
}

2. Service

Service是用来处理一些后台程序的,对用户不可见,当用户关闭了APP页面后,有些还需要继续执行的任务不希望被关闭就可以交给Service来处理。我的项目中的MusicService就是继承了Service类,Service有两种使用方式,Android 音乐播放器制作(带有通知栏、Widget小挂件)_第9张图片
我是用启动方式StartService()和绑定方式BindService()在进入DetailActivity时候就启动MusicService,由于是先用了StartService()来启动了Service,然后再用BindService()绑定Service,所以当DetailActivity生命周期结束时,这个Service并不会结束,而是一直是启动着的。此外,我还在MusicService刚刚启动的时候就注册了一个广播,为的是让它来接收到在其他页面点击了上一曲、下一曲、暂停、播放等按钮时,来做相应的处理,因此,其他页面中点击上一曲、下一曲、暂停、播放按钮时都需要像MusicService发送广播通知,让它来更新音乐。

  • MusicService.java
package com.jal.www.jalmusic;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
import android.widget.Toast;

import java.io.IOException;
import java.util.ArrayList;

public class MusicService extends Service {
    static MediaPlayer mlastPlayer;
    static int mPosition;
    private int position;
    private String path = "";
    private String TAG = "MusicServiceLog";
    private MediaPlayer player;
    private Music music;
    private ArrayList<Music>listMusic;
    private Context context;
    private RemoteViews remoteView;
    private Notification notification;
    private String notificationChannelID = "1";
    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 final int  VAL_UPDATE_UI_PLAY = 1,VAL_UPDATE_UI_PAUSE =2;
    private int notifyId = 1;

    @Override
    public IBinder onBind(Intent intent) {

        //When onCreate() is executed, onBind() will be executed to return the method of operating the music.
        return new MyBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
        listMusic = MusicList.getMusicData(context);
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ACTION);
        registerReceiver(receiver, intentFilter);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        initNotificationBar();
        Bundle bundle = intent.getExtras();
        position = bundle.getInt("position");
        if (mlastPlayer == null || mPosition != position){
            prepare();
        }else{
            player = mlastPlayer;
        }
        return super.onStartCommand(intent, flags, startId);
    }
    private void postState(Context context, int state,int songid) {
        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);
        updateNotification();
        context.sendBroadcast(actionIntent);
    }
    private void initNotificationBar(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            CharSequence name = "notification channel";
            String description = "notification description";
            int importance = NotificationManager.IMPORTANCE_MIN;
            NotificationChannel mChannel = new NotificationChannel(notificationChannelID, name, importance);
            mChannel.setDescription(description);
            mChannel.setLightColor(Color.RED);
            mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
            mNotificationManager.createNotificationChannel(mChannel);
        }
        NotificationCompat.Builder  mBuilder = new NotificationCompat.Builder(this, notificationChannelID);
        Intent intent = new Intent(this, MainActivity.class);
        Bundle bundle = new Bundle();
        bundle.putInt("position", mPosition);
        intent.putExtras(bundle);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0, intent, 0);
        remoteView = new RemoteViews(getPackageName(),R.layout.notification);
        String title = listMusic.get(MusicService.mPosition).getName();
        Log.i(TAG, "updateNotification title = " + title);
        remoteView.setTextViewText(R.id.notification_title, title);
        remoteView.setOnClickPendingIntent(R.id.play_pause,getPendingIntent(this, R.id.play_pause));
        remoteView.setOnClickPendingIntent(R.id.prev_song, getPendingIntent(this, R.id.prev_song));
        remoteView.setOnClickPendingIntent(R.id.next_song, getPendingIntent(this, R.id.next_song));
        if (MusicService.mlastPlayer != null && MusicService.mlastPlayer.isPlaying()) {
            String s = getResources().getString(R.string.pause);
            remoteView.setTextViewText(R.id.play_pause, s);
        }else {
            String s = getResources().getString(R.string.play);
            remoteView.setTextViewText(R.id.play_pause, s);
        }
        mBuilder.setContentIntent(pendingIntent)
                .setContent(remoteView)
                .setWhen(System.currentTimeMillis())
                .setOngoing(false)
                .setVisibility(Notification.VISIBILITY_PUBLIC)
                .setDefaults(Notification.DEFAULT_LIGHTS)
                .setSmallIcon(R.mipmap.zjalmusic)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.jalmusic));
        notification = mBuilder.build();
        notification.flags = Notification.FLAG_ONGOING_EVENT;
        NotificationManager manager = (NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
        manager.notify(notifyId,notification);
        updateNotification();
    }

    private void updateNotification() {
        String title = listMusic.get(MusicService.mPosition).getName();
        Log.i(TAG, "updateNotification title = " + title);
        remoteView.setTextViewText(R.id.notification_title, title);
        if (MusicService.mlastPlayer != null && MusicService.mlastPlayer.isPlaying()) {
            String s = getResources().getString(R.string.pause);
            remoteView.setTextViewText(R.id.play_pause, s);
        }else {
            String s = getResources().getString(R.string.play);
            remoteView.setTextViewText(R.id.play_pause, s);
        }

        notification.contentView = remoteView;
        NotificationManager manager = (NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
        manager.notify(notifyId,notification);
    }

    private PendingIntent getPendingIntent(Context context, int buttonId) {
        Intent intent = new Intent();
        intent.setClass(context, JalMusicWidget.class);
        intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
        intent.setData(Uri.parse(""+buttonId));
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        return pi;
    }
    void prepare(){
        music = listMusic.get(position);
        path = music.getUrl();
        Log.i(TAG,"path:"+path);
        player = new MediaPlayer();//This is only done once, used to prepare the player.
        if (mlastPlayer !=null){
            mlastPlayer.stop();
            mlastPlayer.release();
        }
        mlastPlayer = player;
        mPosition = position;
        player.setAudioStreamType(AudioManager.STREAM_MUSIC);
        try {
            Log.i(TAG,path);
            player.setDataSource(path); //Prepare resources
            player.prepare();
            player.start();
            Log.i(TAG, "Ready to play music");
        } catch (IOException e) {
            Log.i(TAG,"ERROR");
            e.printStackTrace();
        }
        postState(getApplicationContext(), VAL_UPDATE_UI_PLAY,position);
        player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                position +=1;
                position = (position + listMusic.size())%listMusic.size();
                music = listMusic.get(position);
                Toast.makeText(context, "自动为您切换下一首:"+music.getName(), Toast.LENGTH_SHORT).show();
                prepare();
            }
        });
    }

    //This method contains operations on music
    public class MyBinder extends Binder {

        public boolean isPlaying(){
            return player.isPlaying();
        }

        public void play() {
            if (player.isPlaying()) {
                player.pause();
                Log.i(TAG, "Play stop");
            } else {
                player.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);
            prepare();
        }

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

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

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

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

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

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

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

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


}

我还在MusicService中启动了一个通知栏,之所以要将通知栏放在MusicService中是因为通知栏本身就是为了让用户在退出APP的时候,还能看到后台歌曲的播放情况和便捷切换歌曲的。

我在此处又因为Android版本掉坑里了?????就是在new一个通知栏的时候,8.0以上的版本需要将Notification放到channel中,为的是让同一APP的通知都放在一起不对用户造成打扰。刚开始我用我的手机测试的时候,就是直接学习书本上的例子,顺利地开启了通知栏,但在8.0的手机上报错了,报错说找不到Channel,然后我上网查阅了之后才了解到了一些8.0以上的新特性,也相应的找到了修正,如上代码所示。

3. BroadcastReceiver

广播,继承了BroadcastReceiver的类必须要实现onReceiver方法,在这个方法中可以针对接收到的广播消息来做相应的处理。我的项目中很多地方都用到了广播,在用户点击下一曲、上一曲、播放、暂停的时候,都会发送广播,而广播的接受者是MusicService和DetailActivity以及Widget,前者更新音乐,后者更新UI显示。

4. Widget

Widget是桌面小挂件,是伴随着APP一起的一个小页面,想要创建一个Widget很简单,只需要继承AppWidgetProvider就可以,而AppWidgetProvider本身是一个广播,接收着Widget被创建的通知,也可以接收其他通知,如我的项目中歌曲的切换,然后由onReceiver方法来根据不同的通知,调用相应的方法。

  • JalMusicWidget.java
package com.jal.www.jalmusic;

import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.RemoteViews;

import java.util.ArrayList;

/**
 * Implementation of App Widget functionality.
 */
public class JalMusicWidget extends AppWidgetProvider {
    private boolean mStop = true;
    private String TAG = "JalMusicWidgetLog";
    private ArrayList<Music> listMusic;


    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        if (listMusic == null)
            listMusic = MusicList.getMusicData(context);
        pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(MusicService.mPosition).getName(),true);
    }

    @Override
    public void onEnabled(Context context) {
        // Enter relevant functionality for when the first widget is created
        if (listMusic == null)
            listMusic = MusicList.getMusicData(context);
        pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(MusicService.mPosition).getName(),true);
    }

    @Override
    public void onDisabled(Context context) {
        // Enter relevant functionality for when the last widget is disabled
    }
    private void pushAction(Context context, int ACTION) {
        Intent actionIntent = new Intent(MusicService.ACTION);
        actionIntent.putExtra(MusicService.KEY_USR_ACTION, ACTION);
        context.sendBroadcast(actionIntent);
    }
    public void onReceive(Context context, Intent intent) {
        if (listMusic == null)
             listMusic = MusicList.getMusicData(context);
        String action = intent.getAction();
        if (intent.hasCategory(Intent.CATEGORY_ALTERNATIVE)) {
            Uri data = intent.getData();
            int buttonId = Integer.parseInt(data.getSchemeSpecificPart());
            switch (buttonId) {
                case R.id.play_pause:
                    pushAction(context,MusicService.ACTION_PLAY_PAUSE);
                    if(MusicService.mlastPlayer == null){
                        Intent startIntent = new Intent(context,MusicService.class);
                        Bundle bundle1 = new Bundle();
                        bundle1.putInt("position",0);
                        startIntent.putExtras(bundle1);
                        context.startService(startIntent);
                    }
                    break;
                case R.id.prev_song:
                    pushAction(context, MusicService.ACTION_PRE);
                    break;
                case R.id.next_song:
                    pushAction(context, MusicService.ACTION_NEXT);
                    break;
            }

        }else if (MusicService.MAIN_UPDATE_UI.equals(action)){
            int play_pause =  intent.getIntExtra(MusicService.KEY_MAIN_ACTIVITY_UI_BTN, -1);
            int songid = intent.getIntExtra(MusicService.KEY_MAIN_ACTIVITY_UI_TEXT, -1);
            switch (play_pause) {
                case MusicService.VAL_UPDATE_UI_PLAY:
                    pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(songid).getName(),true);
                    break;
                case MusicService.VAL_UPDATE_UI_PAUSE:
                    pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(songid).getName(),false);
                    break;
                default:
                    break;
            }

        }
        super.onReceive(context, intent);
    }
    private void pushUpdate(Context context,AppWidgetManager appWidgetManager,String songName,Boolean play_pause) {
        RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.jal_music_widget);
        remoteView.setOnClickPendingIntent(R.id.play_pause,getPendingIntent(context, R.id.play_pause));
        remoteView.setOnClickPendingIntent(R.id.prev_song, getPendingIntent(context, R.id.prev_song));
        remoteView.setOnClickPendingIntent(R.id.next_song, getPendingIntent(context, R.id.next_song));
        //设置内容
        if (!songName.equals("")) {
            remoteView.setTextViewText(R.id.widget_title, songName);
        }
        //设定按钮图片
        if (play_pause) {
            String s = context.getResources().getString(R.string.pause);
            remoteView.setTextViewText(R.id.play_pause, s);
//            remoteView.setImageViewResource(R.id.play_pause, R.drawable.car_musiccard_pause);
        }else {
            String s = context.getResources().getString(R.string.play);
            remoteView.setTextViewText(R.id.play_pause, s);
//            remoteView.setImageViewResource(R.id.play_pause, R.drawable.car_musiccard_play);
        }
        ComponentName componentName = new ComponentName(context,JalMusicWidget.class);
        appWidgetManager.updateAppWidget(componentName, remoteView);
    }
    private PendingIntent getPendingIntent(Context context, int buttonId) {
        Intent intent = new Intent();
        intent.setClass(context, JalMusicWidget.class);
        intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
        intent.setData(Uri.parse(""+buttonId));
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
        return pi;
    }
}

5. RemoteView/PendingIntent

我的Widget和Notification中自定义的layout都是通过RemoteView来绑定更新布局的,因为Widget和Notification和其他Activity不是在工作在同一个进程中,Widget和Notification是工作在System进程的,不在主进程中我们就无法用findViewByID来绑定控件。但可以通过进程间通信来实现控件的绑定和UI的更新。RemoteView就是一种方便的进程通信,但他支持的布局和控件都有限。绑定布局和控件以及为按钮添加点击事件使用如下:
Android 音乐播放器制作(带有通知栏、Widget小挂件)_第10张图片

6. Hander更新UI

Android为了线程安全,不允许我们在UI线程外操作UI;很多时候我们做界面刷新都需要通过Handler来通知UI组件更新。
当我们的子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而我们发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理。
在我的项目中,我的进度条更新就是通过Hander来实现的,代码在DetailActivity中。

7. 其他类

  • Music.java
package com.jal.www.jalmusic;

import android.os.Parcel;
import android.os.Parcelable;

//The Music class implements the Parcelable interface and can be serialized.
public  class Music implements Parcelable {
    private String title;
    private String singer;
    private String album;
    private String url;
    private long size;
    private long time;
    private String name;

    //Non-parametric construction
    public Music(){};


//    Deserialization
    protected Music(Parcel in) {
        title = in.readString();
        singer = in.readString();
        album = in.readString();
        url = in.readString();
        size = in.readLong();
        time = in.readLong();
        name = in.readString();
    }

//    Serialization
    public static final Creator<Music> CREATOR = new Creator<Music>() {
        @Override
        public Music createFromParcel(Parcel in) {
            return new Music(in);
        }

        @Override
        public Music[] newArray(int size) {
            return new Music[size];
        }
    };

    public String getName()
    {
    return name;
    }
    public void setName(String name)
    {
    this.name = name;
    }
    public String getTitle()
    {
    return title;
    }
    public void setTitle(String title)
    {
    this.title = title;
    }
    public String getSinger()
    {
    return singer;
    }
    public void setSinger(String singer)
    {
    this.singer = singer;
    }
    public String getAlbum()
    {
    return album;
    }
    public void setAlbum(String album)
    {
    this.album = album;
    }
    public String getUrl()
    {
    return url;
    }
    public void setUrl(String url)
    {
    this.url = url;
    }
    public long getSize()
    {
    return size;
    }
    public void setSize(long size)
    {
    this.size =size;
    }
    public long getTime()
    {
    return time;
    }
    public void setTime(long time)
    {
    this.time = time;
    }

    @Override
    public String toString() {
        return "Music{" +
                "title='" + title + '\'' +
                ", singer='" + singer + '\'' +
                ", album='" + album + '\'' +
                ", url='" + url + '\'' +
                ", size=" + size +
                ", time=" + time +
                ", name='" + name + '\'' +
                '}';
    }

    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Music music = (Music) o;
        return size == music.size &&
                time == music.time &&
                title .equals( music.title)&&
                album.equals(music.album)&&
                url.equals(music.url)&&
                name.equals(music.name);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(title);
        dest.writeString(singer);
        dest.writeString(album);
        dest.writeString(url);
        dest.writeLong(size);
        dest.writeLong(time);
        dest.writeString(name);
    }
}
  • MusicAdapter.java
package com.jal.www.jalmusic;

import java.util.List;


import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class MusicAdapter extends BaseAdapter {

    private static final String TAG = "MusicAdapterLog";
    private List<Music> listMusic;
    private Context context;

    public MusicAdapter(Context context, List<Music> listMusic) {
        this.context = context;
        this.listMusic = listMusic;
    }

    public void setListItem(List<Music> listMusic) {
        this.listMusic = listMusic;
    }

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

    @Override
    public Object getItem(int arg0) {
        return listMusic.get(arg0);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
//        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(
                    R.layout.music_item, null);
//        }
        if (MusicService.mlastPlayer != null && MusicService.mPosition == position){
//            if (convertView == null) {
                convertView = LayoutInflater.from(context).inflate(
                        R.layout.music_item2, null);
//            }
        }
        Music m = listMusic.get(position);

        TextView textMusicName = (TextView) convertView
                .findViewById(R.id.music_item_name);
        textMusicName.setText(m.getName());
        TextView textMusicSinger = (TextView) convertView
                .findViewById(R.id.music_item_singer);
        textMusicSinger.setText(m.getSinger());
        TextView textMusicTime = (TextView) convertView
                .findViewById(R.id.music_item_time);
        textMusicTime.setText(toTime((int) m.getTime()));

        if (MusicService.mlastPlayer != null && MusicService.mPosition == position){
            TextView music_isPlay = convertView.findViewById(R.id.music_isPlay);
            if (music_isPlay != null){
                music_isPlay.setText(MusicService.mlastPlayer.isPlaying()?R.string.play:R.string.pause);
            }
        }

        return convertView;
    }

    /**
     * Time format conversion
     *
     * @param time
     * @return
     */
    public String toTime(int time) {
        time /= 1000;
        int minute = time / 60;
        int hour = minute / 60;
        int second = time % 60;
        minute %= 60;
        return String.format("%02d:%02d", minute, second);
    }

}

  • MusicButton.java 这是我用到的旋转图片
package com.jal.www.jalmusic;

import android.animation.ObjectAnimator;
import android.content.Context;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;
import android.view.animation.LinearInterpolator;

public class MusicButton extends AppCompatImageView {
    private ObjectAnimator objectAnimator;

    public static final int STATE_PLAYING =1;
    public static final int STATE_PAUSE =2;
    public static final int STATE_STOP =3;
    public static int state;

    public MusicButton(Context context) {
        super(context);
        init();
    }

    public MusicButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MusicButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        state = STATE_STOP;
        //Add a rotation animation, the rotation center defaults to the midpoint of the control
        objectAnimator = ObjectAnimator.ofFloat(this, "rotation", 0f, 360f);
        objectAnimator.setDuration(3000);//Set animation time
        objectAnimator.setInterpolator(new LinearInterpolator());//Animation time linear gradient
        objectAnimator.setRepeatCount(ObjectAnimator.INFINITE);
        objectAnimator.setRepeatMode(ObjectAnimator.RESTART);
    }

    public void playMusic(){
        if(state == STATE_STOP){
            objectAnimator.start();
            state = STATE_PLAYING;
        }else if(state == STATE_PAUSE){
            objectAnimator.resume();
            state = STATE_PLAYING;
        }else if(state == STATE_PLAYING){
            objectAnimator.pause();
            state = STATE_PAUSE;
        }
    }

    public void play(){
        if(objectAnimator.isPaused()){
            System.out.println("isPaused");
            objectAnimator.resume();
        }else {
            System.out.println("else");
            objectAnimator.start();
        }
    }
    public void pause(){
        objectAnimator.pause();
    }

    public void stopMusic(){
        objectAnimator.end();
        state = STATE_STOP;
    }
}
  • MusicList.java
package com.jal.www.jalmusic;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.provider.MediaStore;

import java.util.ArrayList;

public class MusicList {
    public static ArrayList<Music> getMusicData(Context context) {
        ArrayList<Music> musicList = new ArrayList<Music>();
        ContentResolver cr = context.getContentResolver();
        if (cr != null) {
            // Get all the music
            Cursor cursor = cr.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null,
                    MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
            if (null == cursor) {
                return null;
            }
            if (cursor.moveToFirst()) {
                do {
                    Music m = new Music();
                    String title = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE));
                    String singer = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST));
                    if ("".equals(singer)) {
                        singer = "未知艺术家";
                    }
                    String album = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM));
                    long size = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.SIZE));
                    long time = cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION));
                    String url = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DATA));
                    String name = cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME));
                    String sbr = name.substring(name.length() - 3, name.length());
                    // Log.e("--------------", sbr);
                    if (sbr.equals("mp3")) {
                        m.setTitle(title);
                        m.setSinger(singer);
                        m.setAlbum(album);
                        m.setSize(size);
                        m.setTime(time);
                        m.setUrl(url);
                        m.setName(name);
                        musicList.add(m);
                    }
                } while (cursor.moveToNext());
            }
        }
        return musicList;
    }
}

三、项目源码下载

https://download.csdn.net/download/jal517486222/11086201








我觉得我的项目还有很多需要改进的地方,我会继续努力的,✧(≖ ◡ ≖✿)嘿嘿

你可能感兴趣的:(Android开发)