Android 設置來電鈴聲、通知鈴聲、鬧鐘鈴聲中的坑

最新作項目,遇到一個鈴聲設置的bug,一直被延期了大半年,終於忍不住了,花了大半天時間來研究問題所在。其實這個功能百度一下有不少,但大部分都是同一篇文章,咱們項目裏最開始就是參考的百度的代碼片斷來實現的,可是有個bug,對同一個mp3文件設置來電鈴聲後,而後再設置成通知鈴聲或者鬧鐘鈴聲,那原來設置的來電鈴聲就會變成未知鈴聲,同時失效了。就是分析這個問題花了大半天時間,而我發現百度谷歌的這些文章都沒提到這個點,通過反覆的猜想調試,終於找到問題所在,最後發現其實也很簡單,但也是個很容易被忽略的點(在後面問題分析處提到),特此記錄一下。數據庫

##需求描述ide

如圖,下載的mp3鈴聲,點擊三個按鈕分別實現設置三個鈴聲。設置成功後能夠到系統設置鈴音處查看編碼

需求描述.png

##編碼實現.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=1000uri,而後再講這個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再設置一次,或者不作操做。

##最後 固然這只是其中一種方法,還有其餘方法這裏就再也不贅述了。

相關文章
相關標籤/搜索