学习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以及设置监听。
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