【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系統檔案管
這篇文章主要談談音樂播放器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