一、前言
? ? ? 自己做的音樂APP的界面部分已完成,各種功能基本實現(xiàn)了。但是我對音樂后臺播放這一塊(Service)不太滿意。因為之前對Service接觸得不多,所以沒有一個明確的規(guī)劃,想要實現(xiàn)什么就往Service里添加新的需要實現(xiàn)的功能,導(dǎo)致原來的Service的代碼十分臃腫和凌亂。
? ? ? 一款音樂APP,最核心的業(yè)務(wù)就是播放音樂,所以設(shè)計好一個完善的音樂APP的Service十分有必要。自己在動手寫的時候參考了網(wǎng)上許多的例子,Github里完整的項目,由于編碼風(fēng)格和自己的不太一樣,理解起來效率很低,一些博客里發(fā)布的僅有單個功能的源碼又太簡單。因此我整理了下自己的思路,做一次總結(jié),以獲得更好的提升。
二、實現(xiàn)功能
? ? ? 這個MusicService主要實現(xiàn)了如下功能:
基本功能
? ? ? 播放本地歌曲、網(wǎng)絡(luò)歌曲,實現(xiàn)播放、暫停、下一首、選擇循環(huán)模式、拖動進(jìn)度條改變播放位置等基本功能。
讀取SQLIte數(shù)據(jù)
? ? ? 1、啟動應(yīng)用后,能夠獲取前一次未播放完的音樂信息(如當(dāng)前時間、歌曲總長、進(jìn)度條位置、歌曲、歌手、循環(huán)模式等),并裝入下端播放欄中,點“播放”在暫停處續(xù)播;
? ? ? 2、進(jìn)入本地音樂界面時,如之前已經(jīng)啟動過引用并掃描過本地音樂,則直接載入歌曲信息,如還沒有掃描可點擊界面的右上角彈出Dialog選擇“掃描本地音樂”選項進(jìn)行掃描;
? ? ? 3、在MusicService通過SQLite獲得歌曲信息。
用戶體驗
? ? ? 1、點擊播放欄“列表”圖標(biāo),通過DrawerLayout的方式彈出播放列表;
? ? ? 2、點擊本地音樂列表或者網(wǎng)絡(luò)歌單的歌曲即可播放,并且將當(dāng)前播放的音樂自動添加至播放列表,當(dāng)用戶改變循環(huán)模式,或者增刪本地列表、播放列表的歌曲時,MusicService均可正確地播放下一首歌曲;
? ? ? 3、通過廣播發(fā)送通知,讓Activity更新UI。
三、效果圖
? ? ? 效果圖如下所示:




4、關(guān)鍵思路
本Demo的Java文件包括:
? ? ? 后臺Service:
? ? ? MusicService,用于在后臺播放音樂以及進(jìn)行各種操作的服務(wù)類
? ? ? 前臺Activity
? ? ? Activity:MainActivity:用于進(jìn)入本地音樂界面和網(wǎng)絡(luò)音樂界面的活動類
? ? ? LocalMusicActivity:顯示本地音樂列表界面的活動類
? ? ? OnlineMusicActivity:實現(xiàn)網(wǎng)絡(luò)歌曲(也就是我APP中歌單)界面的活動類
? ? ? 還有一些工具類、線程類分別用于掃描本地音樂,適配ListView等延時操作,詳見源碼。
? ? ? 此外,還需要用到SQLite去對數(shù)據(jù)進(jìn)行存儲和提取,我建立了一個名為music.db的數(shù)據(jù)庫,并在這個數(shù)據(jù)庫里建立了playerbarinfotb、localmusictb、onlinemusictb、playlisttb、loadlocalmusiclistviewtb這幾個表格,分別管理播放欄數(shù)據(jù)、本地音樂、網(wǎng)絡(luò)音樂、播放列表音樂、是否曾掃描并載入本地音樂等信息。
? ? ? 還需要在Activity里定義localsong_list、onlinesong_list和play_list分別存儲本地音樂、網(wǎng)絡(luò)歌曲以及播放列表的歌曲信息。
代碼運行流程就是:
·初始化
? ? ??啟動Activity時先對數(shù)據(jù)庫里的playerbarinfotb這一個表格中去搜索,如果存在數(shù)據(jù),就將播放欄各項信息提取出來,初始化播放欄。
·進(jìn)入本地
? ? ? 進(jìn)入本地音樂后,搜索loadlocalmusiclistviewtb,如果存在數(shù)據(jù),說明已經(jīng)掃描過本地音樂,因此從localmusictb里將全部的歌曲信息提取出來,載入本地音樂界面的ListView中,如果loadlocalmusiclistviewtb不存在數(shù)據(jù),就顯示“掃描本地”按鈕,對手機上的本地音樂進(jìn)行掃描,并在掃描完成后將掃描結(jié)果載入ListView。
? ? ? 在MusicService里,用public static final String的語句定義好各種動作,如:
? ? ? public?static?final?String?ACTION_UPDATE_TIME="ACTION_UPDATE_TIME";
? ? ? public?static?final?String?ACTION_PLAY_CURR_MUSIC="ACTION_PLAY_CURR_MUSIC";
? ? ? public?static?final?String?ACTION_PLAY_ONLINE_MUSIC="ACTION_PLAY_ONLINE_MUSIC";
? ? ? public?static?final?String?ACTION_PLAY_LOCAL_MUSIC="ACTION_PLAY_LOCAL_MUSIC";
? ? ? public?static?final?String?ACTION_PLAY_PLAYLIST_MUSIC="ACTION_PLAY_PLAYLIST_MUSIC";
? ? ? public?static?final?String?ACTION_DELETE_LOCALMUSIC="ACTION_DELET_LOCALMUSIC";
? ? ? public?static?final?String?ACTION_DELETE_PLAYLIST_MUSIC="ACTION_DELETE_PLAYLIST_MUSIC";
? ? ? public?static?final?String?ACTION_PLAY_NEXT="ACTION_PLAY_NEXT";
? ? ? public?static?final?String?ACTION_PLAY_ALL_LOCALMUSIC="ACTION_PLAY_ALL_LOCALMUSIC";
? ? ? public?static?final?String?ACTION_PLAY_ALL_ONLINEMUSIC="ACTION_PLAY_ALL_ONLINEMUSIC";
? ? ? public?static?final?String?ACTION_CLEAR_ALL_PLAYLIST="ACTION_CLEAR_ALL_PLAYLIST";
? ? ? public?static?final?String?ACTION_CHANGE_CIRCULATE_MODE="ACTION_CHANGE_CIRCULATE_MODE";
? ? ? 這些動作顧名思義,分別是“更新時間”、“播放當(dāng)前播放欄音樂”、“播放網(wǎng)絡(luò)音樂”、“播放本地音樂”、“播放音樂列表里的音樂”、“刪除本地歌曲”、“刪除播放列表里的歌曲”、“播放下一首”、“播放所有本地音樂”、“播放所有網(wǎng)絡(luò)音樂”、“清空播放列表”等動作。
·播放歌曲
? ? ? 如果點擊了本地音樂列表里的某一首歌,就通過
? ? ? intent.setAction(MusicService.ACTION_PLAY_LOCAL_MUSIC);
? ? ? 的方法,向intent添加了代表了“播放本地音樂”的這個動作標(biāo)示,與此同時還通過
? ? ? Intetn.putExtra(“position”,position);
? ? ? 的方法傳入當(dāng)前點擊的歌曲在本地歌曲列表中的位置,接著用
? ? ? startService(intent);
? ? ? 的方法啟動項目的服務(wù)類MusicService。MusicService在onStartCommand()方法里通過
? ? ? if(intent.setAction.equals(MusicService.ACTION_PLAY_LOCAL_MUSIC);
? ? ? 的語句去判斷傳入的動作標(biāo)識是什么,并執(zhí)行相應(yīng)的操作。例如播放列表里有歌曲A、B、C、D、E順序排列,他們的播放路徑分別是“sdcard/a.mp3”、“sdcard/b.mp3”、“sdcard/c.mp3”、“sdcard/d.mp3”、“sdcard/e.mp3”,用戶點擊的是本地音樂列表里的第二首歌,MusicService就開始獲取傳入的值position,為1(從0開始算起),在MusicService里提取本地音樂的數(shù)據(jù)庫,通過position找到用戶點擊的是歌曲B。然后播放這首歌。
·添加
? ? ? 歌曲播放的同時,程序會對播放列表的數(shù)據(jù)庫playlisttb進(jìn)行搜索,如果playlisttb里面沒有當(dāng)前播放的歌曲,那么用insert語句將其添加至playlisttb,用.add()語句添加至play_list,并通過Adapter的.updateData()方法刷新播放列表的ListView界面。
? ? ? 歌曲播放時通過定時線程,每秒發(fā)送一次播放器信息廣播,當(dāng)前Activity接收到廣播后更新播放欄的信息。
·下一首
? ? ? 歌曲開始播放之后,在MusicService里執(zhí)行g(shù)etNextSong()的方法,根據(jù)循環(huán)模式,獲取下一首播放的歌曲。列表循環(huán)的時候,獲取歌曲B的下一首也即歌曲C,單曲循環(huán)的時候,獲取夏一首歌曲也即歌曲B,隨機播放的時候,通過Ramdom取出一個范圍為0到(play_list.size()-1)的隨機數(shù)m,用play_list.get(m)的方法獲取下一首待播歌曲。當(dāng)監(jiān)聽到當(dāng)前歌曲播放完畢后,執(zhí)行播放下一首。
·刪除
? ? ? 當(dāng)刪除本地音樂或者播放列表里的歌曲C時,也獲取音樂的position,為2,并發(fā)送刪除歌曲的動作標(biāo)示到MusicService,MusicService根據(jù)position判斷刪除的是歌曲C,如果當(dāng)前循環(huán)模式是列表循環(huán),刪中的正好是下一首待播的歌曲,那么就通過歌曲的路徑,獲取另外的正確的下一首待播歌曲,得到歌曲D(接著D又從播放列表中被刪除,getNextSong()又獲取歌曲E)
·循環(huán)
? ? ? 在MusicService里定義一個公共的int變量MUSIC_CIRCULATION_MODE,設(shè)置終態(tài)變量來表示循環(huán)模式,如public? static final int LIST_CIRCULATION=0表示列表循環(huán),public? static final int SINGLE_CIRCULATION=1表示單曲循環(huán),public? static final int SHAFFULE=2表示隨機播放。點擊播放欄的循環(huán)圖標(biāo)可以改變MusicService的公共變量MUSIC_CIRCULATION_MODE,點擊一次就輪次賦值列表循環(huán)、單曲循環(huán)、隨機播放這三個常量,并通過動作標(biāo)示啟動MusicService,MusicService根據(jù)switch(MUSIC_CIRCULATION_MODE),啟動getNextSong()方法去獲取下一首待播歌曲。
·網(wǎng)絡(luò)歌單
? ? ? 網(wǎng)絡(luò)歌單的頁面在我自己的APP里是通過一個HttpThread的線程類去獲取我服務(wù)端上的歌單列表的,在這個源碼里我就直接定義一個list并直接往里面添加歌曲信息了。只是歌曲的URL是我家局域網(wǎng)里服務(wù)端的歌曲地址,如有需要可以直接改成別的網(wǎng)絡(luò)歌曲連接。
·MainActivity
? ? ? 在這個源碼中我并沒有設(shè)置從本地音樂界面或網(wǎng)絡(luò)歌單界面跳轉(zhuǎn)回主界面的方法,返回主界面上通過菜單上的返回鍵進(jìn)行的,因此,為解決本地音樂界面或者網(wǎng)絡(luò)歌單界面返回主界面后,主界面由于沒有重新onCreate()而沒有更新播放列表的問題,我重寫了onResume()方法,加入了一個刷新播放列表的ListView的方法。使得幾個界面的播放列表能保持同步。
5、源碼
? ? ??安卓音樂APP后臺Service源碼