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

       学习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








你可能感兴趣的:(【android】音乐播放器之service服务设计)