简易AndroidMusicBox实现

app下载链接,欢迎给我的所谓的app找bug,谢谢指教!听朋友反映,问题还不少,有的手机根本无法打开app,有的出现歌曲列表就闪退,正在努力debug,见谅,浪费各位流量了!
http://pan.baidu.com/s/1jHNa5Bg

前言

MusicBox的制作过程涉及到不少知识,如四大组件、MediaStore等等。制作完成后收获颇丰,现在带大家制作自己的音乐播放器。

开发思路

程序由一个Activity(交互)和Service(后台播放音乐)构成。当发生单击事件时,Activity发送广播通知Service改变播放状态;Service将当前播放状态和当前播放的是哪一首歌曲广播给Activity供其更新UI界面。而歌曲信息(歌名、艺术家、路径等)则由系统的多媒体ContentProvider提供。

musicBox开发思路图

开发细节

1.布局界面有两个TextView及5个ImageButton(分别代表上一首、播放/暂停、下一首、停止、显示歌曲列表).见文末效果展示图.5个Button均注册监听器,当单击事件发生时向Service发送广播。为了让Service知道哪个按钮被按下了,可以这样操作:

intent.putExra("contral",xxx);  //xxx是Button的flag,比如
//“上一首”这个Button的flag设置为0x123
sendBrocast(intent);

2.Activity创建三个String数组,分别存储歌曲的title,artist,display_name,而Service中则创建一个String数组,用于存储歌曲的path。可以做到4个数组的同一下标对应同一首歌曲。这样做的好处是Activity和Service之间只需要交换数组的下标就可以实现TextView的更新以及歌曲的切换,避免了直接传输字符串。

3.Service设置一个status标志播放状态,并广播给Activity。Activity依据此改变表示播放/暂停的那个ImageButton显示的图片。

4.当用户点击用于显示歌曲列表的Button时,可以使用AlertDialog加载一个单选列表对话框,这相比跳转到另一Activity的思路更加简单易行。要使用户点击列表的某一首歌时能切歌并使列表消失,只需覆写 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首歌被点击
                            sendBroadcast(intent);
                           dialog.dismiss();//使dialog消失
                        }
                    }

5.Service使用MediaPlayer播放音乐,为了使在无用户交互时能顺序播放,为MediaPlayer对象注册播放完毕监听器,如下:

 //player为MediaPlayer对象
 player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) {
            player.reset();
            try {
                current=(current+1)%(num-1);//current是正在播放的歌曲的数组下标
                player.setDataSource(path[current]);//path[]为路径数组
                player.prepare();
                player.start();
            }catch (IOException e){
                e.printStackTrace();
            }
            Intent intent=new Intent(MainActivity.actionByService);
            intent.putExtra("current",current);
            sendBroadcast(intent);//把current广播给Activity
        }
    });

重点知识讲解

MediaStore获取歌曲信息

类MediaStore可以看作是安卓Media数据库的配置说明(说明该数据库表名、列名、访问的uri等等),MediaStore这个类里面包含了ImageAudioVideo这几个内部类。其中类Audio中有以下表名:MediaGenresPlaylistsArtistsAlbums,每一个表都有其供外部访问的uri和许多String类型的列名。顾名思义,要找专辑信息应当在Albums表找,其他类似。为了有个直观印象,看一下Media表的源码:

Media表源码1
Media表源码2
AudioColums
AudioColums

我们看到AudioColums继承了MediaColums,后者有title、display_name等列名,如图:

MediaColums
MediaColums

必备知识

1.AlertDialog创建对话框知识
2.SQLite数据库知识以及通过Cursor对象访问的知识
掌握了以上内容看起源码才能不费力气,鉴于篇幅限制,读者自行补充

源码

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{
//最大歌曲数目
final static int  max_num=500;
//实际歌曲数目
int real_num=0;
//存储歌曲信息的数组
private String[] Artist=new String[max_num];
private String[] songname=new String[max_num];
private String[] display_name=new String[max_num];
//当前播放的歌曲的下标
int current=0;
//用于注册BrocastReceiver的action
/**
在Activity注册BrocastReceiver用到actionByService
在Service注册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) ;
    //为按钮注册监听器
    previous.setOnClickListener(this);
    playOrpause.setOnClickListener(this);
    stop.setOnClickListener(this);
    next.setOnClickListener(this);
    menu.setOnClickListener(this);
    //读取MediaDataBase的信息(为数组赋值)
    prepareInformation();
    //注册BrocastReceiver
    IntentFilter filter=new IntentFilter();
    filter.addAction(actionByService);
    receiver=new ActivityReceiver();
    registerReceiver(receiver,filter);
    //启动Service
    this.intent=new Intent(MainActivity.this,myService.class);
    startService(intent);
}

@Override
protected void onDestroy() {
    //取消对BrocastReceiver的注册
    unregisterReceiver(receiver);
    //停止myService
    stopService(this.intent);
    super.onDestroy();
    return;
}

//自定义BrocastReceiver,用于处理Service发过来的广播
private class ActivityReceiver extends BroadcastReceiver{
    @Override
        //覆写onReceive
    public void onReceive(Context context, Intent intent) {
        //update获取当前MediaPlayer的播放状态
        int update=intent.getIntExtra("update",-1);
        //current获取当前播放歌曲
        int current=intent.getIntExtra("current",-1);
        //更新文本框内容
        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);
    //根据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:
            //加载单选列表对话框
            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(){
   //创建ContentResolver
   ContentResolver resolver=getContentResolver();
   //读取艺术家信息
   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已启动---");
    
    IntentFilter filter=new IntentFilter();
    filter.addAction(MainActivity.actionByActivity);
    receiver=new ServiceReceiver();
    registerReceiver(receiver,filter);
    
   if((player=new MediaPlayer())!=null) System.out.println("---MediaPlayer创建成功---");
    else System.out.println("---MediaPlayer创建失败---");
   
    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    //记得要在此文件中注册myService,
//并且为程序添加访问外部存储器的权限,由于两个BrocastReceiver均使用动态注册方式,
//故无须在此文件注册

<?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>

效果展示

初始界面
正在播放,第一行中间的按钮呈三角形
歌曲列表

由于知识水平限制,app难免存在各种bug,请各位指教,或者有其他好的开发思路,欢迎共同交流!
app下载链接,欢迎给我的所谓的app找bug,谢谢指教!
http://pan.baidu.com/s/1jHNa5Bg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 本人初学Android,最近做了一个实现安卓简单音乐播放功能的播放器,收获不少,于是便记录下来自己的思路与知识总结...
    落日柳风阅读 19,068评论 2 41
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,350评论 0 17
  • 1.什么是Activity?问的不太多,说点有深度的 四大组件之一,一般的,一个用户交互界面对应一个activit...
    JoonyLee阅读 5,724评论 2 51
  • 中午过后,感觉自己今天好像没太做什么事情,心里有种失落感和不满足感。 看从什么角度讲,从高效能方面,今天比较低效,...
    荣涵阅读 166评论 0 0