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

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

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

一、项目演示

1. 录屏演示

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

2. 截屏显示

3. 项目结构

二、使用技术

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文件,主页面就是空白了。

我的初始化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;@Overrideprotected 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() {@Overridepublic 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 threadpublic MyReceiver(Handler handler) {this.handler = handler;}@Overridepublic void onReceive(final Context context, final Intent intent) {// Post the UI updating code to our Handlerhandler.post(new Runnable() {@Overridepublic void run() {initView();}});}}@Overrideprotected 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() {@Overridepublic 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();}@Overridepublic 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() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case UPDATE_UI:updateUI();break;}}};public DetailsActivity() {}@Overrideprotected 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;}@Overridepublic void onReceive(final Context context, final Intent intent) {// Post the UI updating code to our Handlerhandler.post(new Runnable() {@Overridepublic 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() {@Overridepublic void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {//Progress bar changeif (fromUser) {musicControl.seekTo(progress);}}@Overridepublic void onStartTrackingTouch(SeekBar seekBar) {//Start touching the progress bar}@Overridepublic void onStopTrackingTouch(SeekBar seekBar) {//Stop touching the progress bar}});}@Overridepublic 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.@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {Log.i(TAG, "::MyConnection::onServiceConnected");//Get MyBinder in servicemusicControl = (MusicService.MyBinder) service;//Update button textupdatePlayText();updateUI();}@Overridepublic void onServiceDisconnected(ComponentName name) {Log.i(TAG, "::MyConnection::onServiceDisconnected");}}@Overrideprotected void onResume() {super.onResume();//Start the update UI bar after entering the interfaceif (musicControl != null) {handler.sendEmptyMessage(UPDATE_UI);}}@Overrideprotected void onDestroy() {super.onDestroy();//Unbind from the service after exitingunbindService(conn);getApplicationContext().unregisterReceiver(myReceiver);}@Overrideprotected void onStop() {super.onStop();//Stop the progress of the update progress barhandler.removeCallbacksAndMessages(null);}//Update progress barprivate void updateProgress() {int currenPostion = musicControl.getCurrenPostion();seekBar.setProgress(currenPostion);}//Update button textpublic 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 barint cur_time = musicControl.getCurrenPostion(), total_time = musicControl.getDuration();seekBar.setMax(total_time);//Set the progress of the progress barseekBar.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 Handlerhandler.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有两种使用方式,
我是用启动方式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";  //Actionpublic 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;@Overridepublic IBinder onBind(Intent intent) {//When onCreate() is executed, onBind() will be executed to return the method of operating the music.return new MyBinder();}@Overridepublic void onCreate() {super.onCreate();context = getApplicationContext();listMusic = MusicList.getMusicData(context);IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(ACTION);registerReceiver(receiver, intentFilter);}@Overridepublic 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 resourcesplayer.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() {@Overridepublic 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 musicpublic 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 musicpublic void next(int type){mPosition +=type;mPosition = (mPosition + listMusic.size())%listMusic.size();music = listMusic.get(mPosition);prepare();}//Returns the length of the music in millisecondspublic int getDuration(){return player.getDuration();}//Return the name of the musicpublic String getName(){return music.getName();}//Returns the current progress of the music in millisecondspublic int getCurrenPostion(){return player.getCurrentPosition();}//Set the progress of music playback in millisecondspublic void seekTo(int mesc){player.seekTo(mesc);}}private BroadcastReceiver receiver = new BroadcastReceiver() {@Overridepublic 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 musicpublic 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;@Overridepublic 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);}@Overridepublic void onEnabled(Context context) {// Enter relevant functionality for when the first widget is createdif (listMusic == null)listMusic = MusicList.getMusicData(context);pushUpdate(context, AppWidgetManager.getInstance(context), listMusic.get(MusicService.mPosition).getName(),true);}@Overridepublic 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就是一种方便的进程通信,但他支持的布局和控件都有限。绑定布局和控件以及为按钮添加点击事件使用如下:

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 constructionpublic Music(){};//    Deserializationprotected Music(Parcel in) {title = in.readString();singer = in.readString();album = in.readString();url = in.readString();size = in.readLong();time = in.readLong();name = in.readString();}//    Serializationpublic static final Creator<Music> CREATOR = new Creator<Music>() {@Overridepublic Music createFromParcel(Parcel in) {return new Music(in);}@Overridepublic 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;}@Overridepublic 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);}@Overridepublic int describeContents() {return 0;}@Overridepublic 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;}@Overridepublic int getCount() {return listMusic.size();}@Overridepublic Object getItem(int arg0) {return listMusic.get(arg0);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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 controlobjectAnimator = ObjectAnimator.ofFloat(this, "rotation", 0f, 360f);objectAnimator.setDuration(3000);//Set animation timeobjectAnimator.setInterpolator(new LinearInterpolator());//Animation time linear gradientobjectAnimator.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 musicCursor 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 ("<unknown>".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 音乐播放器制作(带有通知栏、Widget小挂件)相关推荐

  1. Android音乐播放器制作(二 )点击歌曲实现播放

    上次我们实现了把手机里的音频扫描到,然后放在list集合里面,用ListView展示在手机界面上,如果没有看过的可以去看看本人的博客:Android音乐播放器制作(一)扫描本地音乐显示在手机上 这次是 ...

  2. Android音乐播放器制作(一)扫描本地音乐显示在手机上

    思路 首先是扫描本地所有的音频文件,然后全部装进集合当中,接下来就是用ListView展示在屏幕上,大概就是这几个步骤了,接下来细讲 创建一个容器 进行过数据解析的朋友都应该知道JavaBean吧,用 ...

  3. Android 音乐播放器的通知栏

    Android音乐播放器的通知栏 效果说明 我点击×按钮会关闭通知栏和应用, 点击停止按钮会在界面显示停止并且这个按钮的图片换成播放图片,再点击一次界面显示播放,且图片换回来 点击上一首和下一首按钮会 ...

  4. Android桌面小部件AppWidget:音乐播放器桌面控制部件Widget(3)

     Android桌面小部件AppWidget:音乐播放器桌面控制部件Widget(3) Android桌面小部件AppWidget比较常用的场景就是音乐播放器,音乐播放器虽然通常在后台播放,但需要 ...

  5. 基于android音乐播放器的设计

    本科毕业论文(设计)诚信声明 本人郑重声明:所呈交的毕业论文(设计),题目<---基于android音乐播放器的设计----------->是本人在指导教师的指导下,进行研究工作所取得的成 ...

  6. Android音乐播放器开发(3)—注册

    1. 说明 本音乐播放器基于Android开发,原为我和另外两个小伙伴在上学期间一起做的一个小项目,近来有时间整理一下.之前我有文章已经介绍了播放界面的功能实现(Android音乐播放器开发),但介绍 ...

  7. Android音乐播放器开发(4)—修改密码

    1. 说明 本音乐播放器基于Android开发,原为我和另外两个小伙伴在上学期间一起做的一个小项目,近来有时间整理一下.之前我有文章已经介绍了播放界面的功能实现(Android音乐播放器开发),但介绍 ...

  8. linux 音频播放器源码,Android音乐播放器源码

    相当完整的Android音乐播放器,直接上效果图及源代码,自己欣赏,具体不再解释了,可以说是一个很给力的Android音乐播放器. 示例代码: /* * Copyright (C) 2009 Tele ...

  9. android播放器实例,android音乐播放器实例

    郑州app开发android音乐播放器实例.布局代码是一个imagebutton和seekbar. 下面是java代码 MainActivity.java package cn.xhhkj.music ...

最新文章

  1. 机器学习之条件随机场CRF一点理解
  2. linux中locate find 与 grep
  3. ftp服务器网页空白,ftp服务器网页空白
  4. python multiprocessing — 基于进程的并行
  5. 第4章 Python 数字图像处理(DIP) - 频率域滤波7 - 二维DFT和IDFT的一些性质 - 傅里叶频谱和相角
  6. 变与不变: Undo构造一致性读的例外情况
  7. mysql傻瓜教程_mysql索引的使用傻瓜教程_MySQL
  8. 用Docker快速搭建一个博客网站,很简单的嘛~
  9. SQL Server 2016中的新PowerShell Cmdlet
  10. 内核抢占机制(preempt)
  11. 手把手教你学项目管理软件project
  12. linux基础期末考,Linux基础期末考试试题.pdf
  13. 自定义MySQL实用的函数和存储过程(持续更新)
  14. 重庆要做的“边缘计算”,是什么?
  15. 别让我们的幸福感受在别人眼中
  16. ajax初始化 ztree v3,zTree_v3
  17. 谈一谈机器视觉里的定拍与飞拍
  18. 数据库双活和ALWAYSON相比的四大优势
  19. C# 之 随机数应用 -- 洗牌算法
  20. 目前市面上最受欢迎共享产品有哪些

热门文章

  1. python项目源码 日程管理软件_分享:一个开源的基于时间管理四象限的待办管理工具...
  2. 洛谷 P3744 李彬的几何 【计算几何】
  3. win7屏幕保护怎么关
  4. 美学心得(第二百四十五集) 罗国正
  5. 十年蓄势,新基建风口,紫光云如何后发先至?
  6. php 选座,jQuery在线选座(高铁版)
  7. 2013年12月综合交友类行业网站综合影响力排名
  8. 【解决方案】SkeyeVSS+SkeyeIVMS平台打造无人值守变电站智能视频监控系统
  9. 用MQTT.fx模拟温度设备联调阿里云IOT物联网平台
  10. matlab 仿真风速,基于Matlab的组合风速建模与仿真