一、前言
? ? ? 自己做的音樂APP的界面部分已完成,各種功能基本實(shí)現(xiàn)了量蕊。但是我對音樂后臺播放這一塊(Service)不太滿意铺罢。因?yàn)橹皩ervice接觸得不多,所以沒有一個(gè)明確的規(guī)劃残炮,想要實(shí)現(xiàn)什么就往Service里添加新的需要實(shí)現(xiàn)的功能韭赘,導(dǎo)致原來的Service的代碼十分臃腫和凌亂。
? ? ? 一款音樂APP势就,最核心的業(yè)務(wù)就是播放音樂泉瞻,所以設(shè)計(jì)好一個(gè)完善的音樂APP的Service十分有必要。自己在動(dòng)手寫的時(shí)候參考了網(wǎng)上許多的例子苞冯,Github里完整的項(xiàng)目袖牙,由于編碼風(fēng)格和自己的不太一樣,理解起來效率很低舅锄,一些博客里發(fā)布的僅有單個(gè)功能的源碼又太簡單鞭达。因此我整理了下自己的思路,做一次總結(jié)皇忿,以獲得更好的提升畴蹭。
二、實(shí)現(xiàn)功能
? ? ? 這個(gè)MusicService主要實(shí)現(xiàn)了如下功能:
基本功能
? ? ? 播放本地歌曲禁添、網(wǎng)絡(luò)歌曲撮胧,實(shí)現(xiàn)播放、暫停老翘、下一首芹啥、選擇循環(huán)模式、拖動(dòng)進(jìn)度條改變播放位置等基本功能铺峭。
讀取SQLIte數(shù)據(jù)
? ? ? 1墓怀、啟動(dòng)應(yīng)用后,能夠獲取前一次未播放完的音樂信息(如當(dāng)前時(shí)間卫键、歌曲總長傀履、進(jìn)度條位置、歌曲莉炉、歌手钓账、循環(huán)模式等),并裝入下端播放欄中絮宁,點(diǎn)“播放”在暫停處續(xù)播梆暮;
? ? ? 2、進(jìn)入本地音樂界面時(shí)绍昂,如之前已經(jīng)啟動(dòng)過引用并掃描過本地音樂啦粹,則直接載入歌曲信息偿荷,如還沒有掃描可點(diǎn)擊界面的右上角彈出Dialog選擇“掃描本地音樂”選項(xiàng)進(jìn)行掃描;
? ? ? 3唠椭、在MusicService通過SQLite獲得歌曲信息跳纳。
用戶體驗(yàn)
? ? ? 1、點(diǎn)擊播放欄“列表”圖標(biāo)贪嫂,通過DrawerLayout的方式彈出播放列表寺庄;
? ? ? 2、點(diǎn)擊本地音樂列表或者網(wǎng)絡(luò)歌單的歌曲即可播放撩荣,并且將當(dāng)前播放的音樂自動(dòng)添加至播放列表铣揉,當(dāng)用戶改變循環(huán)模式,或者增刪本地列表餐曹、播放列表的歌曲時(shí),MusicService均可正確地播放下一首歌曲敌厘;
? ? ? 3台猴、通過廣播發(fā)送通知,讓Activity更新UI俱两。
三饱狂、效果圖
? ? ? 效果圖如下所示:
4、關(guān)鍵思路
本Demo的Java文件包括:
? ? ? 后臺Service:
? ? ? MusicService挣棕,用于在后臺播放音樂以及進(jìn)行各種操作的服務(wù)類
? ? ? 前臺Activity
? ? ? Activity:MainActivity:用于進(jìn)入本地音樂界面和網(wǎng)絡(luò)音樂界面的活動(dòng)類
? ? ? LocalMusicActivity:顯示本地音樂列表界面的活動(dòng)類
? ? ? OnlineMusicActivity:實(shí)現(xiàn)網(wǎng)絡(luò)歌曲(也就是我APP中歌單)界面的活動(dòng)類
? ? ? 還有一些工具類少辣、線程類分別用于掃描本地音樂脆霎,適配ListView等延時(shí)操作,詳見源碼俊柔。
? ? ? 此外,還需要用到SQLite去對數(shù)據(jù)進(jìn)行存儲和提取活合,我建立了一個(gè)名為music.db的數(shù)據(jù)庫雏婶,并在這個(gè)數(shù)據(jù)庫里建立了playerbarinfotb、localmusictb白指、onlinemusictb留晚、playlisttb、loadlocalmusiclistviewtb這幾個(gè)表格告嘲,分別管理播放欄數(shù)據(jù)错维、本地音樂、網(wǎng)絡(luò)音樂橄唬、播放列表音樂赋焕、是否曾掃描并載入本地音樂等信息。
? ? ? 還需要在Activity里定義localsong_list轧坎、onlinesong_list和play_list分別存儲本地音樂宏邮、網(wǎng)絡(luò)歌曲以及播放列表的歌曲信息。
代碼運(yùn)行流程就是:
·初始化
? ? ??啟動(dòng)Activity時(shí)先對數(shù)據(jù)庫里的playerbarinfotb這一個(gè)表格中去搜索,如果存在數(shù)據(jù)蜜氨,就將播放欄各項(xiàng)信息提取出來械筛,初始化播放欄。
·進(jìn)入本地
? ? ? 進(jìn)入本地音樂后飒炎,搜索loadlocalmusiclistviewtb埋哟,如果存在數(shù)據(jù),說明已經(jīng)掃描過本地音樂郎汪,因此從localmusictb里將全部的歌曲信息提取出來赤赊,載入本地音樂界面的ListView中,如果loadlocalmusiclistviewtb不存在數(shù)據(jù)煞赢,就顯示“掃描本地”按鈕抛计,對手機(jī)上的本地音樂進(jìn)行掃描,并在掃描完成后將掃描結(jié)果載入ListView照筑。
? ? ? 在MusicService里吹截,用public static final String的語句定義好各種動(dòng)作,如:
? ? ? 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)作顧名思義凝危,分別是“更新時(shí)間”波俄、“播放當(dāng)前播放欄音樂”、“播放網(wǎng)絡(luò)音樂”蛾默、“播放本地音樂”懦铺、“播放音樂列表里的音樂”、“刪除本地歌曲”支鸡、“刪除播放列表里的歌曲”冬念、“播放下一首”、“播放所有本地音樂”苍匆、“播放所有網(wǎng)絡(luò)音樂”刘急、“清空播放列表”等動(dòng)作。
·播放歌曲
? ? ? 如果點(diǎn)擊了本地音樂列表里的某一首歌浸踩,就通過
? ? ? intent.setAction(MusicService.ACTION_PLAY_LOCAL_MUSIC);
? ? ? 的方法叔汁,向intent添加了代表了“播放本地音樂”的這個(gè)動(dòng)作標(biāo)示,與此同時(shí)還通過
? ? ? Intetn.putExtra(“position”,position);
? ? ? 的方法傳入當(dāng)前點(diǎn)擊的歌曲在本地歌曲列表中的位置检碗,接著用
? ? ? startService(intent);
? ? ? 的方法啟動(dòng)項(xiàng)目的服務(wù)類MusicService据块。MusicService在onStartCommand()方法里通過
? ? ? if(intent.setAction.equals(MusicService.ACTION_PLAY_LOCAL_MUSIC);
? ? ? 的語句去判斷傳入的動(dòng)作標(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”思杯,用戶點(diǎn)擊的是本地音樂列表里的第二首歌胜蛉,MusicService就開始獲取傳入的值position,為1(從0開始算起)色乾,在MusicService里提取本地音樂的數(shù)據(jù)庫誊册,通過position找到用戶點(diǎn)擊的是歌曲B。然后播放這首歌暖璧。
·添加
? ? ? 歌曲播放的同時(shí)案怯,程序會對播放列表的數(shù)據(jù)庫playlisttb進(jìn)行搜索,如果playlisttb里面沒有當(dāng)前播放的歌曲澎办,那么用insert語句將其添加至playlisttb殴泰,用.add()語句添加至play_list,并通過Adapter的.updateData()方法刷新播放列表的ListView界面浮驳。
? ? ? 歌曲播放時(shí)通過定時(shí)線程,每秒發(fā)送一次播放器信息廣播捞魁,當(dāng)前Activity接收到廣播后更新播放欄的信息至会。
·下一首
? ? ? 歌曲開始播放之后,在MusicService里執(zhí)行g(shù)etNextSong()的方法谱俭,根據(jù)循環(huán)模式奉件,獲取下一首播放的歌曲。列表循環(huán)的時(shí)候昆著,獲取歌曲B的下一首也即歌曲C县貌,單曲循環(huán)的時(shí)候,獲取夏一首歌曲也即歌曲B凑懂,隨機(jī)播放的時(shí)候煤痕,通過Ramdom取出一個(gè)范圍為0到(play_list.size()-1)的隨機(jī)數(shù)m,用play_list.get(m)的方法獲取下一首待播歌曲接谨。當(dāng)監(jiān)聽到當(dāng)前歌曲播放完畢后摆碉,執(zhí)行播放下一首。
·刪除
? ? ? 當(dāng)刪除本地音樂或者播放列表里的歌曲C時(shí)脓豪,也獲取音樂的position巷帝,為2,并發(fā)送刪除歌曲的動(dòng)作標(biāo)示到MusicService扫夜,MusicService根據(jù)position判斷刪除的是歌曲C楞泼,如果當(dāng)前循環(huán)模式是列表循環(huán)驰徊,刪中的正好是下一首待播的歌曲,那么就通過歌曲的路徑堕阔,獲取另外的正確的下一首待播歌曲棍厂,得到歌曲D(接著D又從播放列表中被刪除,getNextSong()又獲取歌曲E)
·循環(huán)
? ? ? 在MusicService里定義一個(gè)公共的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表示隨機(jī)播放例驹。點(diǎn)擊播放欄的循環(huán)圖標(biāo)可以改變MusicService的公共變量MUSIC_CIRCULATION_MODE,點(diǎn)擊一次就輪次賦值列表循環(huán)退唠、單曲循環(huán)鹃锈、隨機(jī)播放這三個(gè)常量,并通過動(dòng)作標(biāo)示啟動(dòng)MusicService瞧预,MusicService根據(jù)switch(MUSIC_CIRCULATION_MODE)屎债,啟動(dòng)getNextSong()方法去獲取下一首待播歌曲。
·網(wǎng)絡(luò)歌單
? ? ? 網(wǎng)絡(luò)歌單的頁面在我自己的APP里是通過一個(gè)HttpThread的線程類去獲取我服務(wù)端上的歌單列表的垢油,在這個(gè)源碼里我就直接定義一個(gè)list并直接往里面添加歌曲信息了盆驹。只是歌曲的URL是我家局域網(wǎng)里服務(wù)端的歌曲地址,如有需要可以直接改成別的網(wǎng)絡(luò)歌曲連接滩愁。
·MainActivity
? ? ? 在這個(gè)源碼中我并沒有設(shè)置從本地音樂界面或網(wǎng)絡(luò)歌單界面跳轉(zhuǎn)回主界面的方法躯喇,返回主界面上通過菜單上的返回鍵進(jìn)行的,因此硝枉,為解決本地音樂界面或者網(wǎng)絡(luò)歌單界面返回主界面后廉丽,主界面由于沒有重新onCreate()而沒有更新播放列表的問題,我重寫了onResume()方法妻味,加入了一個(gè)刷新播放列表的ListView的方法正压。使得幾個(gè)界面的播放列表能保持同步。
5责球、源碼
? ? ??安卓音樂APP后臺Service源碼