当前位置: 代码迷 >> Android >> 【android】音乐播放器之数据存储小结
  详细解决方案

【android】音乐播放器之数据存储小结

热度:132   发布时间:2016-04-27 22:23:18.0
【android】音乐播放器之数据存储总结
            学习Android有一个多月,看完了《第一行代码》以及mars老师的第一期视频通过音乐播放器小项目加深对知识点的理解。从本文开始,将详细的介绍简单仿多米音乐播放器的实现,以及网络解析数据获取百度音乐最新排行音乐以及下载功能。

        功能介绍如下:    

        1、获取本地歌曲列表,实现歌曲播放功能。 
        2、利用jsoup解析网页数据,从网络获取歌曲列表,同时实现歌曲和歌词下载到手机本地的功能。 
        3、通知栏提醒,实现仿QQ音乐播放器的通知栏功能. 
     

       涉及的技术有: 
       1、jsoup解析网络网页,从而获取需要的数据 
       2、android中访问网络,获取文件到本地的网络请求技术,以及下载文件到本地实现断点下载 
       3、线程池 
       4、图片缓存 
       5、service一直在后台运行 
       6、Activity与Fragment间的切换以及通信 
       7、notification通知栏设计 
       8、自定义广播 
       9、android系统文件管

       音乐播放器思路及源码下载见:【android】音乐播放器之设计思路

      这篇文章主要来谈谈android数据存储以及音乐播放器用到的一些存储技术和缓存技术。当然小编也还是会顺带扩展下音乐播放器用到的其他方方面面的知识如果可能的话~!~!!!哈哈哈。先来总结写目前android目前主要的5中数据存储方式吧。

数据存储方式

Android 的数据存储有5种方式:

1. SharedPreferences存储数据 
   SharedPreferences数据存储,也叫作xml存储。这是将数据存储“data/data/程序包名/share_prefs”路径下的到xml文件中。   
2. 文件存储数据 
   分为内部储存和外部存储。内部存储是应用程序使用Android为自己分配的内存空间,数据存储到“/data/data/程序包名/files”路径下的相应文件中。外部存储是使用手机sdcard的内存(这个sdcard并不是我们经常说的那个可以拆卸替换的SD卡,那个SD卡我们称之为扩展卡),使用这部分内存要声明相应的权限。 
3. SQLite数据库存储数据 
  使用数据库进行存储,这个一般数据量比较大的时候。   
4. 使用ContentProvider存储数据 
  这个比较眼熟,ContentProvider也是Android的四大组件之一。ContentProvider一般是第三方提供的数据存储方式,向我们手机中的通讯录联系人,照片,音乐等…… 
5. 网络存储数据 
   这个是将数据上传到网络上进行存储。 

     下面就具体的看看每种存储方式,先看看sharepreferences存储技术:

SharedPreferences存储数据

SharedPreferences是一种轻型的数据存储方式,实际上是基于XML文件存储的“key-value”键值对数据。通常用来存储程序的一些配置信息。其存储在“data/data/程序包名/shared_prefs“目录下。 
   
使用SharedPreferences来存储数据首相我们要获得SharedPreferences的对象。

获得SharedPreferences的对象

  获得SharedPreferences对象的方法有三种: 
(1)通过Context的getSharedPrerences(key, [模式])方法获取SharedPreferences对象;方法的第一个参数是用于指定SharedPreferences文件的名称,第二个参数是指定操作模式,主要有两种模式进行选择:MODE_PRIVATE, MODE_MULTI_PROCESS。默认操作是MODE_PRIVATE。除此之外还有MODE_EORLD_READEABLE,MODE_WORLD_WRITEABLE两种。 
(2)通过Activity类提供的getPrerences(key)方法获取到SharedPreferences对象;该方法会创建一个以当前活动类名作为SharedPreferences文件名的文件。 
(3)通过PreferencesManager类中的getDefaultPreferernces()方法获得;这是一个静态的方法,他接受一个Context参数,并将当前应用程序的包名作为SharedPreferences文件名。

下面我们来看一下如何使用SharedPreferences读写数据……

写数据

步骤:

(1)根据Context的getSharedPrerences(key, [模式])方法获取SharedPreferences对象; 
(2)利用SharedPreferences的editor()方法获取Editor对象; 
(3)通过Editor的putXXX()方法,将键值对存储数据; 
(4)通过Editor的commit()方法将数据提交到SharedPreferences内。

读数据

步骤:

(1)根据Context的getSharedPrerences(key, [模式])方法获取SharedPreference对象; 
(2)通过SharedPreference对象的getXXX方法获得数据。例如:getString(String s, String s1)方法有两个位参数,第一个s为要获得的值的键,第二个s1为如果键值不存在返回的默认的String类型的值。

文件存储数据

  文件存储是Android中最基本的一种存储方式,他不对存储的内容进行任何格式化的处理,所有的数据都是原封不动的保存在文件中的,因此他比较适合于存储一些简单的二进制数据或文本数据。 
  文件存储也分两种:内部存储和外部存储。 

内部存储

  指Android为应用程序分配的内存。 
通过Context类中封装好的输入流和输出流的获取方法获得数据/data/data//files目录下存储的数据。

files目录下写数据

  通过Context的封装好的方法 openFileOutput(String filename, int mode)获得数据流: 
       String filename参数:在/data/data//files目录下存储时的文件名。 
       int mode参数:文件的操作模式,主要有两种模式可选择:MODE_PRIVATE, MODE_APPEND。默认为MODE_PRIVATE,当指定相同文件名进行读写的时候,新的内容会覆盖原有内容;MODE_APPEND模式会在已存在的文件的最后追加新的内容。(还得多唠叨唠叨~!~)除此之外有两种模式:MODE_WORLD_READABLEMODE_WORLD_WRITEABLE,这两种模式表示允许其他应用程序对我们的程序文件进行读写操作。

files目录下读数据

   通过Context的封装好的方法 openFileInput(String filename)获得数据流: 

           String filename参数:在/data/data//files目录下读取时的文件名。

外部存储

          对于多数情况下每个用户都有自己的努力存储空间,应用仅对当前用户的外部存储空间有访问权限:WRITE_EXTERNAL_STORAGE为对外部存储空间的写权限;READ_EXTERNAL_STORAGE为对外部存储空间的写权限;这些都需要在AndroidManifest.fex文件夹中声明。

         READ_EXTERNAL_STORAGE:用于获取主要的外部存储目录,当然这个目录当前很有可能不能访问,这就需要通过状态判断getExternalStorageState 

         getExternalStorageState:可以被用于获取当前的状态;

         getExternalFileDir:返回的是应用所在的目录,当应用被卸载后系统清理的就是这个目录

         getExternalCacheDir:用于返回应用外部的缓存目录

SQLite数据库存储数据

       SQLite是一个轻量级关系型数据库,既然是关系型数据库,那操作起来其实跟mysql、sql server差不多的。 
       SQLite 和其他数据库最大的不同就是对数据类型的支持,创建一个表时,可以在 CREATE TABLE 语句中指定某列的数据类型,但是你可以把任何数据类型放入任何列中。当某个值插入数据库时,SQLite 将检查它的类型。如果该类型与关联的列不匹配,则 SQLite 会尝试将该值转换成该列的类型。如果不能转换,则该值将作为其本身具有的类型存储。比如可以把一个字符串(String)放入 INTEGER 列。SQLite 称这为“弱类型”(manifest typing.)。 
  对数据库SQLite进行操作,我们要借助于SQLiteOpenHelper类进行操作。对数据库的操作也就是”增、删、改、查“。在学习数据库的操作之前我们首先要学会如何创建数据库……

创建数据库

  数据库的操作借助于SQLiteOpenHelper,SQLiteOpenHelper是一个抽象类,我们我们在使用SQLiteOpenHelper时要先创建一个MySQLiteOpenHelper继承SQLiteOpenHelper类。 
  SQLiteOpenHelper有两个非常重要的方法:getReadableDatabase()方法返回数据库是一个只读的;getWriteableDatabase()方法获得是一个可读写的数据库对象。这里我们使用getWriteableDatabase()方法获得数据库Database对象。 

添加数据

  使用SQLiteDatabase的insert(String table, String nullColumnHack, ContentValues values)方法插入数据。这个方法包含三个参数: 
  我们先列举一条SQLite中的插入语句:INSERT INTO user (name, passwords)] VALUES ("张三", "123456"); 
String table:操作的数据表的名称。 
String nullColumnHack:用于我们在未指定添加数据的情况下,为数据表中可以添加null值的数据填入null值。一般这个参数我们传入null。 
ContentValues values:用于传递数据,通常我们通过ContentValues 类的对象的putXXX()方法封装数据,然后将数据添加进数据库。 
  ContentValues 类,类似于java中的Map,以键值对的方式保存数据。

删除数据

  使用SQLiteDatabase的delete(String table, String whereClause, String[] whereArgs)方法删除数据。这个方法包含三个参数: 
  我们先列举一条SQLite中的删除语句:DELETE FROM user WHERE name="张三"。 
String table:操作的数据表的名称。 
String whereClause:约束删除行的条件。相当于SQLite语句中“where name=?“内容。 
String[] whereArgs:与前一个参数对应约束删除行的条件。相当于”where name=”张三““中的”张三“。 
注意:如果参数String whereClause和参数String[] whereArgs都传null的话,就是删除所有行。

修改数据

  使用SQLiteDatabase的 update (String table, ContentValues values, String whereClause, String[] whereArgs)方法删除数据。这个方法包含四个参数: 
  我们先列举一条SQLite中的修改语句:UPDATE user SET name= "李四", passwords= "123" WHERE name="张三"。 
String table:操作的数据表的名称。 
ContentValues values:用于传递数据,通常我们通过ContentValues 类的对象的putXXX()方法封装数据,然后将数据添加进数据库。 
String whereClause:约束修改行的条件。相当于SQLite语句中“where name=?“内容。 
String[] whereArgs:与前一个参数对应约束删除行的条件。相当于”where name=”张三““中的”张三“。

查询数据

  对于”查“操作,SQLiteDatabase提供了多种方法。 
  我们先列举一条SQLite中的修改语句:SELECT passwords="123" FROM user。 
(1)使用SQL语句进行查询。这里SQLiteDatabase提供了方法:

  • rawQuery (String sql, String[] selectionArgs):该方法返回 Cursor类的对象,用于操作查询的结果。

(2)使用SQLiteDatabase内定方法查询:

  • query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit):这个方法有N多个参数啊……String table是操作的数据表的名称;String selection是筛选的字段选项;String[] selectionArgs是字段选项对应的值;String groupBy是筛选结果的分组依据;String having是在由groupBy子句创建的分组上设置条件;String orderBy是结果的排序方式,String limit是筛选结果的显示限制,例如“2, 3”是指从筛选结果的第2个开始显示3个。

ContentProvider存储数据

        ContentProvider内容提供器,主要用于在不同应用程序之间实现数据的共享功能。举例来说,我们开发一个应用程序,我们不可能只使用自己的数据,也会用到其他应用的数据,像手机中的通讯录联系人,图片,音乐等是使用到最多的。我们使用的SharedPreferences,文件存储以及数据库SQLite都是从存储的应用内部的数据,实现不同应用间的数据共享就要使用到ContentProvider。 
  ContentProvider使用方法有两种:一种是使用现有的内容提供器来读取和操作相应程序中的数据;另一种是创建自己的内容提供器给我们的应用提供外部访问接口。 
   
  这里我们只讲解使用已有的内容提供器来读取和操作相应程序的数据。

ContentResolver的使用

  想要访问内容提供器中的内容我们需要借助ContentResolver类。 
  ContentResolver为我们提供了”增“insert(Uri url, ContentValues values),“删”delete(Uri url, String where, String[] selectionArgs),“改”update(Uri uri, ContentValues values, String where, String[] selectionArgs),“查”query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)方法对内容提供器中的内容进行操作,是不是很眼熟,对啊,和SQLiteDatabase中的增删改查操作是一样的,在此我们不再具体描述。

ContentResolver的使用

  想要访问内容提供器中的内容我们需要借助ContentResolver类。 
  ContentResolver为我们提供了”增“insert(Uri url, ContentValues values),“删”delete(Uri url, String where, String[] selectionArgs),“改”update(Uri uri, ContentValues values, String where, String[] selectionArgs),“查”query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)方法对内容提供器中的内容进行操作,是不是很眼熟,对啊,和SQLiteDatabase中的增删改查操作是一样的,在此我们不再具体描述。

Uri的使用

  在SQLiteDatabase中,对数据库的操作是通过接受数据库的表名进行操作的,而在ContentResolver中是不接受表名的,而接受Uri对象。通过Uri指定操作的内容“位置”。 
  Uri由两部分组成:权限和路径。权限是对于不同的应用程序进行区分的,一般用应用程序的包名;路径是对于同一应用程序的不同表进行区分的。例如,我们有一个应用程序的包名是“com.example.ontentproviderdemo”,应用程序中有一个表“table”,则Uri为:“content://com.example.ontentproviderdemo/table”。

查询通讯录

  我们以查询通讯录中的内容为例,看一下ContentProvider的使用。通过Android提供的ContentProvider内容提供器访问通讯录中的联系人数据。 
1. 读取联系人是需要权限的,首先在AndroidManifext中加入权限

2. 创建ContentResolver对象对ContentProvider进行操作。 
3. 创建一个Uri对象,指定访问通讯录。这里我们使用Android已经解析好的Uri即可:ContactsContract.CommonDataKinds.Phone.CONTENT_URI 
4. 查找联系人内容使用ContentResolver的query()方法,返回一个Cursor对象。

     音乐播放器主要用到了文件存储技术、sharepreferences存储技术以及LruCache缓存技术,当然如果你感兴趣也可以自己玩玩数据库啥的~!~

    在PlayService启动阶段,会初始化本地音乐列表(见 【android】音乐播放器之service服务设计)通过MusicUtils访问外部存储设备sdcard读取本地音乐:

public class MusicUtils {	// 存放歌曲列表		public static ArrayList<Music> sMusicList = new ArrayList<Music>();		public static void initMusicList() {			// 获取歌曲列表			sMusicList.clear();			sMusicList.addAll(LocalMusicUtils.queryMusic(getBaseDir()));		}		/**		 * 获取内存卡根		 * @return		 */		public static String getBaseDir() {			String dir = null;			if (!Environment.getExternalStorageState().equals(Environment.MEDIA_UNMOUNTED)) {				dir = Environment.getExternalStorageDirectory() + File.separator;			} else {				dir = App.sContext.getFilesDir() + File.separator;			}			return dir;		}		/**		 * 获取应用程序使用的本地目录		 * @return		 */		public static String getAppLocalDir() {			String dir = null;			if (!Environment.getExternalStorageState().equals(Environment.MEDIA_UNMOUNTED)) {				dir = Environment.getExternalStorageDirectory() + File.separator						+ "Music" + File.separator;			} else {				dir = App.sContext.getFilesDir() + File.separator + "Music" + File.separator;			}			return mkdir(dir);		}		/**		 * 获取音乐存放目录		 * @return		 */		public static String getMusicDir() {			String musicDir = getAppLocalDir();			return mkdir(musicDir);		}		/**		 * 获取歌词存放目录		 * 		 * @return		 */		public static String getLrcDir() {			String lrcDir = getAppLocalDir();			return mkdir(lrcDir);		}		/**		 * 创建文件夹		 * @param dir		 * @return		 */		public static String mkdir(String dir) {			File f = new File(dir);			if (!f.exists()) {				for (int i = 0; i < 5; i++) {					if(f.mkdirs()) return dir;				}				return null;			}						return dir;		}
/**	 * 获取目录下的歌曲	 * @param dirName	 */	public static ArrayList<Music> queryMusic(String dirName) {		ArrayList<Music> results = new ArrayList<Music>();		Cursor cursor = App.sContext.getContentResolver().query(				MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,				MediaStore.Audio.Media.DATA + " like ?",				new String[] { dirName + "Music/" + "%" },				MediaStore.Audio.Media.DEFAULT_SORT_ORDER);		if(cursor == null) return results;				// id title singer data time image		Music music;		for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {			// 如果不是音乐			String isMusic = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.IS_MUSIC));			if (isMusic != null && isMusic.equals("")) continue;						String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));			String artist = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST));						if(isRepeat(title, artist)) continue;						music = new Music();			music.setId(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)));			music.setTitle(title.toString());			music.setArtist(artist.toString());			music.setUri(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA)));			music.setLength(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)));			music.setImage(getAlbumImage(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ALBUM_ID))));			results.add(music);		}		cursor.close();		return results;	}
     代码非常的简单,逐一的访问sdcard读取.mp3/.lrc文件,解析出歌名、著作填充到适配器中去。对于SharePreferences,我们知识将一些共享数据存储在其中,比如每次播放歌曲在本地列表中的position等,部分代码如下:

/**	 * 播放	 * @param position 音乐列表的位置	 * @return 当前播放的位置	 */	public int play(int position) {		if(position < 0) position = 0;		if(position >= MusicUtils.sMusicList.size()) position = MusicUtils.sMusicList.size() - 1;				try {			mPlayer.reset();						mPlayer.setDataSource(MusicUtils.sMusicList.get(position).getUri());			mPlayer.prepare();						start();			if(mListener != null) mListener.onChange(position);		} catch (Exception e) {			e.printStackTrace();		}				mPlayingPosition = position;		SpUtils.put(Constants.PLAY_POS, mPlayingPosition);		if(!readyNotification){			startNotification();		}else{			setRemoteViews();		}		return mPlayingPosition;	}
public class SpUtils {	public static void put(final String key, final Object value) {		SharedPreferences sp = App.sContext.getSharedPreferences(Constants.SP_NAME, 				Context.MODE_PRIVATE);		SharedPreferences.Editor editor = sp.edit();				if(value instanceof Integer) {			editor.putInt(key, (Integer) value);		}else if(value instanceof Float) {			editor.putFloat(key, (Float) value);		}else if(value instanceof Boolean) {			editor.putBoolean(key, (Boolean) value);		}else if(value instanceof Long) {			editor.putLong(key, (Long) value);		}else {			editor.putString(key, (String) value);		}				editor.commit();	}		public static Object get(Context context, String key, Object defaultObject) {		SharedPreferences sp = App.sContext.getSharedPreferences(Constants.SP_NAME,				Context.MODE_PRIVATE);		if (defaultObject instanceof String) {			return sp.getString(key, (String) defaultObject);		} else if (defaultObject instanceof Integer) {			return sp.getInt(key, (Integer) defaultObject);		} else if (defaultObject instanceof Boolean) {			return sp.getBoolean(key, (Boolean) defaultObject);		} else if (defaultObject instanceof Float) {			return sp.getFloat(key, (Float) defaultObject);		} else if (defaultObject instanceof Long) {			return sp.getLong(key, (Long) defaultObject);		}		return defaultObject;	}		/**	 * 移除某个key值已经对应的值	 * @param context	 * @param key	 */	public static void remove(Context context, String key) {		SharedPreferences sp = context.getSharedPreferences(Constants.SP_NAME,				Context.MODE_PRIVATE);		sp.edit().remove(key).commit();	}	/**	 * 清除所有数据	 * @param context	 */	public static void clear(Context context) {		SharedPreferences sp = context.getSharedPreferences(Constants.SP_NAME,				Context.MODE_PRIVATE);		sp.edit().clear().commit();	}}
     读取本地音乐时,其中图片显示部分用到了图片缓存技术MusicIconLoader类的实现,在MusicIconLoader类中除调用BitmapFactory提供的方法对图片大小进行处理外(见Android高效加载大图、多图解决方案,有效避免程序OOM)还使用了LruCache缓存技术,上面这篇文章讲的很清楚,就直接看代码吧:

     

public class MusicIconLoader {private static MusicIconLoader sInstance;		private LruCache<String, Bitmap> mCache;		// 获取MusicIconLoader的实例	public synchronized static MusicIconLoader getInstance() {		if(sInstance == null) sInstance = new MusicIconLoader();		return sInstance;	}		// 构造方法, 初始化LruCache	private MusicIconLoader() {		int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);		mCache = new LruCache<String, Bitmap>(maxSize) {			protected int sizeOf(String key, Bitmap value) {//				return value.getByteCount();				return value.getRowBytes() * value.getHeight();			}		};	}		// 根据路径获取图片	public Bitmap load(final String uri) {		if(uri == null) return null;				final String key = Encrypt.md5(uri);		Bitmap bmp = getFromCache(key);				if(bmp != null) return bmp;				bmp = BitmapFactory.decodeFile(uri);		addToCache(key, bmp);				return bmp;	}		// 从内存中获取图片	private Bitmap getFromCache(final String key) {		return mCache.get(key);	}		// 将图片缓存到内存中	private void addToCache(final String key, final Bitmap bmp) {		if(getFromCache(key) == null && key != null && bmp != null) mCache.put(key, bmp);	}}







版权声明:本文为博主原创文章,未经博主允许不得转载。

2楼tzs_1041218129昨天 21:24
谢谢博主,写的很详细,有时间的话,自己来试试增加其他的功能
Re: zjngogo昨天 22:29
回复tzs_1041218129n期待,希望到时能够分享出来
1楼u010786678昨天 17:31
谢谢小伙伴的分享,学习了
  相关解决方案