app下載鏈接,歡迎給我的所謂的app找bug,謝謝指教!聽朋友反映,問題還不少,有的手機(jī)根本無法打開app,有的出現(xiàn)歌曲列表就閃退,正在努力debug,見諒,浪費(fèi)各位流量了!
http://pan.baidu.com/s/1jHNa5Bg
前言
MusicBox的制作過程涉及到不少知識(shí),如四大組件、MediaStore等等。制作完成后收獲頗豐,現(xiàn)在帶大家制作自己的音樂播放器。
開發(fā)思路
程序由一個(gè)Activity(交互)和Service(后臺(tái)播放音樂)構(gòu)成。當(dāng)發(fā)生單擊事件時(shí),Activity發(fā)送廣播通知Service改變播放狀態(tài);Service將當(dāng)前播放狀態(tài)和當(dāng)前播放的是哪一首歌曲廣播給Activity供其更新UI界面。而歌曲信息(歌名、藝術(shù)家、路徑等)則由系統(tǒng)的多媒體ContentProvider提供。

開發(fā)細(xì)節(jié)
1.布局界面有兩個(gè)TextView及5個(gè)ImageButton(分別代表上一首、播放/暫停、下一首、停止、顯示歌曲列表).見文末效果展示圖.5個(gè)Button均注冊(cè)監(jiān)聽器,當(dāng)單擊事件發(fā)生時(shí)向Service發(fā)送廣播。為了讓Service知道哪個(gè)按鈕被按下了,可以這樣操作:
intent.putExra("contral",xxx); //xxx是Button的flag,比如
//“上一首”這個(gè)Button的flag設(shè)置為0x123
sendBrocast(intent);
2.Activity創(chuàng)建三個(gè)String數(shù)組,分別存儲(chǔ)歌曲的title,artist,display_name,而Service中則創(chuàng)建一個(gè)String數(shù)組,用于存儲(chǔ)歌曲的path。可以做到4個(gè)數(shù)組的同一下標(biāo)對(duì)應(yīng)同一首歌曲。這樣做的好處是Activity和Service之間只需要交換數(shù)組的下標(biāo)就可以實(shí)現(xiàn)TextView的更新以及歌曲的切換,避免了直接傳輸字符串。
3.Service設(shè)置一個(gè)status標(biāo)志播放狀態(tài),并廣播給Activity。Activity依據(jù)此改變表示播放/暫停的那個(gè)ImageButton顯示的圖片。
4.當(dāng)用戶點(diǎn)擊用于顯示歌曲列表的Button時(shí),可以使用AlertDialog加載一個(gè)單選列表對(duì)話框,這相比跳轉(zhuǎn)到另一Activity的思路更加簡(jiǎn)單易行。要使用戶點(diǎn)擊列表的某一首歌時(shí)能切歌并使列表消失,只需覆寫 DialogInterface.OnClickListener的onClick方法即可,如下:
DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent=new Intent(actionByActivity);//actionByActivity是自定義的action
intent.putExtra("contral",which);//which從0開始,表示第which+1首歌被點(diǎn)擊
sendBroadcast(intent);
dialog.dismiss();//使dialog消失
}
}
5.Service使用MediaPlayer播放音樂,為了使在無用戶交互時(shí)能順序播放,為MediaPlayer對(duì)象注冊(cè)播放完畢監(jiān)聽器,如下:
//player為MediaPlayer對(duì)象
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
player.reset();
try {
current=(current+1)%(num-1);//current是正在播放的歌曲的數(shù)組下標(biāo)
player.setDataSource(path[current]);//path[]為路徑數(shù)組
player.prepare();
player.start();
}catch (IOException e){
e.printStackTrace();
}
Intent intent=new Intent(MainActivity.actionByService);
intent.putExtra("current",current);
sendBroadcast(intent);//把current廣播給Activity
}
});
重點(diǎn)知識(shí)講解
MediaStore獲取歌曲信息
類MediaStore可以看作是安卓Media數(shù)據(jù)庫(kù)的配置說明(說明該數(shù)據(jù)庫(kù)表名、列名、訪問的uri等等),MediaStore這個(gè)類里面包含了Image、Audio、Video這幾個(gè)內(nèi)部類。其中類Audio中有以下表名:Media、Genres、Playlists、Artists、Albums,每一個(gè)表都有其供外部訪問的uri和許多String類型的列名。顧名思義,要找專輯信息應(yīng)當(dāng)在Albums表找,其他類似。為了有個(gè)直觀印象,看一下Media表的源碼:




我們看到AudioColums繼承了MediaColums,后者有title、display_name等列名,如圖:


必備知識(shí)
1.AlertDialog創(chuàng)建對(duì)話框知識(shí)
2.SQLite數(shù)據(jù)庫(kù)知識(shí)以及通過Cursor對(duì)象訪問的知識(shí)
掌握了以上內(nèi)容看起源碼才能不費(fèi)力氣,鑒于篇幅限制,讀者自行補(bǔ)充
源碼
MainActivity.java
-------------------------------------------------------------------------------------
package com.golfer.www.musicboxdemo;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.support.v7.app.AppCompatActivity;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.provider.MediaStore;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
//最大歌曲數(shù)目
final static int max_num=500;
//實(shí)際歌曲數(shù)目
int real_num=0;
//存儲(chǔ)歌曲信息的數(shù)組
private String[] Artist=new String[max_num];
private String[] songname=new String[max_num];
private String[] display_name=new String[max_num];
//當(dāng)前播放的歌曲的下標(biāo)
int current=0;
//用于注冊(cè)BrocastReceiver的action
/**
在Activity注冊(cè)BrocastReceiver用到actionByService
在Service注冊(cè)BrocastReceiver用到actionByActivity,完全可以將actionByActivity在myService.java里面定義
**/
static final String actionByActivity="MusicBoxDemo.action.actionByActivity";
static final String actionByService="MusicBoxDemo.action.actionByService";
//控件
TextView nameOfsong,artist;
ImageButton stop,playOrpause,previous,next,menu;
//BrocastReceiver
ActivityReceiver receiver;
//Intent
Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//匹配控件
nameOfsong=(TextView)findViewById(R.id.nameOfsong) ;
artist=(TextView)findViewById(R.id.artist) ;
previous=(ImageButton)findViewById(R.id.previous);
playOrpause=(ImageButton)findViewById(R.id.playOrpause);
stop=(ImageButton)findViewById(R.id.stop) ;
next=(ImageButton)findViewById(R.id.next) ;
menu=(ImageButton)findViewById(R.id.menu) ;
//為按鈕注冊(cè)監(jiān)聽器
previous.setOnClickListener(this);
playOrpause.setOnClickListener(this);
stop.setOnClickListener(this);
next.setOnClickListener(this);
menu.setOnClickListener(this);
//讀取MediaDataBase的信息(為數(shù)組賦值)
prepareInformation();
//注冊(cè)BrocastReceiver
IntentFilter filter=new IntentFilter();
filter.addAction(actionByService);
receiver=new ActivityReceiver();
registerReceiver(receiver,filter);
//啟動(dòng)Service
this.intent=new Intent(MainActivity.this,myService.class);
startService(intent);
}
@Override
protected void onDestroy() {
//取消對(duì)BrocastReceiver的注冊(cè)
unregisterReceiver(receiver);
//停止myService
stopService(this.intent);
super.onDestroy();
return;
}
//自定義BrocastReceiver,用于處理Service發(fā)過來的廣播
private class ActivityReceiver extends BroadcastReceiver{
@Override
//覆寫onReceive
public void onReceive(Context context, Intent intent) {
//update獲取當(dāng)前MediaPlayer的播放狀態(tài)
int update=intent.getIntExtra("update",-1);
//current獲取當(dāng)前播放歌曲
int current=intent.getIntExtra("current",-1);
//更新文本框內(nèi)容
if(current>=0){
nameOfsong.setText(songname[current]);
artist.setText(Artist[current]);
}
//更新按鈕背景
switch(update){
case 0x11:
playOrpause.setImageResource(android.R.drawable.ic_media_pause);
break;
case 0x12:
playOrpause.setImageResource(android.R.drawable.ic_media_play);
break;
case 0x13:
playOrpause.setImageResource(android.R.drawable.ic_media_pause);
break;
}
}
}
//覆寫onClick
public void onClick(View source){
final Intent intent=new Intent(actionByActivity);
//根據(jù)source的ID為intent的Extra屬性賦不同的值
switch(source.getId()){
case R.id.previous:
intent.putExtra("contral",0x124);
break;
case R.id.playOrpause:
intent.putExtra("contral",0x125);
break;
case R.id.stop:
intent.putExtra("contral",0x126);
break;
case R.id.next:
intent.putExtra("contral",0x127);
break;
case R.id.menu:
//加載單選列表對(duì)話框
final AlertDialog dialog=new AlertDialog.Builder(MainActivity.this).setTitle("歌曲列表").setSingleChoiceItems(display_name, -1,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent=new Intent(actionByActivity);
intent.putExtra("contral",which);
sendBroadcast(intent);
System.out.println("which:"+which);
dialog.dismiss();
}
}).create();
dialog.show();
}
sendBroadcast(intent);
}
//讀取歌曲信息
private void prepareInformation(){
//創(chuàng)建ContentResolver
ContentResolver resolver=getContentResolver();
//讀取藝術(shù)家信息
Cursor cursor= resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,null,null,null,null );
int index=0;
while(cursor.moveToNext()){
Artist[index++]=cursor.getString(cursor.getColumnIndex(MediaStore.Audio.AudioColumns.ARTIST));
}
real_num=index;
index=0;
//讀取歌曲名
Cursor cursor2= resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,null,null,null,null );
while(cursor2.moveToNext()){
songname[index++]=cursor2.getString(cursor2.getColumnIndex(MediaStore.MediaColumns.TITLE));
}
index=0;
//讀取歌曲的展示名
Cursor cursor3= resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,null,null,null,null );
while(cursor3.moveToNext()){
display_name[index++]=cursor3.getString(cursor3.getColumnIndex(MediaStore.MediaColumns.TITLE));
}
}
}
myService.java
package com.golfer.www.musicboxdemo;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.IBinder;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.annotation.StringDef;
import java.io.IOException;
/**
* Created by Golfer on 2017/2/4.
*/
public class myService extends Service {
String[] path=new String[MainActivity.max_num];
MediaPlayer player;
int current=0;
int status=0x11;
int num;
ServiceReceiver receiver;
public void onCreate() {
System.out.println("---Service已啟動(dòng)---");
IntentFilter filter=new IntentFilter();
filter.addAction(MainActivity.actionByActivity);
receiver=new ServiceReceiver();
registerReceiver(receiver,filter);
if((player=new MediaPlayer())!=null) System.out.println("---MediaPlayer創(chuàng)建成功---");
else System.out.println("---MediaPlayer創(chuàng)建失敗---");
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
player.reset();
try {
current=(current+1)%(num-1);
player.setDataSource(path[current]);
player.prepare();
player.start();
}catch (IOException e){
e.printStackTrace();
}
Intent intent=new Intent(MainActivity.actionByService);
intent.putExtra("current",current);
sendBroadcast(intent);
}
});
ContentResolver resolver=getContentResolver();
Cursor cursor= resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,null,null,null,null );
int index=0;
while(cursor.moveToNext()){
path[index]=cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DATA));
System.out.println("path["+index+"]:"+path[index]);
index++;
}
num=index;
return;
}
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
unregisterReceiver(receiver);
player.release();
super.onDestroy();
return;
}
private class ServiceReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
int contral=intent.getIntExtra("contral",-1);
if(contral>=0) {
switch (contral) {
case 0x124:
if (status == 0x12 && current > 0) {
player.reset();
try {
player.setDataSource(path[current - 1]);
player.prepare();
player.start();
System.out.println("---成功播放上一首歌曲---");
current = current - 1;
} catch (IOException E) {
E.printStackTrace();
}
}
break;
case 0x125:
if (status == 0x11) {
try {
player.reset();
player.setDataSource(path[current]);
player.prepare();
player.start();
status = 0x12;
} catch (IOException e) {
e.printStackTrace();
}
} else if (status == 0x12) {
player.pause();
status = 0x13;
} else if (status == 0x13) {
player.start();
status = 0x12;
}
break;
case 0x126:
if (status == 0x12 || status == 0x13) {
player.stop();
status = 0x11;
}
break;
case 0x127:
if (status == 0x12 && current < num - 1) {
player.reset();
try {
player.setDataSource(path[current + 1]);
player.prepare();
player.start();
System.out.println("---成功播放下一首歌曲---");
current = current + 1;
} catch (IOException E) {
E.printStackTrace();
}
}
break;
default:
player.reset();
try {
player.setDataSource(path[contral]);
player.prepare();
player.start();
current=contral;
System.out.println("---成功切換播放第"+(current+1)+"首歌---");
}catch (IOException e){
e.printStackTrace();
}
}
}
Intent intent1=new Intent(MainActivity.actionByService);
intent1.putExtra("update",status);
intent1.putExtra("current",current);
sendBroadcast(intent1);
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.golfer.www.musicboxdemo.MainActivity">
<TextView
android:text="歌曲名稱"
android:layout_height="wrap_content"
android:id="@+id/nameOfsong"
android:layout_alignParentStart="true"
android:layout_width="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@android:drawable/ic_media_next"
android:layout_alignTop="@+id/previous"
android:layout_alignParentEnd="true"
android:id="@+id/next" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@android:drawable/ic_media_previous"
android:layout_marginBottom="132dp"
android:id="@+id/previous"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@android:drawable/ic_media_pause"
android:id="@+id/playOrpause"
android:layout_alignTop="@+id/next"
android:layout_centerHorizontal="true" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@android:drawable/ic_lock_power_off"
android:id="@+id/stop"
android:layout_marginTop="29dp"
android:layout_below="@+id/playOrpause"
android:layout_toRightOf="@+id/previous"
android:layout_toEndOf="@+id/previous" />
<TextView
android:text="歌手"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/artist"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:layout_marginTop="44dp"
android:layout_below="@+id/nameOfsong"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@android:drawable/ic_menu_sort_by_size"
android:layout_alignBottom="@+id/stop"
android:layout_toLeftOf="@+id/next"
android:layout_toStartOf="@+id/next"
android:layout_marginRight="11dp"
android:layout_marginEnd="11dp"
android:id="@+id/menu" />
</RelativeLayout>
AndroidManifest.xml //記得要在此文件中注冊(cè)myService,
//并且為程序添加訪問外部存儲(chǔ)器的權(quán)限,由于兩個(gè)BrocastReceiver均使用動(dòng)態(tài)注冊(cè)方式,
//故無須在此文件注冊(cè)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.golfer.www.musicboxdemo">
<application
android:allowBackup="true"
android:icon="mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".myService">
</service>
</application>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>
效果展示



由于知識(shí)水平限制,app難免存在各種bug,請(qǐng)各位指教,或者有其他好的開發(fā)思路,歡迎共同交流!
app下載鏈接,歡迎給我的所謂的app找bug,謝謝指教!
http://pan.baidu.com/s/1jHNa5Bg