Android設(shè)置鈴聲分析測試機:小米2.3.5版本
代碼其實沒有幾行,這里簡單記錄下學(xué)習(xí)的過程. Android系統(tǒng)啟動時會掃描系統(tǒng)與SD卡中的對媒體文件,分別存入數(shù)據(jù)庫sqlite中,以contentProvider的形式對外提供服務(wù) 路徑:/data/data/com.android.providers.media/databases/XXX...
可以看到有2個db文件, 一個是系統(tǒng)的,一個是sd卡里的 用SQLite Expert打開internal.db,部分截圖如下:
這里面記錄了音頻audio、視頻video、圖片images的相關(guān)數(shù)據(jù)信息,我們以音頻audio為例,藍(lán)色部分audio_meta就是audio數(shù)據(jù)表,打開之后就可以看到詳細(xì)信息了,里面列出了系統(tǒng)內(nèi)部的所有音頻文件,各個字段在android.provider.MediaStore中都定義有相應(yīng)的常量,如id --- MediaStore.Audio.Media._ID.
而這里面有想說下這四個字段
含義在源碼里都有說明,看了一遍數(shù)據(jù),發(fā)現(xiàn)這四個字段同時有且僅有一個字段為1,也就是對于一個多媒體文件只能是這四種中的一種,默認(rèn)為0,如果是某種類型,則android系統(tǒng)默認(rèn)置為1,所以也就明白了為什么很多掃描系統(tǒng)通知或者來電鈴聲的示例代碼中,都會有一個類似的條件語句:is_notification = 1. 如: /** * 掃描系統(tǒng)內(nèi)部通知鈴聲 */ 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 ......
上面扯了些其他的,關(guān)于設(shè)置鈴聲的方法,系統(tǒng)提供了一個鈴聲管理器android.provider.RingtoneManager,其中提供了獲取與設(shè)置鈴聲的API 如:Uri uri = RingtoneManager.getActualDefaultRingtoneUri(MediaActivity.this, RingtoneManager.TYPE_NOTIFICATION);可以獲取到當(dāng)前系統(tǒng)的通知鈴聲uri 第二個參數(shù)可以指定獲取的鈴聲類型,還有其他的TYPE_RINGTONE,TYPE_ALARM, TYPE_ALL 設(shè)置鈴聲的API: RingtoneManager.setActualDefaultRingtoneUri(MediaActivity.this, RingtoneManager.TYPE_NOTIFICATION, Uri.parse(data.get(position))); 第二個參數(shù)同上,最后一個是指定一個新的Uri, 這里的data.get(position)就是在上面的掃描代碼掃描出的所有通知鈴聲path路徑中選澤一個,然后在解析成一個URI對象傳入即可
那么android是如何獲取指定類型的系統(tǒng)鈴聲呢? 這涉及到另一個類android.provider.Settings 相關(guān)源碼如下: public static Uri getActualDefaultRingtoneUri(Context context, int type) { //根據(jù)指定的類型獲取Settings類中對應(yīng)的類型,這里RingtoneManager.TYPE_NOTIFICATION對應(yīng)的為Settings.System.NOTIFICATION_SOUND,其實也就是下面所說的system表中的一個name字段名 String setting = getSettingForType(type); if (setting == null) return null; //調(diào)用Settings類中靜態(tài)內(nèi)部類System中的相應(yīng)方法 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系統(tǒng)啟動時,會初始化30(目前是30)條涉及系統(tǒng)安全的設(shè)置數(shù)據(jù)(如果http代理設(shè)置,wifi相關(guān)設(shè)置),并且存入數(shù)據(jù)庫中,與多媒體的db不同,系統(tǒng)默認(rèn)存放在settings.db中,路徑為/data/data/com.android.providers.settings/databases,具體是存放在settings.db數(shù)據(jù)庫實例的secure表中,用工具打開,可以看到此表中恰好有30條數(shù)據(jù)。說了那么多,其實這里是檢查你所指定的類型也就是db中的字段在不在這個集合中,如果在,則會調(diào)用Settings類中的另一個靜態(tài)內(nèi)部類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); } //如果不在那個涉及系統(tǒng)安全的設(shè)置集合中,則調(diào)用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中g(shù)etString()方法部分代碼 Cursor c = null; try { //mUri == "content://settings/system"在NameValueCache初始化時賦值,指定查詢的是settings.db中的system表,同理上面提到的Secure類的getString(...)中調(diào)用的也是這個緩存類的同名方法,只不過mUri被指定為查詢secure表(這2個表中除了id,只有name與value2個字段,分別指定設(shè)置的類型與對應(yīng)的值) 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集合中,當(dāng)然如果這個集合中已經(jīng)存在這個鍵值對,那么也就不會執(zhí)行這段操作db的代碼了 mValues.put(name, value); } settings.db結(jié)構(gòu)如下:
上面示例中指定的TYPE_NOTIFICATION的數(shù)據(jù)如下(藍(lán)色部分):
最后返回的就是file:///..........這個String數(shù)據(jù),再轉(zhuǎn)化成URI返回給調(diào)用者
OK,那么設(shè)置鈴聲的API, setAc.......執(zhí)行的過程也類似: public static boolean putString(ContentResolver resolver, String name, String value) { //這里依然是檢查設(shè)置的類型是否涉及到系統(tǒng)預(yù)置的安全設(shè)置集合,如果是,則直接返回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; } //這里執(zhí)行的是另一個靜態(tài)內(nèi)部類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與相應(yīng)value插入db的system表中,如果表中已經(jīng)存在指定的類型字段怎么辦? 請看上面的源碼注釋... resolver.insert(uri, values); return true; } catch (SQLException e) { Log.w(TAG, "Can't set key " + name + " in " + uri, e); return false; } }
最后總結(jié)下,從整個過程可以看到android系統(tǒng)的一些設(shè)計思想 1,設(shè)置鈴聲之前,要先知道有哪些系統(tǒng)鈴聲,所以需要掃描,android提供了xxx.media這個contentProvider為此服務(wù),對應(yīng)的數(shù)據(jù)庫為internal.db/external-xx.db 2,拿到鈴聲,真正需要設(shè)置的時候,提供了Setting類管理這個過程,其對應(yīng)的數(shù)據(jù)庫為settings.db 2.1 首先檢查是否涉及到系統(tǒng)的一些安全設(shè)置參數(shù),這里定義了Secure類來管理,如果涉及到系統(tǒng)安全,那么又分為兩種情況: 2.1.1 如果是查詢,則操作secure 表查詢 2.1.2 如果是寫操作,則直接return 2.2 不涉及到系統(tǒng)安全,就屬于正常設(shè)置,接著定義了System類管理 3,查詢操作的實際操作類NameValueCache, 其中定義了 緩存name/value鍵值對的集合,避免每次操作都去操作數(shù)據(jù)庫 可以由調(diào)用者指定的uri,便于根據(jù)uri決定去操作哪張表 以及寫操作的NameValueTable類,因為寫操作涉及到id, 所以繼承了BaseColumns類 |
|
|