摘要:音乐播放器,实现了播放、暂停、上一首、下一首,四个功能,并且使用了App Widget,在手机桌面添加该应用的小组件也可以实现操作。
下载地址:https://github.com/douyacai/App_Widgets_Test/tree/musicPlayer
首先定义一个Music的结构体,各变量表示音乐的各个属性:
package com.example.musicplayer; public class Music { private String mName; private String mSinger; private String mAlbum; private String mUrl; private long mSize; private long mTime; private long mAlbumid; private long mSongid; public long getSongid() { return mSongid; } public void setSongid(long songid) { this.mSongid = songid; } private String name; public String getName() { return name; } public long getAlbumid() { return mAlbumid; } public void setAlbumid(long albumid) { this.mAlbumid = albumid; } public void setName(String name) { this.name = name; } public String getTitle() { return mName; } public void setTitle(String title) { this.mName = title; } public String getSinger() { return mSinger; } public void setSinger(String singer) { this.mSinger = singer; } public String getAlbum() { return mAlbum; } public void setAlbum(String album) { this.mAlbum = album; } public String getUrl() { return mUrl; } public void setUrl(String url) { this.mUrl = url; } public long getSize() { return mSize; } public void setSize(long size) { this.mSize = size; } public long getTime() { return mTime; } public void setTime(long time) { this.mTime = time; } }
在PlayMusicActivity的onCreate中进行一些初始化动作,包括实例化、注册广播、绑定监听器等:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.music_main); // 界面中控件初始化 initView(); // 注册广播 register(); // 绑定监听器 buttonclick(); service = new MusicPreference(this); intent = new Intent(PlayMusicActivity.this, PlayMusicService.class); musiclist = MusicList.getMusicData(this); toTime = new Tool(); allMusic.setText(String.valueOf(musiclist.size())); }
注册广播主要是用于在播放音乐时接收,用来更新音乐的名称和歌手名称,以及用户点击关闭时收到广播来finish该Activity:
receiver = new MusicPlayer(); IntentFilter filter = new IntentFilter( "com.example.musicserviceplayer"); this.registerReceiver(receiver, filter); close = new Close(); IntentFilter filter2 = new IntentFilter("com.sleep.close"); this.registerReceiver(close, filter2);
下面是当收到音乐播放广播时的处理:
public class MusicPlayer extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Music m = musiclist.get(PlayMusicService.last_song); endTime.setText(toTime.toTime((int) m.getTime())); musicName.setText(m.getTitle()); if (m.getSinger().equals("未知艺术家")) { musicSinger.setText("music"); } else { musicSinger.setText(m.getSinger()); } if (musiclist.size() > 0) currentMusic.setText(String .valueOf(PlayMusicService.last_song + 1)); else currentMusic.setText("0"); imageBtnPlay.setBackgroundResource(R.drawable.pause1); } }
下面是当收到关闭广播时的处理:
public class Close extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { finish(); } }
绑定监听器,主要是绑定onClick的,监听按下播放、上一首、下一首时,启动PlayMusicService:
// 按键监听器 private class MyListener implements OnClickListener { @Override public void onClick(View v) { if (v == imageBtnRewind) { // 上一首 intent.putExtra("mode", 1); startService(intent); } else if (v == imageBtnPlay) { if (PlayMusicService.mPlayer != null && PlayMusicService.playflag) { imageBtnPlay.setBackgroundResource(R.drawable.play1); } else { imageBtnPlay.setBackgroundResource(R.drawable.pause1); } intent.putExtra("mode", 0); startService(intent); } else if (v == imageBtnForward) { intent.putExtra("mode", 2); startService(intent); } } }
new一个Runnable,在播放音乐时用来更新进度:
Runnable runnable = new Runnable() { @Override public void run() { handler.postDelayed(runnable, 100); if (PlayMusicService.mPlayer != null) { seekBar.setProgress((int) (PlayMusicService.mPlayer .getCurrentPosition() * 1000 / PlayMusicService.mPlayer .getDuration())); seekBar.invalidate(); startTime.setText(toTime.toTime((int) PlayMusicService.mPlayer .getCurrentPosition())); } } };
在onResume()中调用:
handler.postDelayed(runnable, 100);
PlayMusicService是实现运行在后台播放音乐的组件,同样在onCreate()中需要执行一些初始化以及注册广播等操作:
@Override public void onCreate() { musiclist = MusicList.getMusicData(getApplicationContext()); service = new MusicPreference(this); toTime = new Tool(); register(); new Thread(this).start(); super.onCreate(); }
这里注册广播,主要是用来接收关闭时发送的广播,用来stopService及保存当前播放进度:
private void register() { close = new Close(); IntentFilter filter22 = new IntentFilter("com.sleep.close"); this.registerReceiver(close, filter22); }
public class Close extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { try { if (mPlayer != null) { String time = toTime.toTime(mPlayer.getCurrentPosition()); service.saveProgress(time, Integer.valueOf(mPlayer.getCurrentPosition())); widgetstop(); mPlayer.release(); mPlayer = null; } Music mz = musiclist.get(last_song); String musicna = mz.getName(); service.savename(musicna, Integer.valueOf(last_song)); String Name = mz.getName(); service.saveMusic(Name, Integer.valueOf(last_song)); } catch (Exception e) { e.printStackTrace(); } playflag = false; } }
当startService时,回调onStartCommand,在这个函数里执行相应操作,如播放:
@Override public int onStartCommand(Intent intent, int flags, int startId) { int mode = intent.getIntExtra("mode", 0); Log.d("onStartCommand", String.valueOf(mode)); if (0 == mode) { if (playflag) {// 暂停 playflag = false; if (null != mPlayer) { mPlayer.pause(); widgetstop(); } try { Music m = musiclist.get(last_song); String Name = m.getName(); service.saveMusic(Name, Integer.valueOf(last_song)); service.saveProgress( toTime.toTime(mPlayer.getCurrentPosition()), Integer.valueOf(mPlayer.getCurrentPosition())); } catch (Exception e) { e.printStackTrace(); } } else {// 播放 if (mPlayer != null) { // 继续播放 mPlayer.start(); playflag = true; } else if (mPlayer == null) { // 读取播放进度播放 Map<String, String> progressMap = service.getProgress(); progress = Integer.valueOf(progressMap.get("progress")); Map<String, String> params = service.getMusic(); last_song = (Integer.valueOf(params.get("currentId"))); if (last_song >= musiclist.size() - 1) { last_song = (musiclist.size() - 1); } Log.d("progress", "上次的进度是:" + String.valueOf(progress)); playMusic(last_song); mPlayer.seekTo(progress); } playflag = true; widgetplay(); } } else if (1 == mode) { last_song = (last_song + 1); if (last_song > musiclist.size() - 1) { last_song = 0; } playMusic(last_song); widgetUpdate(); } else if (2 == mode) { last_song = (last_song - 1); if (last_song < 0) { last_song = (musiclist.size() - 1); } playMusic(last_song); widgetUpdate(); } return super.onStartCommand(intent, flags, startId); }
在playMusic方法中,实现播放音乐以及发送广播的功能:
private void playMusic(int song) { if (null != mPlayer) { mPlayer.release(); mPlayer = null; } Music m = musiclist.get(song); Intent intent = new Intent("com.example.musicserviceplayer"); sendBroadcast(intent); String url = m.getUrl(); Uri myUri = Uri.parse(url); mPlayer = new MediaPlayer(); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); try { mPlayer.setDataSource(getApplicationContext(), myUri); mPlayer.prepare(); mPlayer.start(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } mPlayer.setOnCompletionListener(new OnCompletionListener()); mPlayer.setOnErrorListener(new OnErrorListener()); }
当完成播放以及播放错误时,也需要进行相应的处理:
private class OnCompletionListener implements android.media.MediaPlayer.OnCompletionListener { @Override public void onCompletion(MediaPlayer arg0) { last_song = (last_song + 1); if (last_song > musiclist.size() - 1) { last_song = (0); } playMusic(last_song); } } private final class OnErrorListener implements android.media.MediaPlayer.OnErrorListener { @Override public boolean onError(MediaPlayer arg0, int arg1, int arg2) { if (null != mPlayer) { mPlayer.release(); mPlayer = null; } mPlayer = new MediaPlayer(); Music m = musiclist.get(last_song); String url = m.getUrl(); Uri myUri = Uri.parse(url); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); try { mPlayer.setDataSource(getApplicationContext(), myUri); mPlayer.prepare(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } }
当执行播放或者暂停操作时,也需要同步更新App Widget的ImageButton的ImageViewResource:
public void widgetstop() { RemoteViews remoteViews = new RemoteViews(this.getPackageName(), R.layout.music_appwidget); remoteViews.setImageViewResource(R.id.widget_startBtn, R.drawable.widget_play); AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(getBaseContext()); ComponentName componentName = new ComponentName(getBaseContext(), MusicAppWidgetProvider.class); appWidgetManager.updateAppWidget(componentName, remoteViews); } public void widgetUpdate() { RemoteViews remoteViews = new RemoteViews(this.getPackageName(), R.layout.music_appwidget); Music m = musiclist.get(last_song); Map<String, String> paramsui = service.getPreferencesback(); Uri uri = Uri.parse(paramsui.get("background")); ContentResolver contentResolver = this.getContentResolver(); if (String.valueOf(uri).length() > 4) { try { bitmap = BitmapFactory.decodeStream(contentResolver .openInputStream(uri)); int h = bitmap.getHeight(); remoteViews.setImageViewBitmap(R.id.widget_logo, RoundCorner.toRoundCorner(bitmap, h / 34)); } catch (FileNotFoundException e) { e.printStackTrace(); } } String sbr = m.getName().substring(0, m.getName().length() - 4); remoteViews.setTextViewText(R.id.widgettitlemain, sbr); if (m.getSinger().equals("未知艺术家")) { remoteViews.setTextViewText(R.id.widgetsinger, ""); } else { remoteViews.setTextViewText(R.id.widgetsinger, m.getSinger()); } AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(getBaseContext()); ComponentName componentName = new ComponentName(getBaseContext(), MusicAppWidgetProvider.class); appWidgetManager.updateAppWidget(componentName, remoteViews); }
音乐在播放时也需要在App Widget中显示进度,这里呢,进度使用一个点在圆环中顺时针移动来表示:
@Override public void run() { while (1 > 0) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } long current = 0; long total = 0; int degree = 0; try { current = mPlayer.getCurrentPosition(); total = mPlayer.getDuration(); degree = (int) (current * 360 / total); } catch (Exception e) { e.printStackTrace(); } RemoteViews remoteViews = new RemoteViews(this.getPackageName(), R.layout.music_appwidget); if (degree >= 360) { degree = 360; } try { Xuanzhuan xuanzhuan = new Xuanzhuan(PlayMusicService.this); remoteViews.setImageViewBitmap(R.id.thumb2, xuanzhuan.rotate(degree)); remoteViews.setImageViewBitmap( R.id.icon_panel_progress_barleft, xuanzhuan.rotate2(degree)); } catch (Exception e) { e.printStackTrace(); } catch (OutOfMemoryError e) { e.printStackTrace(); } if (degree >= 180) { remoteViews.setViewVisibility(R.id.leftyuanquan, View.VISIBLE); // 右半绿圆出现 remoteViews.setViewVisibility(R.id.icon_panel_progress_bg2, View.INVISIBLE); } if (degree < 180) { remoteViews .setViewVisibility(R.id.leftyuanquan, View.INVISIBLE); // 右半绿圆消失 remoteViews.setViewVisibility(R.id.icon_panel_progress_bg2, View.VISIBLE); } AppWidgetManager appWidgetManager = AppWidgetManager .getInstance(getBaseContext()); ComponentName componentName = new ComponentName(getBaseContext(), MusicAppWidgetProvider.class); appWidgetManager.updateAppWidget(componentName, remoteViews); } }
在MusicAppWidgetProvider中:
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { final int N = appWidgetIds.length; for (int i = 0; i < N; i++) { int appWidgetId = appWidgetIds[i]; RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.music_appwidget); //play Intent intentPlay = new Intent(context, PlayMusicService.class); intentPlay.putExtra("mode", 0); PendingIntent pendingPlay = PendingIntent.getService( context, 0, intentPlay, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.widget_startBtn, pendingPlay); //next Intent intentNext = new Intent(context, PlayMusicService.class); intentNext.putExtra("mode", 1); PendingIntent pendingNext = PendingIntent.getService( context, 1, intentNext, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.widget_frontBtn, pendingNext); //pre Intent intentPre = new Intent(context, PlayMusicService.class); intentPre.putExtra("mode", 2); PendingIntent pendingPre = PendingIntent.getService( context, 2, intentPre, PendingIntent.FLAG_UPDATE_CURRENT); views.setOnClickPendingIntent(R.id.widget_nextBtn, pendingPre); appWidgetManager.updateAppWidget(appWidgetId, views); } }
PendingIntent有四个参数:
- 第一个参数,context表示上下文;
- 第二个参数,requestCode,这里不同的请求需要用不同的requestCode,不然传递的Extra里的值都会被最新的覆盖掉;
- 第三个参数,intent;
- 第四个参数,操作标识,一般有FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT,FLAG_UPDATE_CURRENT会更新之前PendingIntent的消息
在AndroidManifest中不要忘记对PlayMusicActivity,PlayMusicService,和MusicAppWidgetProvider进行注册:
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.example.musicplayer.PlayMusicActivity" android:label="@string/app_name" android:screenOrientation="portrait" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name="com.example.musicplayer.PlayMusicService" /> <receiver android:name="MusicAppWidgetProvider" > <intent-filter> <action android:name="com.example.action.CLICK" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/music_appwidget_info" /> </receiver> </application>
App Widgets的使用还需要在res/xml里定义music_appwidget_info.xml:
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/music_appwidget" android:minHeight="300dp" android:minWidth="300dp" android:updatePeriodMillis="86400000" > </appwidget-provider>