学习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; } }
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