android音乐播放器(Service+ContentProvider+Broadcast+Activity四大组件完成)
1、获取音乐
1-1:获取手机中的音乐(用ContentProvider内容提供者来完成):
1 package com.firefly.util; 2 3 import java.util.ArrayList; 4 import java.util.HashMap; 5 import java.util.Iterator; 6 import java.util.List; 7 8 import android.content.Context; 9 import android.database.Cursor; 10 import android.provider.MediaStore; 11 import android.util.Log; 12 13 public class MediaUtil { 14 /** 15 * 用于从数据库中查询歌曲的信息,保存在List当中 16 * 17 * @return 18 */ 19 public static List<Mp3Info> getMp3Infos(Context context) { 20 Cursor cursor = context.getContentResolver().query( 21 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, 22 MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 23 List<Mp3Info> mp3Infos = new ArrayList<Mp3Info>(); 24 25 for (int i = 0; i < cursor.getCount(); i++) { 26 cursor.moveToNext(); 27 Mp3Info mp3Info = new Mp3Info(); 28 long id = cursor.getLong(cursor 29 .getColumnIndex(MediaStore.Audio.Media._ID)); // 音乐id 30 String title = cursor.getString((cursor 31 .getColumnIndex(MediaStore.Audio.Media.TITLE))); // 音乐标题 32 String artist = cursor.getString(cursor 33 .getColumnIndex(MediaStore.Audio.Media.ARTIST)); // 艺术家 34 long duration = cursor.getLong(cursor 35 .getColumnIndex(MediaStore.Audio.Media.DURATION)); // 时长 36 long size = cursor.getLong(cursor 37 .getColumnIndex(MediaStore.Audio.Media.SIZE)); // 文件大小 38 String url = cursor.getString(cursor 39 .getColumnIndex(MediaStore.Audio.Media.DATA)); // 文件路径 40 int isMusic = cursor.getInt(cursor 41 .getColumnIndex(MediaStore.Audio.Media.IS_MUSIC)); // 是否为音乐 42 if (isMusic != 0) { // 只把音乐添加到集合当中 43 44 mp3Info.setId(id); 45 mp3Info.setTitle(title); 46 mp3Info.setArtist(artist); 47 mp3Info.setDuration(duration); 48 mp3Info.setSize(size); 49 mp3Info.setUrl(url); 50 51 mp3Infos.add(mp3Info); 52 } 53 } 54 return mp3Infos; 55 } 56 57 /** 58 * 格式化时间,将毫秒转换为分:秒格式 59 * 60 * @param time 61 * @return 62 */ 63 public static String formatTime(long time) { 64 String min = time / (1000 * 60) + ""; 65 String sec = time % (1000 * 60) + ""; 66 if (min.length() < 2) { 67 min = "0" + time / (1000 * 60) + ""; 68 } else { 69 min = time / (1000 * 60) + ""; 70 } 71 if (sec.length() == 4) { 72 sec = "0" + (time % (1000 * 60)) + ""; 73 } else if (sec.length() == 3) { 74 sec = "00" + (time % (1000 * 60)) + ""; 75 } else if (sec.length() == 2) { 76 sec = "000" + (time % (1000 * 60)) + ""; 77 } else if (sec.length() == 1) { 78 sec = "0000" + (time % (1000 * 60)) + ""; 79 } 80 return min + ":" + sec.trim().substring(0, 2); 81 } 82 }
1-2:写实体类来存放获取的音乐数据
1 package com.firefly.util; 2 3 public class Mp3Info { 4 public long id; // 音乐id 5 public String title;// 音乐标题 6 public String artist;// 艺术家 7 public long duration; // 时长 8 public long size;// 文件大小 9 public String url;// 文件路径 10 public int isMusic;// 是否为音乐 11 12 public long getId() { 13 return id; 14 } 15 16 public void setId(long id) { 17 this.id = id; 18 } 19 20 public String getTitle() { 21 return title; 22 } 23 24 public void setTitle(String title) { 25 this.title = title; 26 } 27 28 public String getArtist() { 29 return artist; 30 } 31 32 public void setArtist(String artist) { 33 this.artist = artist; 34 } 35 36 public long getDuration() { 37 return duration; 38 } 39 40 public void setDuration(long duration) { 41 this.duration = duration; 42 } 43 44 public long getSize() { 45 return size; 46 } 47 48 public void setSize(long size) { 49 this.size = size; 50 } 51 52 public String getUrl() { 53 return url; 54 } 55 56 public void setUrl(String url) { 57 this.url = url; 58 } 59 60 public int getIsMusic() { 61 return isMusic; 62 } 63 64 public void setIsMusic(int isMusic) { 65 this.isMusic = isMusic; 66 } 67 68 }
1-3:写适配器来将音乐数据初始化
1 package com.firefly.util; 2 3 import java.util.List; 4 5 import com.firefly.beautifulmusic.R; 6 7 import android.content.Context; 8 import android.graphics.Color; 9 import android.view.LayoutInflater; 10 import android.view.View; 11 import android.view.ViewGroup; 12 import android.widget.BaseAdapter; 13 import android.widget.TextView; 14 15 public class MusicAdapter extends BaseAdapter { 16 List<Mp3Info> datas; 17 LayoutInflater inflater; 18 int index = -1; 19 20 // 把当前位置传过来 21 public void setIndex(int index) { 22 this.index = index; 23 } 24 25 public MusicAdapter(Context context, List<Mp3Info> datas) { 26 this.inflater = LayoutInflater.from(context); 27 this.datas = datas; 28 } 29 30 @Override 31 public int getCount() { 32 // TODO Auto-generated method stub 33 return datas.size(); 34 } 35 36 @Override 37 public Object getItem(int position) { 38 // TODO Auto-generated method stub 39 return datas.get(position); 40 } 41 42 @Override 43 public long getItemId(int position) { 44 // TODO Auto-generated method stub 45 return position; 46 } 47 48 @Override 49 public View getView(int position, View convertView, ViewGroup parent) { 50 ViewHolder holder; 51 if (convertView == null) { 52 convertView = inflater.inflate(R.layout.item_music_list, null); 53 holder = new ViewHolder(); 54 holder.title = (TextView) convertView.findViewById(R.id.item_title); 55 holder.duration = (TextView) convertView 56 .findViewById(R.id.item_duration); 57 holder.artist = (TextView) convertView 58 .findViewById(R.id.item_artist); 59 convertView.setTag(holder); 60 } else { 61 holder = (ViewHolder) convertView.getTag(); 62 } 63 Mp3Info music = datas.get(position); 64 holder.title.setTag(music); 65 holder.title.setText(music.getTitle()); 66 holder.artist.setText(music.getArtist()); 67 // 获取时间并改变它的格式 68 Long time = music.getDuration(); 69 long min = (time / 1000) / 60; 70 long second = (time / 1000) % 60; 71 holder.duration.setText(min + ":" + second); 72 if (position == index) { 73 // 如果正在播放当前项,就改其颜色 74 holder.title.setTextColor(Color.parseColor("#E34C36")); 75 holder.duration.setTextColor(Color.parseColor("#E34C36")); 76 holder.artist.setTextColor(Color.parseColor("#E34C36")); 77 } else { 78 // 否则的话则回到原来的状态 79 holder.title.setTextColor(Color.BLACK); 80 holder.duration.setTextColor(Color.BLACK); 81 holder.artist.setTextColor(Color.BLACK); 82 } 83 return convertView; 84 } 85 86 static class ViewHolder { 87 public TextView title; 88 public TextView duration; 89 public TextView artist; 90 } 91 92 }
1-4:(可不参考)用来适配的素材xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="horizontal" > 6 7 <LinearLayout 8 android:layout_width="match_parent" 9 android:layout_height="match_parent" 10 android:layout_weight="1" 11 android:orientation="vertical" > 12 13 <TextView 14 android:id="@+id/item_title" 15 android:layout_width="match_parent" 16 android:layout_height="wrap_content" 17 android:text="歌名" /> 18 19 <TextView 20 android:id="@+id/item_artist" 21 android:layout_width="match_parent" 22 android:layout_height="wrap_content" 23 android:text="歌手" /> 24 </LinearLayout> 25 26 <TextView 27 android:id="@+id/item_duration" 28 android:layout_width="match_parent" 29 android:layout_height="match_parent" 30 android:layout_weight="4" 31 android:text="时长" /> 32 33 </LinearLayout>
2、在主页面中进行初始化数据
2-1:xml页面布局
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:orientation="vertical" 6 tools:context="${relativePackage}.${activityClass}" > 7 8 <ListView 9 android:id="@+id/lv_musicList" 10 android:layout_width="match_parent" 11 android:layout_height="match_parent" 12 android:layout_weight="1" > 13 </ListView> 14 15 <LinearLayout 16 android:layout_width="match_parent" 17 android:layout_height="wrap_content" 18 android:gravity="center" 19 android:orientation="horizontal" > 20 21 <Button 22 android:id="@+id/btnUp" 23 android:layout_width="50dp" 24 android:layout_height="50dp" 25 android:background="@drawable/up" 26 android:onClick="playMusic" /> 27 28 <Button 29 android:id="@+id/btnPlay" 30 android:layout_width="50dp" 31 android:layout_height="50dp" 32 android:layout_marginLeft="20dp" 33 android:layout_marginRight="20dp" 34 android:background="@drawable/pause" 35 android:onClick="playMusic" /> 36 37 <Button 38 android:id="@+id/btnDown" 39 android:layout_width="50dp" 40 android:layout_height="50dp" 41 android:background="@drawable/down" 42 android:onClick="playMusic" /> 43 </LinearLayout> 44 45 <SeekBar 46 android:id="@+id/sb" 47 android:layout_width="match_parent" 48 android:layout_height="wrap_content" /> 49 50 </LinearLayout>
2-2:在其相对应的Activiity文件中初始化数据
1 package com.firefly.beautifulmusic; 2 3 import java.io.File; 4 import java.util.List; 5 6 import android.app.Activity; 7 import android.content.BroadcastReceiver; 8 import android.content.ComponentName; 9 import android.content.Context; 10 import android.content.Intent; 11 import android.content.IntentFilter; 12 import android.content.ServiceConnection; 13 import android.graphics.Color; 14 import android.os.Bundle; 15 import android.os.IBinder; 16 import android.telephony.TelephonyManager; 17 import android.util.Log; 18 import android.view.View; 19 import android.widget.AdapterView; 20 import android.widget.AdapterView.OnItemClickListener; 21 import android.widget.Button; 22 import android.widget.ListView; 23 import android.widget.SeekBar; 24 import android.widget.TextView; 25 import android.widget.Toast; 26 27 import com.firefly.service.MusicService; 28 import com.firefly.service.MusicService.MyBinder; 29 import com.firefly.util.MediaUtil; 30 import com.firefly.util.Mp3Info; 31 import com.firefly.util.MusicAdapter; 32 33 public class MainActivity extends Activity { 34 ServiceConnection conn; 35 MusicService ms; 36 String music_name; 37 File f; 38 Boolean isPlay = false; 39 ListView lv; 40 MusicAdapter adapter; 41 List<Mp3Info> datas; 42 Button btn; 43 int current; 44 SeekBar sb; 45 46 @Override 47 protected void onCreate(Bundle savedInstanceState) { 48 super.onCreate(savedInstanceState); 49 setContentView(R.layout.activity_main); 50 //初始化数据 51 init(); 52 } 53 54 private void init() { 55 btn = (Button) findViewById(R.id.btnPlay); 56 lv = (ListView) findViewById(R.id.lv_musicList); 57 //获取手机中 的音乐数据 58 datas = MediaUtil.getMp3Infos(MainActivity.this); 59 sb = (SeekBar) findViewById(R.id.sb); 60 adapter = new MusicAdapter(MainActivity.this, datas); 61 lv.setAdapter(adapter); 62 lv.setOnItemClickListener(new OnItemClickListener() { 63 @Override 64 public void onItemClick(AdapterView<?> parent, View view, 65 int position, long id) { 66 //点击哪一项就播放哪一项(前提条件是它是音乐文件) 67 current = position; 68 f = new File(datas.get(current).getUrl()); 69 if (f.exists() && f.length() > 0) { 70 play(); 71 } else { 72 Toast.makeText(getApplicationContext(), "该文件不是音乐", 0) 73 .show(); 74 } 75 } 76 }); 77 // 注册广播 78 broadcast(); 79 // 服务 80 service(); 81 } 82 83 private void broadcast() { 84 // TODO Auto-generated method stub 85 MusicBroadcast mb = new MusicBroadcast(); 86 IntentFilter ifilter = new IntentFilter(); 87 ifilter.addAction("com.firefly.music"); 88 ifilter.addAction("android.intent.action.PHONE_STATE"); 89 registerReceiver(mb, ifilter); 90 Log.e("TAG", "动态注册成功"); 91 } 92 93 private void service() { 94 // TODO Auto-generated method stub 95 conn = new ServiceConnection() { 96 97 @Override 98 public void onServiceDisconnected(ComponentName name) { 99 // TODO Auto-generated method stub 100 101 } 102 103 @Override 104 public void onServiceConnected(ComponentName name, IBinder service) { 105 // TODO Auto-generated method stub 106 MyBinder binder = (MyBinder) service; 107 ms = binder.getService(); 108 ms.setSeekBar(sb); 109 } 110 }; 111 112 // 绑定服务 113 Intent i = new Intent(this, MusicService.class); 114 bindService(i, conn, BIND_AUTO_CREATE); 115 } 116 117 public void playMusic(View v) { 118 switch (v.getId()) { 119 case R.id.btnPlay: 120 if (isPlay == false) { 121 play(); 122 } else { 123 pause(); 124 } 125 break; 126 // 上一首 127 case R.id.btnUp: 128 if (isPlay) { 129 if ((current - 1) >= 0) { 130 --current; 131 f = new File(datas.get(current).getUrl()); 132 play(); 133 } else { 134 Toast.makeText(getApplicationContext(), "已经是第一首了", 0).show(); 135 } 136 } 137 break; 138 // 下一首 139 case R.id.btnDown: 140 if ((current + 1) < datas.size()) { 141 ++current; 142 f = new File(datas.get(current).getUrl()); 143 play(); 144 } else { 145 Toast.makeText(getApplicationContext(), "已经是最后一首了", 0).show(); 146 } 147 break; 148 149 default: 150 break; 151 } 152 } 153 154 //播放 155 public void play() { 156 //调用服务中的播放方法 157 ms.PlayMusic(f.getAbsolutePath()); 158 btn.setBackgroundResource(R.drawable.play); 159 isPlay = true; 160 // 如果在播放中,就改变它的样式 161 adapter.setIndex(current); 162 adapter.notifyDataSetChanged(); 163 } 164 165 //暂停 166 public void pause() { 167 //调用服务中的暂停方法 168 ms.PauseMusic(); 169 btn.setBackgroundResource(R.drawable.pause); 170 isPlay = false; 171 } 172 173 // 新建广播 174 public class MusicBroadcast extends BroadcastReceiver { 175 176 @Override 177 public void onReceive(Context context, Intent intent) { 178 // 接收广播 179 if (intent.getAction() == "com.firefly.music") { 180 if (intent.getStringExtra("end").equals("播放完毕")) { 181 if ((current + 1) < datas.size()) { 182 //如果播放完毕了,就播放下一首 183 f = new File(datas.get(++current).getUrl()); 184 play(); 185 } else { 186 Toast.makeText(getApplicationContext(), "已经是最后一首了", 0) 187 .show(); 188 } 189 } 190 } else if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) { 191 //电话监听事件,如果要拨打电话就暂停音乐 192 String rs = getResultData(); 193 Toast.makeText(context, "你在拨打" + rs + "的电话了,现在我已经暂停音乐了。", 0).show(); 194 pause(); 195 } else { 196 //来电也要暂停音乐 197 TelephonyManager telMan = (TelephonyManager) context 198 .getSystemService(Context.TELEPHONY_SERVICE); 199 // 判断电话的状态 200 switch (telMan.getCallState()) { 201 case TelephonyManager.CALL_STATE_RINGING: 202 Log.e("TAG", "响铃"); 203 Toast.makeText(getApplicationContext(), "来电话了", 0).show(); 204 pause(); 205 break; 206 207 case TelephonyManager.CALL_STATE_IDLE: 208 Log.e("TAG", "挂断"); 209 Toast.makeText(getApplicationContext(), "您已经挂断,音乐将继续播放", 0).show(); 210 play(); 211 break; 212 213 case TelephonyManager.CALL_STATE_OFFHOOK: 214 Log.e("TAG", "接听"); 215 Toast.makeText(getApplicationContext(), "您正在接收电话,音乐已经暂停", 0).show(); 216 pause(); 217 break; 218 219 default: 220 break; 221 } 222 } 223 } 224 225 } 226 }
2-3:写一个接口类,统一管理服务(也可不写)
1 package com.firefly.service; 2 3 public interface IMusic { 4 // 新建一个接口,能用服务来实现它,在这里可以统一管理 5 public void PlayMusic(String music_name); 6 7 public void PauseMusic(); 8 9 public void PreviousMusic(); 10 11 public void NextMusic(); 12 }
2-4:服务类
1 package com.firefly.service; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.media.AudioManager; 6 import android.media.MediaPlayer; 7 import android.media.MediaPlayer.OnCompletionListener; 8 import android.media.MediaPlayer.OnPreparedListener; 9 import android.os.Binder; 10 import android.os.Handler; 11 import android.os.IBinder; 12 import android.os.Message; 13 import android.util.Log; 14 import android.widget.SeekBar; 15 import android.widget.SeekBar.OnSeekBarChangeListener; 16 import android.widget.Toast; 17 18 public class MusicService extends Service implements IMusic { 19 MediaPlayer music; 20 String music_name; 21 SeekBar sb; 22 Handler handler; 23 boolean flag = false;// 控制线程的播放 24 25 @Override 26 public void PlayMusic(String music_name) { 27 this.music_name = this.music_name; 28 if (music != null && music.isPlaying()) { 29 // 如果在播放的状态下切换到下一首,先把原来的停止、释放、置空,再新建 30 music.stop(); 31 music.release(); 32 music = null; 33 try { 34 music = new MediaPlayer(); 35 music.setAudioStreamType(AudioManager.STREAM_MUSIC); 36 music.setDataSource(music_name); 37 music.prepare(); 38 music.start(); 39 } catch (Exception e) { 40 // TODO Auto-generated catch block 41 e.printStackTrace(); 42 } 43 } else { 44 45 if (music != null) {// 这个状态可能是暂停可能是停止 46 // 如果只是把它暂停,重新开始就可以了 47 music.start(); 48 } else { 49 // 第一次播放这首音乐 50 try { 51 music = new MediaPlayer(); 52 music.setAudioStreamType(AudioManager.STREAM_MUSIC); 53 music.setDataSource(music_name); 54 music.prepareAsync(); 55 // 准备监听事件 56 music.setOnPreparedListener(new OnPreparedListener() { 57 @Override 58 public void onPrepared(MediaPlayer mp) { 59 music.start(); 60 // 设置SeekBar 61 int max = music.getDuration(); 62 int cur = music.getCurrentPosition(); 63 Log.e("TAG", max + "==" + cur); 64 sb.setMax(max); 65 sb.setProgress(cur); 66 sb.setEnabled(true); 67 } 68 }); 69 // 音乐播放完毕的监听器 70 music.setOnCompletionListener(new OnCompletionListener() { 71 72 @Override 73 public void onCompletion(MediaPlayer mp) { 74 music.stop(); 75 music.release(); 76 music = null; 77 // 发送一个广播,已经播放完毕了 78 Intent i = new Intent(); 79 i.setAction("com.firefly.music"); 80 i.putExtra("end", "播放完毕"); 81 sendBroadcast(i); 82 83 } 84 }); 85 } catch (Exception e) { 86 e.printStackTrace(); 87 } 88 } 89 flag = true; 90 } 91 // 这个线程用来控制进度条的更新 92 Thread th = new Thread(new Runnable() { 93 94 @Override 95 public void run() { 96 while (flag) { 97 try { 98 Thread.sleep(200); 99 } catch (InterruptedException e) { 100 // TODO Auto-generated catch block 101 e.printStackTrace(); 102 } 103 Message msg = new Message(); 104 msg.what = 0; 105 handler.sendMessage(msg); 106 } 107 } 108 }); 109 th.start(); 110 111 } 112 113 @Override 114 public void PauseMusic() { 115 // TODO Auto-generated method stub 116 if (music != null && music.isPlaying()) { 117 music.pause(); 118 flag = false; 119 } 120 } 121 122 @Override 123 public void PreviousMusic() { 124 // TODO Auto-generated method stub 125 126 } 127 128 @Override 129 public void NextMusic() { 130 // TODO Auto-generated method stub 131 132 } 133 134 public void setSeekBar(final SeekBar sb) { 135 this.sb = sb; 136 sb.setEnabled(false);// 没开始播放的时候进度条是不能拖动的 137 sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { 138 139 @Override 140 public void onStopTrackingTouch(SeekBar seekBar) { 141 // TODO Auto-generated method stub 142 if (music != null) { 143 music.seekTo(seekBar.getProgress()); 144 } 145 } 146 147 @Override 148 public void onStartTrackingTouch(SeekBar seekBar) { 149 // TODO Auto-generated method stub 150 151 } 152 153 @Override 154 public void onProgressChanged(SeekBar seekBar, int progress, 155 boolean fromUser) { 156 // TODO Auto-generated method stub 157 158 } 159 }); 160 161 handler = new Handler() { 162 @Override 163 public void handleMessage(Message msg) { 164 switch (msg.what) { 165 case 0: 166 if (music != null) { 167 // 播放和SeekBar同步 168 sb.setProgress(music.getCurrentPosition()); 169 } 170 break; 171 172 default: 173 break; 174 } 175 } 176 177 }; 178 179 } 180 181 @Override 182 public IBinder onBind(Intent intent) { 183 // TODO Auto-generated method stub 184 return new MyBinder(); 185 } 186 187 public class MyBinder extends Binder { 188 public MusicService getService() { 189 return MusicService.this; 190 } 191 } 192 193 }
3、清单文件
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.firefly.beautifulmusic" 4 android:versionCode="1" 5 android:versionName="1.0" > 6 7 <uses-sdk 8 android:minSdkVersion="14" 9 android:targetSdkVersion="19" /> 10 <!-- 读写SD卡的权限、读取电话状态的权限 --> 11 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> 12 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 13 <uses-permission android:name="android.permission.READ_PHONE_STATE" /> 14 15 <application 16 android:allowBackup="true" 17 android:icon="@drawable/ic_launcher" 18 android:label="@string/app_name" 19 android:theme="@style/AppTheme" > 20 <activity 21 android:name=".MainActivity" 22 android:label="@string/app_name" > 23 <intent-filter> 24 <action android:name="android.intent.action.MAIN" /> 25 26 <category android:name="android.intent.category.LAUNCHER" /> 27 </intent-filter> 28 </activity> 29 30 <service android:name="com.firefly.service.MusicService" > 31 </service> 32 </application> 33 34 </manifest>
4、效果图
种一棵树最早的时间是十年前,其次是现在。