Android 设置铃声分析
代码其实没有几行,这里简单记录下学习的过程.
Android系统启动时会扫描系统与SD卡中的对媒体文件,分别存入数据库sqlite中,以contentProvider的形式对外提供服务
路径:/data/data/com.android.providers.media/databases/XXX...
可以看到有2个db文件, 一个是系统的,一个是sd卡里的
用SQLite Expert打开internal.db,部分截图如下:
这里面记录了音频audio、视频video、图片images的相关数据信息,我们以音频audio为例,蓝色部分audio_meta就是audio数据表,打开之后就可以看到详细信息了,里面列出了系统内部的所有音频文件,各个字段在android.provider.MediaStore中都定义有相应的常量,如id --- MediaStore.Audio.Media._ID.
而这里面有想说下这四个字段
含义在源码里都有说明,看了一遍数据,发现这四个字段同时有且仅有一个字段为1,也就是对于一个多媒体文件只能是这四种中的一种,默认为0,如果是某种类型,则android系统默认置为1,所以也就明白了为什么很多扫描系统通知或者来电铃声的示例代码中,都会有一个类似的条件语句:is_notification = 1.
如:
/** * 扫描系统内部通知铃声 */ private void scannerMediaFile() { ContentResolver cr = this.getContentResolver(); Cursor cursor = cr.query(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.TITLE }, "is_notification != ?", new String[] { "0" }, "_id asc"); if (cursor == null) { return; } while (cursor.moveToNext()) { data.add(cursor.getString(1)); } }
这里 is_notification != 0,效果是一样的,除非哪天google再定义个2, 3 ......
上面扯了些其他的,关于设置铃声的方法,系统提供了一个铃声管理器android.provider.RingtoneManager,其中提供了获取与设置铃声的API
如:Uri uri = RingtoneManager.getActualDefaultRingtoneUri(MediaActivity.this, RingtoneManager.TYPE_NOTIFICATION);可以获取到当前系统的通知铃声uri
第二个参数可以指定获取的铃声类型,还有其他的TYPE_RINGTONE,TYPE_ALARM, TYPE_ALL
设置铃声的API:
RingtoneManager.setActualDefaultRingtoneUri(MediaActivity.this, RingtoneManager.TYPE_NOTIFICATION, Uri.parse(data.get(position)));
第二个参数同上,最后一个是指定一个新的Uri, 这里的data.get(position)就是在上面的扫描代码扫描出的所有通知铃声path路径中选泽一个,然后在解析成一个URI对象传入即可
那么android是如何获取指定类型的系统铃声呢?
这涉及到另一个类android.provider.Settings
相关源码如下:
public static Uri getActualDefaultRingtoneUri(Context context, int type) { //根据指定的类型获取Settings类中对应的类型,这里RingtoneManager.TYPE_NOTIFICATION对应的为Settings.System.NOTIFICATION_SOUND,其实也就是下面所说的system表中的一个name字段名 String setting = getSettingForType(type); if (setting == null) return null; //调用Settings类中静态内部类System中的相应方法 final String uriString = Settings.System.getString(context.getContentResolver(), setting); return uriString != null ? Uri.parse(uriString) : null; }
public synchronized static String getString(ContentResolver resolver, String name) { //MOVED_TO_SECURE是System类中定义的一个hashSet集合,在Android系统启动时,会初始化30(目前是30)条涉及系统安全的设置数据(如果http代理设置,wifi相关设置),并且存入数据库中,与多媒体的db不同,系统默认存放在settings.db中,路径为/data/data/com.android.providers.settings/databases,具体是存放在settings.db数据库实例的secure表中,用工具打开,可以看到此表中恰好有30条数据。说了那么多,其实这里是检查你所指定的类型也就是db中的字段在不在这个集合中,如果在,则会调用Settings类中的另一个静态内部类Secure中的getString(...)方法 if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, returning read-only value."); return Secure.getString(resolver, name); } //如果不在那个涉及系统安全的设置集合中,则调用Settings中定义的一个缓存类NameValueCache中的getString(...) if (sNameValueCache == null) { sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_SYSTEM); } return sNameValueCache.getString(resolver, name); }
//NameValueCache中getString()方法部分代码 Cursor c = null; try { //mUri == "content://settings/system"在NameValueCache初始化时赋值,指定查询的是settings.db中的system表,同理上面提到的Secure类的getString(...)中调用的也是这个缓存类的同名方法,只不过mUri被指定为查询secure表(这2个表中除了id,只有name与value2个字段,分别指定设置的类型与对应的值) c = cp.query(mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER, new String[]{name}, null); if (c == null) { Log.w(TAG, "Can't get key " + name + " from " + mUri); return null; } String value = c.moveToNext() ? c.getString(0) : null; synchronized (this) { //查询完讲name/value键值对放入mValues集合中,当然如果这个集合中已经存在这个键值对,那么也就不会执行这段操作db的代码了 mValues.put(name, value); }
settings.db结构如下:
上面示例中指定的TYPE_NOTIFICATION的数据如下(蓝色部分):
最后返回的就是file:///..........这个String数据,再转化成URI返回给调用者
OK,那么设置铃声的API, setAc.......执行的过程也类似:
public static boolean putString(ContentResolver resolver, String name, String value) { //这里依然是检查设置的类型是否涉及到系统预置的安全设置集合,如果是,则直接返回false if (MOVED_TO_SECURE.contains(name)) { Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System" + " to android.provider.Settings.Secure, value is unchanged."); return false; } //这里执行的是另一个静态内部类NameValueTable中的方法 return putString(resolver, CONTENT_URI, name, value); }
protected static boolean putString(ContentResolver resolver, Uri uri, String name, String value) { // The database will take care of replacing duplicates. try { ContentValues values = new ContentValues(); values.put(NAME, name); values.put(VALUE, value); //指定类型name与相应value插入db的system表中,如果表中已经存在指定的类型字段怎么办? 请看上面的源码注释... resolver.insert(uri, values); return true; } catch (SQLException e) { Log.w(TAG, "Can't set key " + name + " in " + uri, e); return false; } }
最后总结下,从整个过程可以看到android系统的一些设计思想
1,设置铃声之前,要先知道有哪些系统铃声,所以需要扫描,android提供了xxx.media这个contentProvider为此服务,对应的数据库为internal.db/external-xx.db
2,拿到铃声,真正需要设置的时候,提供了Setting类管理这个过程,其对应的数据库为settings.db
2.1 首先检查是否涉及到系统的一些安全设置参数,这里定义了Secure类来管理,如果涉及到系统安全,那么又分为两种情况:
2.1.1 如果是查询,则操作secure 表查询
2.1.2 如果是写操作,则直接return
2.2 不涉及到系统安全,就属于正常设置,接着定义了System类管理
3,查询操作的实际操作类NameValueCache, 其中定义了
缓存name/value键值对的集合,避免每次操作都去操作数据库
可以由调用者指定的uri,便于根据uri决定去操作哪张表
以及写操作的NameValueTable类,因为写操作涉及到id, 所以继承了BaseColumns类
=========================================
=========================================以下是转载附带参考:
首先,要得到一个ContentResolver实例,ContentResolver可以这样获取,利用一个Activity或者Service的Context即可。如下所示:
ContentResolver mResolver = ctx.getContentResolver();
上面的那个ctx的就是一个context,Activity.this就是那个Context,这个Context就相当于一个上下文环境。得到这个Context后就可以调用getContentResolver接口获取ContentResolver实例了。ContentResolver实例获得后,就可以进行各种查询,下面我就以音频数据库为例讲解增删改查的方法,视频和图像和音频非常类似。
在讲解各种查询之前,我给大家介绍下怎么看android都提供了哪些多媒体表。在adb shell中,找到/data/data/com.android.providers.media/databases/下,然后找到SD卡的数据库文件(一般是一个.db文件),然后输入命令sqlite3加上这个数据库的名字就可以查询android的多媒体数据库了。.table命令可以列出所有多媒体数据库的表,.scheme加上表名可以查询表中的所有列名。这里可以利用SQL语句来查看你想要的数据,记得最后一定要记住每条语句后面都加上分号。下面开始讲述怎么在这些表上进行增删改查。
查询,代码如下所示:
Cursor cursor = resolver.query(_uri, prjs, selections, selectArgs, order);
ContentResolver的query方法接受几个参数,参数意义如下:
Uri:这个Uri代表要查询的数据库名称加上表的名称。这个Uri一般都直接从MediaStore里取得,例如我要取所有歌的信息,就必须利用MediaStore.Audio.Media. EXTERNAL _CONTENT_URI这个Uri。专辑信息要利用MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI这个Uri来查询,其他查询也都类似。
Prjs:这个参数代表要从表中选择的列,用一个String数组来表示。
Selections:相当于SQL语句中的where子句,就是代表你的查询条件。
selectArgs:这个参数是说你的Selections里有?这个符号是,这里可以以实际值代替这个问号。如果Selections这个没有?的话,那么这个String数组可以为null。
Order:说明查询结果按什么来排序。
上面就是各个参数的意义,它返回的查询结果一个Cursor,这个Cursor就相当于数据库查询的中Result,用法和它差不多。
-------------------------------------------------------------------------------------------------------------------
增加,代码如下所以:
ContentValues values = new ContentValues();
values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER,0);
resolver.insert(_uri, values);
这个insert传递的参数只有两个,一个是Uri(同查询那个Uri),另一个是ContentValues。这个ContentValuses对应于数据库的一行数据,只要用put方法把每个列的设置好之后,直接利用insert方法去插入就好了。
更新,代码如下:
ContentResolver resolver = ctx.getContentResolver();
Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
ContentValues values = new ContentValues();
values.put(MediaStore.Audio.Media.DATE_MODIFIED, sid);
resolver.update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,values, where, selectionArgs);
上面update方法和查询还有增加里的参数都很类似,这里就不再重复叙述了,大家也可直接参考google的文档,那里也写的很清楚。
删除,代码如下:
ContentResolver resolver = ctx.getContentResolver();
nbsp; resolver.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,where, selectionArgs);
delete和更新的方法很类似 。
文章转载:http://blog.csdn.net/djy1992/article/details/9947867