对于Android的学习,需要掌握的东西有点多,需要我们认真,小心,不断的进取。前天突然有个想法,觉得Mp3播放器是一个可以练习的项目,于是在网上搜了下,发现有人已经写了博客,看了他们的博客后,我觉得他们说的一点很对,Mp3播放器基本用到了Android里面的许多知识点,做完这个过后,可能对于Android整个架构有了一定了解,我于是也想尝试下,于是准备边做,编写博客,来记录自己开发的过程,这个也许叫作项目开发日志吧。
第一个我的想法是先做:本地音乐播放器。
于是我用了个粗浅的方法来加载mp3文件,用Listview控件来显示所有的本地音乐。
实现的效果如下:
主界面:
左边显示的是音乐的ID,上面是文件名,下面是歌手,右边是个点击按钮,但是这个按钮的功能现在还没做。
播放界面如下:
实现的功能:点击主界面的歌,进入播放界面播放。
后退回来,再次点击主界面的歌,进入播放界面重新播放。
上一首和下一首的播放功能还没做。这个需要获得mp3的路径,目前方法不是很好,感觉比较挫,等想到新方法,在来考虑这个功能。
其实最为重要的是:获得mp3的信息和播放service的实现,这个最为重要。
mp3信息类:
public class Mp3Info { private String name; private long ID; private String title;//音乐标题 private String artist;//艺术家 private long duration;//时长 private long size; //文件大小 private String url; //文件路径 public String getName() { return this.name; } public void setName(String name) { this.name =name; } public String getTitle() { return this.title; } public void setTitle(String title) { this.title =title; } public void setArtist(String artist) { this.artist =artist; } public String getArtist(){ return this.artist; } public String getUrl(){ return this.url; } public void setUrl(String url) { this.url =url; } public void setID(long id) { this.ID =id; } public long getID(){ return this.ID; } public long getDuration(){ return this.duration; } public long getSize() { return this.size; } public void setDuration(long duration){ this.duration =duration; } public void setSize(long size){ this.size = size; }}
获得mp3的信息函数
public List<Mp3Info> getMp3Infos() { Cursor cursor = getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); List<Mp3Info> mp3Infos = new ArrayList<Mp3Info>(); for (int i = 0; i < cursor.getCount(); i++) { Mp3Info mp3Info = new Mp3Info(); cursor.moveToNext(); long id = cursor.getLong(cursor .getColumnIndex(MediaStore.Audio.Media._ID)); //音乐id String title = cursor.getString((cursor .getColumnIndex(MediaStore.Audio.Media.TITLE)));//音乐标题 String artist = cursor.getString(cursor .getColumnIndex(MediaStore.Audio.Media.ARTIST));//艺术家 long duration = cursor.getLong(cursor .getColumnIndex(MediaStore.Audio.Media.DURATION));//时长 long size = cursor.getLong(cursor .getColumnIndex(MediaStore.Audio.Media.SIZE)); //文件大小 String url = cursor.getString(cursor .getColumnIndex(MediaStore.Audio.Media.DATA)); //文件路径 int isMusic = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));//是否为音乐 if (isMusic != 0) { //只把音乐添加到集合当中 mp3Info.setID(id); mp3Info.setTitle(title); mp3Info.setArtist(artist); mp3Info.setDuration(duration); mp3Info.setSize(size); mp3Info.setUrl(url); mp3Infos.add(mp3Info); } } return mp3Infos; }
这是调用文件数据库查找音频,并将文件信息保存在结构体里面。
接下来介绍建立MusicService.java
public class MusicService extends Service { // mp3的绝对路径。 String path; //一个Binder用来和Activity来交互 class MyBinder extends Binder { public Service getService(){ return MusicService.this; } } @Override //每次程序执行的时候需要调用的函数 public int onStartCommand(Intent intent, int flags, int startId) { path =intent.getStringExtra("url"); init(); if(mediaPlayer.isPlaying()) { pause(); } return super.onStartCommand(intent, flags, startId); } IBinder musicBinder = new MyBinder(); //播放音乐的媒体类 MediaPlayer mediaPlayer; private String TAG = "MyService"; //第一次创建执行,或者service结束在开启的时候执行 @Override public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate() executed"); } //必须重载的方法 @Override public IBinder onBind(Intent arg0) { // TODO Auto-generated method stub //当绑定后,返回一个musicBinder return musicBinder; } //初始化音乐播放 void init(){ //进入Idle mediaPlayer = new MediaPlayer(); try { //初始化 mediaPlayer.setDataSource(path); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); // prepare 通过异步的方式装载媒体资源 mediaPlayer.prepareAsync(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } //返回当前的播放进度,是double类型,即播放的百分比 public double getProgress(){ int position = mediaPlayer.getCurrentPosition(); int time = mediaPlayer.getDuration(); double progress = (double)position / (double)time; return progress; } //通过activity调节播放进度 public void setProgress(int max , int dest){ int time = mediaPlayer.getDuration(); mediaPlayer.seekTo(time*dest/max); } //测试播放音乐 public void play(){ if(mediaPlayer != null){ mediaPlayer.start(); } } //暂停音乐 public void pause() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); } } //service 销毁时,停止播放音乐,释放资源 @Override public void onDestroy() { // 在activity结束的时候回收资源 if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.stop(); mediaPlayer.release(); mediaPlayer = null; } super.onDestroy(); }}
接下来就是如何和activity交互,通过bindService函数来在activity里面绑定个服务。需要用到ServiceConnection函数。
如何通过处理来获得音乐播放的进度,并将数据从service返回到activity里面呢?android里面通过Handler来处理,通过重写handleMessage方法来得到service里面返回来的信息。在主线程里面,我们的等待时间不能超过5秒,否则就会出现UI无法刷新问题,我们这里用到了SeekBar这个进度条,所以这个UI更新的问题需要放在另外一个线程里面,这个时候需要重写Runnable接口。
具体看代码:MusicActivity.java
public class MusicActivity extends Activity { private ImageView MusicPlay; private ImageView MusicNext; private ImageView MusicPrevious; Boolean mBound = false; //记录鼠标点击了几次 boolean flag =false; MusicService mService; SeekBar seekBar; //多线程,后台更新UI Thread myThread; //控制后台线程退出 boolean playStatus = true; //处理进度条更新 Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: //从bundle中获取进度,是double类型,播放的百分比 double progress = msg.getData().getDouble("progress"); //根据播放百分比,计算seekbar的实际位置 int max = seekBar.getMax(); int position = (int) (max * progress); //设置seekbar的实际位置 seekBar.setProgress(position); break; default: break; } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.musicplay); MusicPlay = (ImageView) findViewById(R.id.Musicplay); MusicNext = (ImageView) findViewById(R.id.musicnext); MusicPrevious = (ImageView) findViewById(R.id.musicprevious); //定义一个新线程,用来发送消息,通知更新UI myThread = new Thread(new UpdateProgress()); //绑定service; Intent serviceIntent = new Intent(MusicActivity.this, MusicService.class); //如果未绑定,则进行绑定,第三个参数是一个标志,它表明绑定中的操作.它一般应是BIND_AUTO_CREATE,这样就会在service不存在时创建一个 if (!mBound) { bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE); } seekBar = (SeekBar) findViewById(R.id.MusicProgress); seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { //手动调节进度 // TODO Auto-generated method stub //seekbar的拖动位置 int dest = seekBar.getProgress(); //seekbar的最大值 int max = seekBar.getMax(); //调用service调节播放进度 mService.setProgress(max, dest); } @Override public void onProgressChanged(SeekBar arg0, int arg1, boolean arg2) { // TODO Auto-generated method stub } @Override public void onStartTrackingTouch(SeekBar arg0) { // TODO Auto-generated method stub } }); MusicPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { if (mBound&&flag) { MusicPlay.setImageDrawable(getResources().getDrawable(R.drawable.musicpause)); mService.pause(); flag =false; }else{ MusicPlay.setImageDrawable(getResources().getDrawable(R.drawable.musicplay)); mService.play(); flag =true; } } }); } //实现runnable接口,多线程实时更新进度条 public class UpdateProgress implements Runnable { //通知UI更新的消息 //用来向UI线程传递进度的值 Bundle data = new Bundle(); //更新UI间隔时间 int milliseconds = 100; double progress; @Override public void run() { // TODO Auto-generated method stub //用来标识是否还在播放状态,用来控制线程退出 while (playStatus) { try { //绑定成功才能开始更新UI if (mBound) { //发送消息,要求更新UI Message msg = new Message(); data.clear(); progress = mService.getProgress(); msg.what = 0; data.putDouble("progress", progress); msg.setData(data); mHandler.sendMessage(msg); } Thread.sleep(milliseconds); //Thread.currentThread().sleep(milliseconds); //每隔100ms更新一次UI } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } /** * Defines callbacks for service binding, passed to bindService() */ private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder binder) { // We've bound to LocalService, cast the IBinder and get LocalService instance MusicService.MyBinder myBinder = (MusicService.MyBinder) binder; //获取service mService = (MusicService) myBinder.getService(); //绑定成功 mBound = true; //开启线程,更新UI myThread.start(); MusicPlay.setImageDrawable(getResources().getDrawable(R.drawable.musicplay)); mService.play(); flag =true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } }; public boolean onCreateOptionsMenu(Menu menu){ // Inflate the menu; this adds items to the action bar if it is present. // getMenuInflater().inflate(R.menu.main, menu); return true; } public void onDestroy() { //销毁activity时,要记得销毁线程 playStatus = false; super.onDestroy(); }}
基本难点都介绍完了,现在看下MainActivity.java代码:
public class MainActivity extends Activity implements AdapterView.OnItemClickListener { //Music的listview控件 private ListView MusicList; // 存储数据的数组列表 ArrayList<HashMap<String, Object>> MusiclistData = new ArrayList<HashMap<String, Object>>(); // 适配器 private SimpleAdapter MusicListAdapter; List<Mp3Info> mp3Infos; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MusicList = (ListView) findViewById(R.id.listviewmusic); mp3Infos = getMp3Infos(); GetData(mp3Infos); MusicListAdapter = new SimpleAdapter( this, MusiclistData, R.layout.listmusic, new String[]{"ID", "Title", "Artist", "Icon"}, new int[]{R.id.MusicID, R.id.Musictitle, R.id.MusicArtist, R.id.MusicIcon} ); //赋予数据 MusicList.setAdapter(MusicListAdapter); MusicList.setOnItemClickListener(MusiclistListen); } AdapterView.OnItemClickListener MusiclistListen = new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { //Toast.makeText(MainActivity.this, String.valueOf(l), Toast.LENGTH_SHORT).show(); //判断当前服务是否已经开启 if(isServiceRunning(getBaseContext(),"com.flashmusic.MusicService")){ stopService(new Intent(MainActivity.this, MusicService.class)); } Intent intent = new Intent(); intent.putExtra("url", mp3Infos.get(i).getUrl()); intent.setClass(MainActivity.this, MusicService.class); //启动服务 startService(intent); //启动音乐播放界面 startActivity(new Intent(MainActivity.this,MusicActivity.class)); } }; public static boolean isServiceRunning(Context mContext,String className) { boolean isRunning = false; ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningServiceInfo> serviceList= activityManager.getRunningServices(50); if (!(serviceList.size()>0)) { return false; } for (int i=0; i<serviceList.size(); i++) { String a =serviceList.get(i).service.getClassName(); if (serviceList.get(i).service.getClassName().equals(className) == true) { isRunning = true; break; } } return isRunning; } public void GetData(List<Mp3Info> mp3Infos) { for (int i = 0; i < mp3Infos.size(); i++) { HashMap<String, Object> map = new HashMap<String, Object>(); map.put("ID", i + 1); map.put("Title", mp3Infos.get(i).getTitle()); map.put("Artist", mp3Infos.get(i).getArtist()); map.put("Icon", R.drawable.musicicon); MusiclistData.add(map); } } public List<Mp3Info> getMp3Infos() { Cursor cursor = getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); List<Mp3Info> mp3Infos = new ArrayList<Mp3Info>(); for (int i = 0; i < cursor.getCount(); i++) { Mp3Info mp3Info = new Mp3Info(); cursor.moveToNext(); long id = cursor.getLong(cursor .getColumnIndex(MediaStore.Audio.Media._ID)); //音乐id String title = cursor.getString((cursor .getColumnIndex(MediaStore.Audio.Media.TITLE)));//音乐标题 String artist = cursor.getString(cursor .getColumnIndex(MediaStore.Audio.Media.ARTIST));//艺术家 long duration = cursor.getLong(cursor .getColumnIndex(MediaStore.Audio.Media.DURATION));//时长 long size = cursor.getLong(cursor .getColumnIndex(MediaStore.Audio.Media.SIZE)); //文件大小 String url = cursor.getString(cursor .getColumnIndex(MediaStore.Audio.Media.DATA)); //文件路径 int isMusic = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));//是否为音乐 if (isMusic != 0) { //只把音乐添加到集合当中 mp3Info.setID(id); mp3Info.setTitle(title); mp3Info.setArtist(artist); mp3Info.setDuration(duration); mp3Info.setSize(size); mp3Info.setUrl(url); mp3Infos.add(mp3Info); } } return mp3Infos; } @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { }}
使用SimpleAdapter来加载数据,可以更好的定义子布局文件。
接下来布局文件就不介绍了,只贴下代码:
activity_main.xml,定义ListView控件
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"><ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/listviewmusic"></ListView></LinearLayout>
播放界面布局:musicplay.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="horizontal" android:background="#d8e4f3"><LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/linearLayout" android:background="#295b75" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginBottom="50dp"> <ImageView android:layout_width="40dp" android:layout_height="40dp" android:id="@+id/imageView" android:layout_weight="1" android:layout_margin="10dp"/> <ImageView android:layout_width="40dp" android:layout_height="40dp" android:id="@+id/musicprevious" android:layout_weight="1" android:layout_margin="10dp" android:src="@drawable/musicprevious"/> <ImageView android:layout_width="40dp" android:layout_height="40dp" android:id="@+id/Musicplay" android:layout_weight="1" android:layout_margin="10dp" android:src="@drawable/musicpause"/> <ImageView android:layout_width="40dp" android:layout_height="40dp" android:id="@+id/musicnext" android:layout_weight="1" android:layout_margin="10dp" android:src="@drawable/musicnext"/> <ImageView android:layout_width="40dp" android:layout_height="40dp" android:id="@+id/musicmenu" android:layout_weight="1" android:layout_margin="10dp"/></LinearLayout> <SeekBar android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/MusicProgress" android:max="100" android:indeterminate="false" android:layout_above="@+id/linearLayout" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_marginBottom="20dp"/></RelativeLayout>
listmusic.xml,自定义子布局:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="60dp" android:orientation="vertical"> <TextView android:layout_width="70dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:text="Small Text" android:id="@+id/MusicID" android:gravity="center" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:layout_alignParentTop="true"/> <TextView android:layout_width="200dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:text="Small Text" android:id="@+id/Musictitle" android:textStyle="bold" android:layout_alignParentTop="true" android:layout_toRightOf="@+id/MusicID" android:layout_toEndOf="@+id/MusicID" android:layout_marginLeft="20dp" android:layout_marginTop="10dp" android:textSize="15dp" android:singleLine="true" android:ellipsize="end"/> <TextView android:layout_width="220dp" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:text="Small Text" android:id="@+id/MusicArtist" android:textColor="#878383" android:layout_toRightOf="@+id/MusicID" android:layout_toEndOf="@+id/MusicID" android:layout_marginLeft="20dp" android:layout_below="@+id/Musictitle" android:layout_marginTop="7dp" android:textSize="12dp" android:ellipsize="end" android:singleLine="true"/> <ImageView android:layout_width="25dp" android:layout_height="25dp" android:id="@+id/MusicIcon" android:layout_centerVertical="true" android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_marginRight="30dp" /></RelativeLayout>
这篇文章就介绍到这里面,现在实现的功能只能够点击播放,然后点击暂停和播放,拖动滑动条实现快进和后退。
上一首和下一首还有播放模式都还未做完,具体看下篇文章的介绍。目前正在构思和参考别人的实现。
哈哈,继续加油,共同分享,不断提高自己的技术。
介绍service
源码下载:源代码下载地址