第5节 播放列表的存取
关于播放列表的存取需要三个组件的协同配合,
MusicListActivity :让用户选择多首或一首音乐,将用户的选择项,传递给MusicService ;
MusicService :接收到添加列表的请求后,把数据交给PlayListContentProvider ,进行存储;
PlayListContentProvider :将播放列表存储到SQLite 数据库中;
5.1 PlayListContentProvider的实现
自定义的ContentProvider 与系统自带的ContentProvider 在设计和用法上都是一样的。
5.1.1 地址设计
我们首先来学习一下ContentProvider 的Uri地址。
任何对数据的操作范围无外乎,
- 单条数据的操作:每一次操作增加一条数据,删除一条数据,修改一条数据,查询特定一条数据的详细内容;
- 多条数据的操作:每一次操作增加多条数据,删除多条数据,修改多条数据,查询符合某个特征的多条数据;
例如,我们可能会向书架上“一次放一本书”-单条增,“一次取下一本书”-单条删,“一次问书架上有多少书”-多条查。
因此,仿照网站地址的设计方式,我们可以用如下的“网络地址”来表达我们希望进行的对ContentProvider 的操作是针对单一一条数据还是同时多条数据:
- xxx.xxx.xxx/items/1:针对单一一条数据,最后的数字代表特定一本书的编号(也可以使用书的名字来代替);
- xxx.xxx.xxx/items:针对多条数据;
“网站”只要看到以上的格式就知道,要操作的是一条数据还是多条数据了。
每个ContentProvider 就是一个“网站”,每个网站都有自己的“网址”。安卓系统为这个“网址”设计了如下的结构,
scheme:
scheme :固定为“content”,相当于一个网址的“http”;
authority :由开发者自己确定,通常把它写成这个ContentProvider 的包名,例如“com.anddle.mycontentprovider”,它就相当于网址的地址“www.google.com”;
path :根据查询内容的逻辑,由开发者自己决定,通常要分成多条数据和单一数据两类;
典型的例子就像这样,
多条数据
content:
单一数据
content:
这里面, scheme:“content” authority:“com.anddle.mycontentprovider” path:“items”或者“items/1”
只要定义好了前面两种原则,外界(其他组件或者其他应用)就可以获取到ContentProvider 中的内容了。
Uri 的scheme 字段是固定的,使用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
继承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[] selectionArgs) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public Cursor query(Uri uri,String[] projection,String[] selectionArgs,String sortOrder) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
定义提供给其他组件使用的“网络地址”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";
public static final Uri CONTENT_SONGS_URI = Uri.parse(SCHEME + AUTHORITY + PATH_SONGS);
......
}
在响应函数中,根据Uri,做对应的操作,例如insert() 函数中,就要实现对数据增插入的真正操作,不过现在我们把操作数据库的操作留到后面来讲解 @Override
public Uri insert(Uri uri,ContentValues values) {
Uri result = null;?
return result;
} 类似的,其他delete() update() query() 实现的函数做相同的处理。
对于getType() ,需要为每一种类型的Uri 返回一种数据类型-MIME type ,告诉调用者,当前这种Uri可以处理什么类型的数据。 它的格式型如typesubtype ,有很多知名的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 相关的代码,
在项目上点击右键,选择New->other->ContentProvider
在弹出的对话中,填写上ContentProvider 的名字和自定义的authorities ,并选择Finish ,
创建成功后,自定义的ContentProvider 框架就自动帮我们实现了;同时,替我们在AndroidManifest.xml 文件中完成了对它的注册。剩下的就是要我们向里面添加自己的逻辑代码了。
从这里也能看到使用Android Studion开发环境的便利之处。
5.1.5 数据库存储
创建了ContentProvider 后,它还只是虚有其表,不能保存任何数据。要保存数据,通常会让它使用数据库的方式实现。
假设数据库的名字叫做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型数据,以毫秒为单位,记录该音乐一共可以播放的时长;
安卓系统为我们提供了一个方便的使用SQLite 数据库的工具类SQLiteOpenHelper ,通过它来创建、更新或者删除SQLite 数据库。
继承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) {
}
}
创建数据库和更新数据库; 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_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 newVersion) {
db.execSQL("DROP TABLE IF EXISTS "+ PLAYLIST_TABLE_NAME);
onCreate(db);
}
}
PlayListContentProvider 的增删改查工作,将依赖于我们刚创建的DBHelper ,
创建DBHelper , public class PlayListContentProvider extends ContentProvider {
private DBHelper mDBHelper;
......
@Override
public boolean onCreate() {
mDBHelper = new DBHelper(getContext());
return true;
}
......
}
添加播放列表的操作,每次向数据库插入一条信息, public class PlayListContentProvider extends ContentProvider {
......
@Override
public Uri insert(Uri uri,ContentValues values) {
Uri result = null;
SQLiteDatabase db = mDBHelper.getWritableDatabase();
long id = db.insert(DBHelper.PLAYLIST_TABLE_NAME,values);
if(id > 0) {
result = ContentUris.withAppendedId(CONTENT_SONGS_URI,id);
}
return result;
}
......
}
删除播放列表的操作,我们的音乐播放器在批量添加歌曲到播放列表的时候,首先要清空所有的播放歌曲列表,并没有单独删除某一条歌曲的需要,所以在进行删除操作的时候,我们只需要将整个playlist_table 清空就好了, public class PlayListContentProvider extends ContentProvider {
......
@Override
public int delete(Uri uri,String[] selectionArgs) {
SQLiteDatabase db = mDBHelper.getWritableDatabase();
int count = db.delete(DBHelper.PLAYLIST_TABLE_NAME,selection,selectionArgs);
return count;
}
......
}
修改播放列表的操作,当音乐在播放的时候,我们需要记录下档前音乐的播放进度,就要把这个信息更新到现有的数据库当中, public class PlayListContentProvider extends ContentProvider {
......
@Override
public int update(Uri uri,String[] selectionArgs) {
SQLiteDatabase db = mDBHelper.getWritableDatabase();
int count = db.update(DBHelper.PLAYLIST_TABLE_NAME,values,selectionArgs);
return count;
}
......
}
查询播放列表的操作,当用户想查看当前播放的音乐列表时,查询播放列表中有哪些音乐, public class PlayListContentProvider extends ContentProvider {
......
@Override
public Cursor query(Uri uri,String sortOrder) {
SQLiteDatabase db = mDBHelper.getReadableDatabase();
Cursor cursor = db.query(DBHelper.PLAYLIST_TABLE_NAME,projection,selectionArgs,sortOrder);
return cursor;
}
......
}
至此,一个功能完整的ContentProvider 就实现了。
5.1.6 使用自定义ContentProvider
无论是使用应用自己的ContentProvider 还是使用其他应用提供的,它们的使用方式都和使用系统提供的ContentProvider 一样,
添加一条数据数据:通过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());
Uri uri = getContentResolver().insert(PlayListContentProvider.CONTENT_SONGS_URI,cv); 通常会返回指向刚成功插入的这条数据的Uri (内容就如content://com.anddle.PlayListContentProvider/songs/8 )。
删除一条数据:通过ContentResolver 获取访问ContentProvider 的入口,使用Uri 删除指定的数据; Uri uri = PlayListContentProvider.CONTENT_SONGS_URI;
String where = null;
String [] keywords = null;
ContentResolver cr = getContentResolver();
cr.delete(uri,where,keywords);
修改一条数据:通过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("数据字段名称","新数据内容",keywords);
cr.update(Uri.parse(uri,cv,keywords);
查询某一类的数据(或者特定某条数据), 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,keywords,sortOrder);
if(cursor != null) {
while(cursor.moveToNext()) {
......
}
cursor.close();
}
在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智能硬件相关的教程,您可以在我们的网店安豆的杂货铺中购买相关硬件。同时也感谢大家对我们这些码农的支持。
*最后再次感谢各位读者对安豆 的支持,谢谢:) /*******************************************************************/ (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|