【android】音乐播放器之service服务设计

论坛 期权论坛 脚本     
匿名技术用户   2021-1-7 08:18   11   0

学习Android有一个多月,看完了《第一行代码》以及mars老师的第一期视频通过音乐播放器小项目加深对知识点的理解。从本文开始,将详细的介绍简单仿多米音乐播放器的实现,以及网络解析数据获取百度音乐最新排行音乐以及下载功能。

功能介绍如下:

1、获取本地歌曲列表,实现歌曲播放功能。
2、利用jsoup解析网页数据,从网络获取歌曲列表,同时实现歌曲和歌词下载到手机本地的功能。
3、通知栏提醒,实现仿QQ音乐播放器的通知栏功能.

涉及的技术有:
1、jsoup解析网络网页,从而获取需要的数据
2、android中访问网络,获取文件到本地的网络请求技术,以及下载文件到本地实现断点下载
3、线程池
4、图片缓存
5、service一直在后台运行
6、Activity与Fragment间的切换以及通信
7、notification通知栏设计
8、自定义广播
9、android系统文件管

音乐播放器思路及源码下载见:【android】音乐播放器之设计思路

这篇文章主要谈谈音乐播放器service设计的方方面面。主要用到了两个service服务:一个用于播放音乐的PlayService;另一个用于下载歌曲的DownLoadService。使用了service的两种启动方式:startservice()启动方式和bindservice()启动方式(想要深入理解这两种启动方式可以参考博文:深入理解Android的startservice和bindservice)。

我们可以在application类中使用startservice()启动服务,startservice()方式启动不会随着content的销而销毁服务,除非调用stopservice()停止service。这样做的好处是尽可能的提高了服务的优先级可以使service可以在后台一直运行。根据上面的说明很容易实现application类:

public class App extends Application{
 public static Context sContext;
 public static int sScreenWidth;
 public static int sScreenHeight;
 
 @Override
 public void onCreate() {
  super.onCreate();
  sContext = getApplicationContext();
  
  startService(new Intent(this, PlayService.class));
  startService(new Intent(this, DownloadService.class));
  
  WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
  DisplayMetrics dm = new DisplayMetrics();
  wm.getDefaultDisplay().getMetrics(dm);
  sScreenWidth = dm.widthPixels;
  sScreenHeight = dm.heightPixels;
 }
}

在application类中启动类两个service服务:Playservice和DownloadService。代码量比比较大,小编这边就不逐一进行分析(~!~)概括性的讲讲,见谅见谅!!!先来看看PlayService启动后的初始化工作吧:

public void onCreate() {
  super.onCreate();

  MusicUtils.initMusicList();
  mPlayingPosition = (Integer) SpUtils.get(this, Constants.PLAY_POS, 0);
  mPlayer = new MediaPlayer();
  mPlayer.setOnCompletionListener(this);
  
  // 开始更新进度的线程
  mProgressUpdatedListener.execute(mPublishProgressRunnable);
  
  if(MusicUtils.sMusicList.size() != 0)
  {
   startNotification();
   readyNotification = true;
  }
 }
代码一目了然,完成的主要工作如下几方面:

(1)调用MusicUtil类的InitMusic()方法通过指定位置到sdcard中读取.mp3文件以及.lrc文件,并将这些数据加载到ArrayList数组填充本地音乐列表。

(2)创建MediaPlayer以及设置监听。

(3)调用startNotification()初始化通知栏,并且在startNotification()方法中注册广播器监听通知栏的点击事件。

(4)通过回调接口实现Service与Activity界面歌曲播放进度等一系列功能的更新。

就先贴出通知栏这块比较重要点的代码吧,PlayService显示的功能带后文bingService()启动后还会进一步分析:

private void startNotification() {
  /**
   * 该方法虽然被抛弃过时,但是通用!
   */
  PendingIntent pendingIntent = PendingIntent
    .getActivity(PlayService.this,
    0, new Intent(PlayService.this, PlayActivity.class), 0);
  remoteViews = new RemoteViews(getPackageName(),
    R.layout.play_notification);
  notification = new Notification(R.drawable.icon,
    "歌曲正在播放", System.currentTimeMillis());
  notification.contentIntent = pendingIntent;
  notification.contentView = remoteViews;
  //标记位,设置通知栏一直存在
  notification.flags =Notification.FLAG_ONGOING_EVENT;
  
  Intent intent = new Intent(PlayService.class.getSimpleName());
  intent.putExtra("BUTTON_NOTI", 1);
  PendingIntent preIntent = PendingIntent.getBroadcast(
    PlayService.this,
    1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  remoteViews.setOnClickPendingIntent(
    R.id.music_play_pre, preIntent);
  
  intent.putExtra("BUTTON_NOTI", 2);
  PendingIntent pauseIntent = PendingIntent.getBroadcast(
    PlayService.this,
    2, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  remoteViews.setOnClickPendingIntent(
    R.id.music_play_pause, pauseIntent);
  
  intent.putExtra("BUTTON_NOTI", 3);
  PendingIntent nextIntent = PendingIntent.getBroadcast
    (PlayService.this,
    3, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  remoteViews.setOnClickPendingIntent(
    R.id.music_play_next, nextIntent);
  
  intent.putExtra("BUTTON_NOTI", 4);
  PendingIntent exit = PendingIntent.getBroadcast(PlayService.this,
    4, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  remoteViews.setOnClickPendingIntent(
    R.id.music_play_notifi_exit, exit);
  
  notificationManager = (NotificationManager)
    getSystemService(NOTIFICATION_SERVICE);
  setRemoteViews();
  
  /**
   * 注册广播接收者
   * 功能:
   * 监听通知栏按钮点击事件 
   */
  IntentFilter filter = new IntentFilter(
    PlayService.class.getSimpleName());
  MyBroadCastReceiver receiver = new MyBroadCastReceiver();
  registerReceiver(receiver, filter);
 }
public void setRemoteViews(){
  L.l(TAG, "进入——》setRemoteViews()");
  remoteViews.setTextViewText(R.id.music_name,
    MusicUtils.sMusicList.get(
      getPlayingPosition()).getTitle());
  remoteViews.setTextViewText(R.id.music_author,
    MusicUtils.sMusicList.get(
      getPlayingPosition()).getArtist());
  Bitmap icon = MusicIconLoader.getInstance().load(
    MusicUtils.sMusicList.get(
      getPlayingPosition()).getImage());
  remoteViews.setImageViewBitmap(R.id.music_icon,icon == null
    ? ImageTools.scaleBitmap(R.drawable.icon)
      : ImageTools
    .scaleBitmap(icon));
  if (isPlaying()) {
   remoteViews.setImageViewResource(R.id.music_play_pause,
     R.drawable.btn_notification_player_stop_normal);
  }else {
   remoteViews.setImageViewResource(R.id.music_play_pause,
     R.drawable.btn_notification_player_play_normal);
  }
  //通知栏更新
  notificationManager.notify(5, notification);
 }

 private class MyBroadCastReceiver extends BroadcastReceiver{
  @Override
  public void onReceive(Context context, Intent intent) {
   if (intent.getAction().equals(
     PlayService.class.getSimpleName())) {
    L.l(TAG, "MyBroadCastReceiver类——》onReceive()");
    L.l(TAG, "button_noti-->"
    +intent.getIntExtra("BUTTON_NOTI", 0));
    switch (intent.getIntExtra("BUTTON_NOTI", 0)) {
    case 1:
     pre();
     break;
    case 2:
     if (isPlaying()) {
      pause(); // 暂停
     } else {
      resume(); // 播放
     }
     break;
    case 3:
     next();
     break;
    case 4:
     if (isPlaying()) {
      pause();
     }
     //取消通知栏
     notificationManager.cancel(5);
     break;
    default:
     break;
    }
   }
   if (mListener != null) {
    mListener.onChange(getPlayingPosition());
   }
  }
 }
为了读者思路的连贯性(~!~一方面也因为DownLoadService初始化确实ye没做啥工作。。。),我就继续将PlayService的bindService()方式也一并分析了再说DownLoadService吧,见谅见谅!!!!~·~!

本地列表LocalFragment在创建时调用了onstart()方法调用的活动中的bindservice()方法实现绑定服务,同理在销毁的时候调用onstop()方法调用活动中的unbindservice()方法销毁服务,这种启动方式会随着content的销毁而销毁服务:

@Override
 public void onStart() {
  super.onStart();
  mActivity.allowBindService();
 }
 
 @Override
 public void onStop() {
  super.onStop();
  mActivity.allowUnbindService();
 }
/**
  * Fragment的view加载完成后回调
  */
 public void allowBindService() {
  bindService(new Intent(this, PlayService.class), mPlayServiceConnection,
    Context.BIND_AUTO_CREATE);
 }
private ServiceConnection mPlayServiceConnection = new ServiceConnection() {

  @Override
  public void onServiceConnected(ComponentName arg0, IBinder service) {
   // TODO Auto-generated method stub
   mPlayService = ((PlayService.PlayBinder) service).getService();
   mPlayService.setOnMusicEventListener(mMusicEventListener);
   onChange(mPlayService.getPlayingPosition());
  }

  @Override
  public void onServiceDisconnected(ComponentName arg0) {
   mPlayService = null;
   
  }
  
 };
同理,在PlayActivity也是通过这种方式实现绑定服务,在LocalFragment以及PlayActivity中通过各自界面的监听事件调用service中的想过方法实现音乐的播放、暂停、下一首、上一首以及通过回调函数实现播放进度的更新等一系列功能,部分代码代码如下:

 /**
  * 播放
  * @param position 音乐列表的位置
  * @return 当前播放的位置
  */
 public int play(int position) {
  if(position < 0) position = 0;
  if(position >= MusicUtils.sMusicList.size()) position = MusicUtils.sMusicList.size() - 1;
  
  try {
   mPlayer.reset();
   
   mPlayer.setDataSource(MusicUtils.sMusicList.get(position).getUri());
   mPlayer.prepare();
   
   start();
   if(mListener != null) mListener.onChange(position);
  } catch (Exception e) {
   e.printStackTrace();
  }
  
  mPlayingPosition = position;
  SpUtils.put(Constants.PLAY_POS, mPlayingPosition);
  if(!readyNotification){
   startNotification();
  }else{
   setRemoteViews();
  }
  return mPlayingPosition;
 }

 private void start() {
  mPlayer.start();
 }
 
 /**
  * 是否正在播放
  * @return
  */
 public boolean isPlaying() {
  return mPlayer != null&& mPlayer.isPlaying(); 
 }
 
 /**
  * 继续播放
  * @return 当前播放的位置 默认为0
  */
 public int resume() {
  if(isPlaying()){
   return -1;
  }else if(mPlayingPosition <= 0 || mPlayingPosition >= MusicUtils.sMusicList.size()){
   mPlayingPosition = 0;
   play(mPlayingPosition);
   setRemoteViews();
   return mPlayingPosition;
  }else{
   mPlayer.start();
   setRemoteViews();
   return mPlayingPosition;
  }
 }
 
 /**
  * 暂停播放
  * @return 当前播放的位置
  */
 public int pause() {
  if(!isPlaying()) return -1;
  mPlayer.pause();
  setRemoteViews();
  return mPlayingPosition;
 }
 
 /**
  * 下一曲
  * @return 当前播放的位置
  */
 public int next() {
  if(mPlayingPosition >= MusicUtils.sMusicList.size() - 1) {
   return play(0);
  }
  setRemoteViews();
  return play(mPlayingPosition + 1);
 }
 
 /**
  * 上一曲
  * @return 当前播放的位置
  */
 public int pre() {
  if(mPlayingPosition <= 0) {
   return play(MusicUtils.sMusicList.size() - 1);
  }
  setRemoteViews();
  return play(mPlayingPosition - 1);
 }
马上来看下DownLoadService代码分析:

public class DownloadService extends Service{
 private SparseArray<Download> mDownloads = new SparseArray<Download>();
 private RemoteViews mRemoteViews;
 
 public class DownloadBinder extends Binder {
  public DownloadService getService() {
   return DownloadService.this;
  }
 }
 
 @Override
 public IBinder onBind(Intent intent) {
  return new DownloadBinder();
 }
 
 @Override
 public void onCreate() {
  super.onCreate();
 }
 
 public void download(final int id, final String url, final String name) {
  L.l("download", url);
  Download d = new Download(id, url, MusicUtils.getMusicDir() + name);
  d.setOnDownloadListener(mDownloadListener).start(false);
  mDownloads.put(id, d);
 }
 
 private void refreshRemoteView() {
  @SuppressWarnings("deprecation")
  Notification notification = new Notification(
    android.R.drawable.stat_sys_download, "",
    System.currentTimeMillis());
  mRemoteViews = new RemoteViews(getPackageName(),
    R.layout.download_remote_layout);
     notification.contentView = mRemoteViews;
     
     StringBuilder builder = new StringBuilder();
  for(int i=0,size=mDownloads.size();i<size;i++) {
   builder.append(mDownloads.get(mDownloads.keyAt(i))
     .getLocalFileName());
   builder.append("、");
  }
  
  mRemoteViews.setTextViewText(R.id.tv_download_name, 
    builder.substring(0, builder.lastIndexOf("、")));
     
     startForeground(R.drawable.icon, notification);
 }
 
 private void onDownloadComplete(int downloadId) {
  mDownloads.remove(downloadId);
  if(mDownloads.size() == 0) {
   stopForeground(true);
   return;
  }
  
  refreshRemoteView();
 }
 
 /**
  * 发送广播,通知系统扫描指定的文件
  */
 private void scanSDCard() {
  
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
   // 判断SDK版本是不是4.4或者高于4.4
   String[] paths = new String[]{
     Environment.getExternalStorageDirectory().toString()};
   MediaScannerConnection.scanFile(this, paths, null, null);
  } else {
   Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED);
   intent.setClassName("com.android.providers.media",
     "com.android.providers.media.MediaScannerReceiver");
   intent.setData(Uri.parse("file://"+ MusicUtils.getMusicDir()));
   sendBroadcast(intent);
  }
 }
 
 private Download.OnDownloadListener mDownloadListener = 
   new Download.OnDownloadListener() {
  
  @Override
  public void onSuccess(int downloadId) {
   L.l("download", "success");
   Toast.makeText(DownloadService.this, 
     mDownloads.get(downloadId).getLocalFileName() + "下载完成",
     Toast.LENGTH_SHORT).show();
   onDownloadComplete(downloadId);
   scanSDCard();
  }
  
  @Override
  public void onStart(int downloadId, long fileSize) {
   L.l("download", "start");
   refreshRemoteView();
   Toast.makeText(DownloadService.this, "开始下载" + 
     mDownloads.get(downloadId).getLocalFileName(),
     Toast.LENGTH_SHORT).show();
  }
  
  @Override
  public void onPublish(int downloadId, long size) {
//   L.l("download", "publish" + size);
  }
  
  @Override
  public void onPause(int downloadId) {
   L.l("download", "pause");
  }
  
  @Override
  public void onGoon(int downloadId, long localSize) {
   L.l("download", "goon");
  }
  
  @Override
  public void onError(int downloadId) {
   L.l("download", "error");
   Toast.makeText(DownloadService.this, 
     mDownloads.get(downloadId).getLocalFileName() + "下载失败",
     Toast.LENGTH_SHORT).show();
   onDownloadComplete(downloadId);
  }
  
  @Override
  public void onCancel(int downloadId) {
   L.l("download", "cancel");
   onDownloadComplete(downloadId);
  }
 };
}
相信你看完PlayService代码后一定觉得DownLoadService相当的简单,主要结合DownLoad类实现真正的下载音乐的功能,以及通过回掉接口传递数据更新ui的功能。就不多做分析了~·~!!!。

另外,小编想说移植用模拟器也没有观察锁屏后service服务是否会停止,不过,即使服务被销毁姐姐办法也是很简单很简单:只要在启动服务的时候获取电源琐,在服务被注销的时候释放电源琐就应该可以,感兴趣的亲可以试试~!~.HAHAHA








分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:7942463
帖子:1588486
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP