手把手教你做音乐播放器(五)音乐列表的存储(上)

第5节 播放列表的存取

关于播放列表的存取需要三个组件的协同配合,

  1. MusicListActivity:让用户选择多首或一首音乐,将用户的选择项,传递给MusicService
  2. MusicService:接收到添加列表的请求后,把数据交给PlayListContentProvider,进行存储;
  3. PlayListContentProvider:将播放列表存储到SQLite数据库中;

5.1 PlayListContentProvider的实现

自定义的ContentProvider与系统自带的ContentProvider在设计和用法上都是一样的。

5.1.1 地址设计

我们首先来学习一下ContentProvider的Uri地址。

任何对数据的操作范围无外乎,

  • 单条数据的操作:每一次操作增加一条数据,删除一条数据,修改一条数据,查询特定一条数据的详细内容;
  • 多条数据的操作:每一次操作增加多条数据,删除多条数据,修改多条数据,查询符合某个特征的多条数据;

例如,我们可能会向书架上“一次放一本书”-单条增,“一次取下一本书”-单条删,“一次问书架上有多少书”-多条查。

因此,仿照网站地址的设计方式,我们可以用如下的“网络地址”来表达我们希望进行的对ContentProvider的操作是针对单一一条数据还是同时多条数据:

  • xxx.xxx.xxx/items/1:针对单一一条数据,最后的数字代表特定一本书的编号(也可以使用书的名字来代替);
  • xxx.xxx.xxx/items:针对多条数据;

“网站”只要看到以上的格式就知道,要操作的是一条数据还是多条数据了。

每个ContentProvider就是一个“网站”,每个网站都有自己的“网址”。安卓系统为这个“网址”设计了如下的结构,

scheme://authority/path
  1. scheme:固定为“content”,相当于一个网址的“http”;
  2. authority:由开发者自己确定,通常把它写成这个ContentProvider的包名,例如“com.anddle.mycontentprovider”,它就相当于网址的地址“www.google.com”;
  3. path:根据查询内容的逻辑,由开发者自己决定,通常要分成多条数据和单一数据两类;

典型的例子就像这样,

多条数据
content://com.anddle.mycontentprovider/items

单一数据
content://com.anddle.mycontentprovider/items/1

这里面,

scheme:“content”

authority:“com.anddle.mycontentprovider”

path:“items”或者“items/1”

只要定义好了前面两种原则,外界(其他组件或者其他应用)就可以获取到ContentProvider中的内容了。

Urischeme字段是固定的,使用content:

authority定义成程序的包名com.anddle.mycontentprovider

path就像是网站内部的分类,依据网站的逻辑进行划分。

假设我们的ContentProvider提供书籍book和文件file两种内容的查询操作。而每种类型都可以进行单一数据的操作和多条数据的操作。

例如,

  • 操作所有书的信息:content://com.anddle.mycontentprovider/books;
  • 操作某本特定书的信息:content://com.anddle.mycontentprovider/books/8

针对我们这个音乐的例子,它的URI地址可能就是,

  • 操作多首音乐:content://com.anddle.anddlemusicprovider/songs;
  • 操作某首特定音乐:content://com.anddle.anddlemusicprovider/songs/8

5.1.2 创建ContentProvider

  1. 继承ContentProvider类,会要求我们实现getType() insert() delete() update() query() onCreate()等接口,

    public class PlayListContentProvider extends ContentProvider {
    
        @Override
        public boolean onCreate() {
    
            return true;
        }
    
        @Override
        public String getType(Uri uri) {
    
            throw new UnsupportedOperationException("Not yet implemented");
        }
    
        @Override
        public Uri insert(Uri uri, ContentValues values) {
    
            throw new UnsupportedOperationException("Not yet implemented");
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
    
            throw new UnsupportedOperationException("Not yet implemented");
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection,
                          String[] selectionArgs) {
    
            throw new UnsupportedOperationException("Not yet implemented");
        }
    
        @Override
        public Cursor query(Uri uri, String[] projection, String selection,
                            String[] selectionArgs, String sortOrder) {
    
            throw new UnsupportedOperationException("Not yet implemented");
        }
    
    }
  2. 定义提供给其他组件使用的“网络地址”URI,这里我们把它们定义成content://com.anddle.PlayListContentProvider/songs
    public class PlayListContentProvider extends ContentProvider {
    
        private static final String SCHEME = "content://";
    
        private static final String PATH_SONGS = "/songs";
    
        public static final String AUTHORITY = "com.anddle.PlayListContentProvider";
    
        //"content://com.anddle.PlayListContentProvider/songs"
        public static final Uri CONTENT_SONGS_URI = Uri.parse(SCHEME + AUTHORITY + PATH_SONGS);
    ......
    }
  3. 在响应函数中,根据Uri,做对应的操作,例如insert()函数中,就要实现对数据增插入的真正操作,不过现在我们把操作数据库的操作留到后面来讲解
    @Override
    public Uri insert(Uri uri, ContentValues values) {
    
        Uri result = null;?    //从ContentValues中取出数据,保存起来;返回保存数据的Uri地址,例如
        //content://com.anddle.PlayListContentProvider/songs/1
        //具体的插入方式我们在后面讲解
    
        return result;
    }

    类似的,其他delete() update() query()实现的函数做相同的处理。

  4. 对于getType(), 需要为每一种类型的Uri返回一种数据类型-MIME type,告诉调用者,当前这种Uri可以处理什么类型的数据。

    它的格式型如type\subtype,有很多知名的MIME type类型,例如application/pdf image/jpeg等等,可以在网上查找到公开的MIME type类型有哪些。也可以自定义自己应用支持的特殊MIME type类型。

    这里我们就返回一个空值,

    @Override
    public String getType(Uri uri) {
    
       return null;
    }

至此,一个ContentProvider的就完成了。不过它现在还没有添加上真正可以存储数据的功能。

5.1.3 声明ContentProvider

千万不要忘记,在应用的AndroidManifest.xml文件中,声明新添加的ContentProvider

<provider
    android:name=".MyContentProvider"
    android:authorities="com.anddle.PlayListContentProvider"
    android:enabled="true"
    android:exported="true" />

这里的android:authorities属性值,就要填写定义MyContentProvider时,代码中的那个,

public static final String AUTHORITY = "com.anddle.PlayListContentProvider";

android:exported属性如何设置成true,说明这个ContentProvider可以被其他应用使用(就像一个公共网站,可以被任何人访问),如果设置成false,说明它只能被自己所在的应用使用(就像一个内部网站,只能在公司内部访问)。

5.1.4 使用Android Studio创建

上面介绍了创建ContentProvider的原理,如果使用Android Studio做开发,可以更加方便的为我们创建ContentProvider相关的代码,

  1. 在项目上点击右键,选择New->other->ContentProvider

  2. 在弹出的对话中,填写上ContentProvider的名字和自定义的authorities,并选择Finish

创建成功后,自定义的ContentProvider框架就自动帮我们实现了;同时,替我们在AndroidManifest.xml文件中完成了对它的注册。剩下的就是要我们向里面添加自己的逻辑代码了。

从这里也能看到使用Android Studion开发环境的便利之处。

5.1.5 数据库存储

创建了ContentProvider后,它还只是虚有其表,不能保存任何数据。要保存数据,通常会让它使用数据库的方式实现。

  1. 假设数据库的名字叫做playlist.db,播放列表将存储在该数据库名叫playlist_table的表中。

    该表的结构如下,

    id name last_play_time song_uri album_uri duration
    1 国歌 0 xxxx xxxx 13908888
    2 小苹果 0 xxxx xxxx 13908888
    3 回家 0 xxxx xxxx 13908888

    其中,这些字段对应的数据类型分别是:

    id:自增的int型数据,作为每条数据的主键,每插入一条数据,该值将被数据库自动分配;

    name:字符类型的数据,存放歌曲的名称;

    last_play_time:long型数据,以毫秒为单位,记录该音乐上次播放到的时间;

    song_uri:字符型数据,记录每首音乐的uri地址,例如xxxxx;

    album_uri:字符型数据,记录每首音乐封面的uri地址,例如xxxxx;

    duration:long型数据,以毫秒为单位,记录该音乐一共可以播放的时长;

  2. 安卓系统为我们提供了一个方便的使用SQLite数据库的工具类SQLiteOpenHelper,通过它来创建、更新或者删除SQLite数据库。
    1. 继承SQLiteOpenHelper,并定义数据库中使用的表名称和字段;继承的时候,需要我们一定实现onCreate()onUpgrade()函数,前者实现对数据库的创建,后者告知开发者,数据库的版本有变化,让开发者有机会重新组织已存储的数据,

      public class DBHelper extends SQLiteOpenHelper {
      
          private final static String DB_NAME = "playlist.db";
          private final static int DB_VERSION = 1;
          public final static String PLAYLIST_TABLE_NAME = "playlist_table";
      
          public final static String ID = "id";
          public final static String NAME = "name";
          public final static String LAST_PLAY_TIME = "last_play_time";
          public final static String SONG_URI = "song_uri";
          public final static String ALBUM_URI = "album_uri";
          public final static String DURATION = "duration";
      
          public DBHelper(Context context) {
              super(context, DB_NAME, null, DB_VERSION);
          }
      
          @Override
          public void onCreate(SQLiteDatabase db) {
      
          }
      
          @Override
          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
      
          }
      
      }
    2. 创建数据库和更新数据库;
      public class DBHelper extends SQLiteOpenHelper {
      
          private final static String DB_NAME = "playlist.db";
          private final static int DB_VERSION = 1;
          public final static String PLAYLIST_TABLE_NAME = "playlist_table";
      
          public final static String ID = "id";
          public final static String NAME = "name";
          public final static String LAST_PLAY_TIME = "last_play_time";
          public final static String SONG_URI = "song_uri";
          public final static String ALBUM_URI = "album_uri";
          public final static String DURATION = "duration";
      
          public DBHelper(Context context) {
              super(context, DB_NAME, null, DB_VERSION);
          }
      
          @Override
          public void onCreate(SQLiteDatabase db) {
              //创建播放列表的存储表项
              String PLAYLIST_TABLE_CMD = "CREATE TABLE " + PLAYLIST_TABLE_NAME
              + "("
              + ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
              + NAME +" VARCHAR(256),"
              + LAST_PLAY_TIME +" LONG,"
              + SONG_URI +" VARCHAR(128),"
              + ALBUM_URI +" VARCHAR(128),"
              + DURATION + " LONG"
              + ");" ;
              db.execSQL(PLAYLIST_TABLE_CMD);
          }
      
          @Override
          public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
              //如果遇到数据库更新,我们简单的处理为删除以前的表,重新创建一张
              db.execSQL("DROP TABLE IF EXISTS "+ PLAYLIST_TABLE_NAME);
              onCreate(db);
          }
      
      }
    3. PlayListContentProvider的增删改查工作,将依赖于我们刚创建的DBHelper
      1. 创建DBHelper

        public class PlayListContentProvider extends ContentProvider {
        
            //声明成员变量,mDBHelper将帮助我们操作SQLite数据库
            private DBHelper mDBHelper;
            ......
        
            @Override
            public boolean onCreate() {
                //ContentProvider被创建的时候,获取DBHelper对象
                mDBHelper = new DBHelper(getContext());
        
                return true;
            }
            ......
        }
      2. 添加播放列表的操作,每次向数据库插入一条信息,
        public class PlayListContentProvider extends ContentProvider {
            ......
        
            //实现insert函数
            @Override
            public Uri insert(Uri uri, ContentValues values) {
        
                Uri result = null;
                //通过DBHelper获取写数据库的方法
                SQLiteDatabase db = mDBHelper.getWritableDatabase();
                //将要数据ContentValues插入到数据库中
                long id = db.insert(DBHelper.PLAYLIST_TABLE_NAME, null, values);
        
                if(id > 0) {
                    //根据返回到id值组合成该数据项对应的Uri地址,
                    //假设id为8,那么这个Uri地址类似于content://com.anddle.PlayListContentProvider/songs/8
                    result = ContentUris.withAppendedId(CONTENT_SONGS_URI, id);
                }
        
                return result;
        
            }
            ......
        } 
      3. 删除播放列表的操作,我们的音乐播放器在批量添加歌曲到播放列表的时候,首先要清空所有的播放歌曲列表,并没有单独删除某一条歌曲的需要,所以在进行删除操作的时候,我们只需要将整个playlist_table清空就好了,
        public class PlayListContentProvider extends ContentProvider {
            ......
        
            //实现delete函数
            @Override
            public int delete(Uri uri, String selection, String[] selectionArgs) {
                //通过DBHelper获取写数据库的方法
                SQLiteDatabase db = mDBHelper.getWritableDatabase();
                //清空playlist_table表,并将删除的数据条数返回
                int count = db.delete(DBHelper.PLAYLIST_TABLE_NAME, selection, selectionArgs);
                return count;
            }
            ......
        } 
      4. 修改播放列表的操作,当音乐在播放的时候,我们需要记录下档前音乐的播放进度,就要把这个信息更新到现有的数据库当中,
        public class PlayListContentProvider extends ContentProvider {
            ......
            //实现update函数
            @Override
            public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
                //通过DBHelper获取写数据库的方法
                SQLiteDatabase db = mDBHelper.getWritableDatabase();
                //更新数据库的指定项
                int count = db.update(DBHelper.PLAYLIST_TABLE_NAME, values, selection, selectionArgs);
        
                return count;
            }
            ......
        } 
      5. 查询播放列表的操作,当用户想查看当前播放的音乐列表时,查询播放列表中有哪些音乐,
        public class PlayListContentProvider extends ContentProvider {
            ......
            //实现query函数
            @Override
            public Cursor query(Uri uri, String[] projection, String selection,
                                String[] selectionArgs, String sortOrder) {
                //通过DBHelper获取读数据库的方法
                SQLiteDatabase db = mDBHelper.getReadableDatabase();
                //查询数据库中的数据项
                Cursor cursor = db.query(DBHelper.PLAYLIST_TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
        
                return cursor;
            }
            ......
        } 


      至此,一个功能完整的ContentProvider就实现了。

5.1.6 使用自定义ContentProvider

无论是使用应用自己的ContentProvider还是使用其他应用提供的,它们的使用方式都和使用系统提供的ContentProvider一样,

  1. 添加一条数据数据:通过ContentResolver获取访问ContentProvider的入口,使用ContentValues添加要插入的数据;

    //获取要插入的音乐信息
    MusicItem item = xxx;
    //准备要保存的数据
    ContentValues cv = new ContentValues();
    cv.put(DBHelper.NAME, item.name);
    cv.put(DBHelper.DURATION, item.duration);
    cv.put(DBHelper.LAST_PLAY_TIME, item.playedTime);
    cv.put(DBHelper.SONG_URI, item.songUri.toString());
    cv.put(DBHelper.ALBUM_URI, item.albumUri.toString());
    //通过ContentProvider插入数据
    Uri uri = getContentResolver().insert(PlayListContentProvider.CONTENT_SONGS_URI, cv);

    通常会返回指向刚成功插入的这条数据的Uri(内容就如content://com.anddle.PlayListContentProvider/songs/8)。

  2. 删除一条数据:通过ContentResolver获取访问ContentProvider的入口,使用Uri删除指定的数据;
    Uri uri = PlayListContentProvider.CONTENT_SONGS_URI;
    String where = null;
    String [] keywords = null;
    ContentResolver cr = getContentResolver();
    cr.delete(uri, where, keywords);
  3. 修改一条数据:通过ContentResolver获取访问ContentProvider的入口,使用Uri更新指定的数据,要修改的数据放在ContentValues当中;
    Uri uri = PlayListContentProvider.CONTENT_SONGS_URI;
    String where = “字段值满足的条件,是一条SQL语句”;
    String [] keywords = null;
    ContentResolver cr = getContentResolver();
    ContentValues cv = new ContentValues();
    cv.put("数据字段名称", "新数据内容",where,keywords);
    cr.update(Uri.parse(uri, cv, where, keywords);
  4. 查询某一类的数据(或者特定某条数据),
    Uri uri = PlayListContentProvider.CONTENT_SONGS_URI;
    
    String[] searchKey = null;
    String where = null;
    String [] keywords = null;
    String sortOrder = null;
    
    ContentResolver resolver = getContentResolver();
    Cursor cursor = resolver.query(
                        uri,
                        searchKey,
                        where,
                        keywords,
                        sortOrder);
    
    if(cursor != null) {
        while(cursor.moveToNext()) {
            ......
        }
    
        cursor.close();
    }
  5. AndroidManifest.xml文件中,要添加上读写磁盘的权限,这样才能成功的读取和保存数据,
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.anddle.anddlemusic">
    
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
        <application>
            ......
        </application>
        ......
    </manifest>

注意,在删改查的操作中,还会使用诸如where sortOrder keywords searchKey这样的参数,它们是辅助ContentProvider查询特定数据时用的。



/*******************************************************************/

* 版权声明

* 本教程只在CSDN安豆网发布,其他网站出现本教程均属侵权。

*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店安豆的杂货铺中购买相关硬件。同时也感谢大家对我们这些码农的支持。

*最后再次感谢各位读者对安豆的支持,谢谢:)

/*******************************************************************/

时间: 2024-10-12 07:24:11

手把手教你做音乐播放器(五)音乐列表的存储(上)的相关文章

Android获取第三方音乐播放器的音乐信息

最近在做Android手机获取第三方音乐播放器的音乐信息.一开始头疼的很,采集第三方的信息太难了,后面看了一遍博文是关于怎么监听系统的音乐播放信息,发现在播放下一首音乐的时候会发送广播,广播会包含下一首信息. Android系统的音乐播放器会发送: com.android.music.metachanged 那怎么获取第三方的音乐信息? 先反编译了QQ音乐的Apk, 找到它关于player的service,发现里面用的就是"com.android.music.metachaged" 接

Java音乐播放器-乐乐音乐(PC版)

乐乐音乐目前是基于musique开发的一个java音乐播放器,之前开发了一个android版本的音乐播放器,现在把android版本的代码移植过来PC版本这里.不过遇到了一个难题,就是java如果要播放mp3等歌曲,要安装jmf,我觉得安装这jmf真是太麻烦了,电脑是64位的伤不起啊.于是我从网上搜了一下,搜到了不少的开源播放器,如: (1)YOYOPlayer,感觉 YOYOPlayer整体上都不错,我的歌词也参考了一下它的实现,不过它在播放的时候,有一个问题,就是快进的时候,拖动条上显示的时

Android开发本地及网络Mp3音乐播放器(九)音乐收藏与列表切换

实现功能: 使用快速开发框架xUtils中的DbUtils模块,为音乐收藏功能做准备 实现PlayActivity(独立音乐播放界面)收藏.取消收藏按钮 实现MainActivity(主界面)菜单选择事件进入MyLoveMusicActivity(音乐收藏界面) 实现本地音乐列表与音乐收藏列表切换功能 (目前源码,只实现了音乐收藏列表,菜单中最近播放列表后续会进行补充) 截止到目前的源码下载: http://download.csdn.net/detail/iwanghang/9504916 x

手把手教你做音乐播放器(八)桌面小工具(下)(完)

8.4 MusicService的改造 8.4.1 App widget触发MusicService 当App widget的按钮被点击后,会触发隐式定义的Intent发送给MusicService.例如当下一首按钮被点击后,携带action-MusicService.ACTION_PLAY_MUSIC_NEXT的Intent将触发MusicService的onStartCommand()函数. 我们可以在onStartCommand()函数当中接收到App widget要求的操作命令,进行相应

一步一步实战HTML音乐播放器

在这里我用HTML5从头开始一步一步来制作一个简约的音乐播放器,大家可以参考一下,接下来正式开始. 音乐播放器效果 播放器分析 这里将播放器分两块来做: 视图层(html + css) 逻辑层 ( js ) 视图层分析 视图中包含: 播放器容器 播放器名称 音乐专辑图 音乐信息 歌曲名 歌手 专辑名 控制区 上一曲 播放 下一曲 播放进度条 播放时间 当前时间 歌曲总时间 音频控件 页面背景 逻辑层分析 逻辑层处理包括: 加载歌单 渲染歌曲信息 专辑图 歌曲名 歌手 专辑名 歌曲时长 歌曲音频地

10个免费开源的JS音乐播放器插件

音乐播放器在网页设计中有时候会用到,比如一些时尚类.音乐或影视类等项目,但这些 网页播放器 插件比较少见,所以这里为大家整理一个集合,也许会有用到的时候. 下面整理的播放器有些是支持自适应的,如果需要用到微信或手机上,可根据自己需要求,选择对应的网页播放器.  ● Codrops Audio Codrops Audio 界面使用纯CSS编写,支持响应式,可以方便在桌面.平板以及手机设备上使用.界面因为是CSS编写,所以如果懂CSS样式,可以自己设计一个新的样式来修改. 演示&下载 ● Dark

每天写个APP_第2天——升级版音乐播放器

每天写个APP_第2天--升级版音乐播放器 android 音乐播放器 SeekBar Runnable 前面的话 :有同学说我写的不够详细,我以后会注意和完善.又说能否有源码,我一般会在文章里贴出所有源码,另外我今天会吧代码传到github上提供下载. 回顾:上篇文章实现各一个简单的播放器,但是仍然不是很酷.今天,我们对它做个改进.还记得网易云音乐APP中播放界面有个CD吗?当播放音乐时,中间的CD就会转动,是不是很酷?今天我们也把中间的CD转起来!然后,我们还要添加歌曲进度条,还可以用手指拖

android调用音乐播放器,三种方

小弟想请问一下,如何在自己写的程序中调用系统的音乐播放器呢. 我在google上搜索了,主要是有两种方法,但是都不是我想要的. 第一种是,使用mp3音乐文件的uri,和intent,进行调用,但是这种是针对某一首歌曲的播放使用的. /** * 播放指定名称的歌曲 * @param audioPath 指定默认播放的音乐 */ public static void playAudio(String audioPath){ Intent mIntent = new Intent(); mIntent

手把手教你做音乐播放器(八)桌面小工具(上)

第8节 桌面小工具 桌面小工具是可以放置在主界面的.快速控制应用的小助手.例如我们的音乐小工具,它可以帮助用户在桌面上就完成音乐的暂停.播放.切换等操作,而不需要启动应用本身. 在安卓系统中,我们也常常叫它App widget. 实现一个App widget要经过以下几个步骤, 创建一个App widget类,让它继承自AppWidgetProvider,例如AnddleMusicAppWidget类: 放在res\layout目录下,为App widget的界面定义一个布局,例如anddle_

用PHP+H5+Boostrap做简单的音乐播放器(进阶版)

前言:之前做了一个音乐播放器(纯前端),意外的受欢迎,然后有人建议我把后台一起做了,正好也想学习后台,所以学了两天php(不要吐槽我的速度,慢工出细活嘛~)然后在之前的基础上也又完善了一些功能,所以这个Demo比之前的可以算是进阶呢~v2.0哈哈哈~感觉截图体验很不好呢,所以在美图秀秀上面做了简易的动图,大家感受感受 正文: 老规矩,先上图~感觉有点卡,愿意等的就等等嘛,不愿意等的,往下看,有图片讲解 功能实现: 1.点击音乐列表播放音乐 2.拖动或点击进度条,调节音乐播放进度 3.浮动到音量控