最新作項目,遇到一個鈴聲設置的bug
,一直被延期了大半年,終於忍不住了,花了大半天時間來研究問題所在。其實這個功能百度一下有不少,但大部分都是同一篇文章,咱們項目裏最開始就是參考的百度的代碼片斷來實現的,可是有個bug
,對同一個mp3
文件設置來電鈴聲
後,而後再設置成通知鈴聲
或者鬧鐘鈴聲
,那原來設置的來電鈴聲就會變成未知鈴聲
,同時失效了。就是分析這個問題花了大半天時間,而我發現百度谷歌的這些文章都沒提到這個點,通過反覆的猜想調試,終於找到問題所在,最後發現其實也很簡單,但也是個很容易被忽略的點(在後面問題分析
處提到),特此記錄一下。數據庫
##需求描述ide
如圖,下載的mp3鈴聲,點擊三個按鈕分別實現設置三個鈴聲。設置成功後能夠到系統設置鈴音處查看編碼
##編碼實現.net
懶得看分析的看官可直接複製如下代碼到本身工程,親測ok) 注意看代碼中註釋的兩個步驟,這是兩個關鍵的地方。就是這個地方讓我研究了大半天,有追求的看官的能夠看繼續日後看
原理分析
和問題分析
。調試
/** * * 設置鈴聲 * * [@param](https://my.oschina.net/u/2303379) type RingtoneManager.TYPE_RINGTONE 來電鈴聲 * RingtoneManager.TYPE_NOTIFICATION 通知鈴聲 * RingtoneManager.TYPE_ALARM 鬧鐘鈴聲 * * [@param](https://my.oschina.net/u/2303379) path 下載下來的mp3全路徑 * [@param](https://my.oschina.net/u/2303379) title 鈴聲的名字 */ public static void setRing(Context context, int type, String path, String title) { Uri oldRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE); //系統當前 通知鈴聲 Uri oldNotification = RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_NOTIFICATION); //系統當前 通知鈴聲 Uri oldAlarm = RingtoneManager.getActualDefaultRingtoneUri(context, RingtoneManager.TYPE_ALARM); //系統當前 鬧鐘鈴聲 File sdfile = new File(path); ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, sdfile.getAbsolutePath()); values.put(MediaStore.MediaColumns.TITLE, title); values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3"); values.put(MediaStore.Audio.Media.IS_RINGTONE, true); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true); values.put(MediaStore.Audio.Media.IS_ALARM, true); values.put(MediaStore.Audio.Media.IS_MUSIC, true); Uri uri = MediaStore.Audio.Media.getContentUriForPath(sdfile.getAbsolutePath()); Uri newUri = null; String deleteId = ""; try { Cursor cursor = context.getContentResolver().query(uri, null, MediaStore.MediaColumns.DATA + "=?", new String[] { path },null); if (cursor.moveToFirst()) { deleteId = cursor.getString(cursor.getColumnIndex("_id")); } LogTool.e("AGameRing", "deleteId:" + deleteId); context.getContentResolver().delete(uri, MediaStore.MediaColumns.DATA + "=\"" + sdfile.getAbsolutePath() + "\"", null); newUri = context.getContentResolver().insert(uri, values); } catch (Exception e) { e.printStackTrace(); } if (newUri != null) { String ringStoneId = ""; String notificationId = ""; String alarmId = ""; if (null != oldRingtoneUri) { ringStoneId = oldRingtoneUri.getLastPathSegment(); } if (null != oldNotification) { notificationId = oldNotification.getLastPathSegment(); } if (null != oldAlarm) { alarmId = oldAlarm.getLastPathSegment(); } Uri setRingStoneUri; Uri setNotificationUri; Uri setAlarmUri; if (type == RingtoneManager.TYPE_RINGTONE || ringStoneId.equals(deleteId)) { setRingStoneUri = newUri; } else { setRingStoneUri = oldRingtoneUri; } if (type == RingtoneManager.TYPE_NOTIFICATION || notificationId.equals(deleteId)) { setNotificationUri = newUri; } else { setNotificationUri = oldNotification; } if (type == RingtoneManager.TYPE_ALARM || alarmId.equals(deleteId)) { setAlarmUri = newUri; } else { setAlarmUri = oldAlarm; } RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_RINGTONE, setRingStoneUri); RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_NOTIFICATION, setNotificationUri); RingtoneManager.setActualDefaultRingtoneUri(context, RingtoneManager.TYPE_ALARM, setAlarmUri); switch (type) { case RingtoneManager.TYPE_RINGTONE: Toast.makeText(context.getApplicationContext(), "設置來電鈴聲成功!", Toast.LENGTH_SHORT).show(); break; case RingtoneManager.TYPE_NOTIFICATION: Toast.makeText(context.getApplicationContext(), "設置通知鈴聲成功!", Toast.LENGTH_SHORT).show(); break; case RingtoneManager.TYPE_ALARM: Toast.makeText(context.getApplicationContext(), "設置鬧鐘鈴聲成功!", Toast.LENGTH_SHORT).show(); break; } } }
##原理分析code
要將一個sd卡的mp3文件設置成爲鈴聲,須要有兩個操做get
一、將mp3文件路徑存到
ContentProvider
裏,獲得相應的uri
二、調用RingtoneManager.setActualDefaultRingtoneUri()
,傳入相應的uri和須要設置的鈴聲類型便可。it
注:ContentProvider能夠簡單的理解成一個系統數據庫,返回的uri其實就是這個數據庫裏某一條數據的惟一標識,含有_id。 用getActivity().getContentResolver().query這個方法能夠查詢到相關數據。 ... values.put(MediaStore.Audio.Media.IS_RINGTONE, true); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true); values.put(MediaStore.Audio.Media.IS_ALARM, true); values.put(MediaStore.Audio.Media.IS_MUSIC, true); ... 這一堆其實就是表裏的字段名和值,有用沒有就看你怎麼運用
##問題分析io
問題主要出在第一步。 要將
mp3
路徑存到ContentProvider
裏,須要調用getActivity().getContentResolver().insert()方法,可是裏面原來有這個鈴聲了的話,是插入不成功的。這個時候爲了保險起見須要調用一次刪除方法getActivity().getContentResolver().delete(),這兩部也基本上成了標配,網上的文章大部分也就這兩步就獲得了須要的uri。ast
若是沒有像我這裏提到的需求,對同一個mp3能夠分開分別設置的話就沒有我這個bug。
**問題就出在delete這個步驟,**假設對一個鈴聲先設置了來電鈴聲獲得_id=1000
的uri
,而後再講這個mp3設置爲通知鈴音,那麼在設置通知鈴聲的時候掉的delete方法會將_id=1000
的這個數據刪了,由於此次要插入的記錄和上一次同樣的,而後再插入新的獲得_id=1001
的uri,這個時候通知鈴聲就是_id=1001
的uri,而以前設置的來電鈴聲讀取的_id=1000
的記錄就沒有了,查看鈴聲的時候就是未知鈴音了。
找到問題所在後,咱們就解決這個問題。
因此我在delete以前先進行了一次query操做,若是查到了就把那條記錄的_id記錄下來,後面再設置鈴聲的時候將三個類型的鈴聲的當前設置的uri經過uri.getLastPathSegment()
獲得其_id,而後和剛剛刪除的_id(deleteId)
進行對比,若是發現同樣就說明以前這個類型的鈴聲就是我如今要插入的鈴聲,就再把新的 _id=1001
的uri設置進去,不然就把獲得的系統的uri再設置一次,或者不作操做。
##最後 固然這只是其中一種方法,還有其餘方法這裏就再也不贅述了。