【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;@Overridepublic 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{@Overridepublic 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的銷毀而銷毀服務:
@Overridepublic void onStart() {super.onStart();mActivity.allowBindService();}@Overridepublic 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() {@Overridepublic void onServiceConnected(ComponentName arg0, IBinder service) {// TODO Auto-generated method stubmPlayService = ((PlayService.PlayBinder) service).getService();mPlayService.setOnMusicEventListener(mMusicEventListener);onChange(mPlayService.getPlayingPosition());}@Overridepublic 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;}}@Overridepublic IBinder onBind(Intent intent) {return new DownloadBinder();}@Overridepublic 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.4String[] 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() {@Overridepublic void onSuccess(int downloadId) {L.l("download", "success");Toast.makeText(DownloadService.this, mDownloads.get(downloadId).getLocalFileName() + "下載完成",Toast.LENGTH_SHORT).show();onDownloadComplete(downloadId);scanSDCard();}@Overridepublic void onStart(int downloadId, long fileSize) {L.l("download", "start");refreshRemoteView();Toast.makeText(DownloadService.this, "開始下載" + mDownloads.get(downloadId).getLocalFileName(),Toast.LENGTH_SHORT).show();}@Overridepublic void onPublish(int downloadId, long size) { // L.l("download", "publish" + size);}@Overridepublic void onPause(int downloadId) {L.l("download", "pause");}@Overridepublic void onGoon(int downloadId, long localSize) {L.l("download", "goon");}@Overridepublic void onError(int downloadId) {L.l("download", "error");Toast.makeText(DownloadService.this, mDownloads.get(downloadId).getLocalFileName() + "下載失敗",Toast.LENGTH_SHORT).show();onDownloadComplete(downloadId);}@Overridepublic void onCancel(int downloadId) {L.l("download", "cancel");onDownloadComplete(downloadId);}}; }? ? ? 相信你看完PlayService代碼后一定覺得DownLoadService相當的簡單,主要結合DownLoad類實現真正的下載音樂的功能,以及通過回掉接口傳遞數據更新ui的功能。就不多做分析了~·~!!!。
? ? ? 另外,小編想說移植用模擬器也沒有觀察鎖屏后service服務是否會停止,不過,即使服務被銷毀姐姐辦法也是很簡單很簡單:只要在啟動服務的時候獲取電源瑣,在服務被注銷的時候釋放電源瑣就應該可以,感興趣的親可以試試~!~.HAHAHA
總結
以上是生活随笔為你收集整理的【android】音乐播放器之service服务设计的全部內容,希望文章能夠幫你解決所遇到的問題。