Android中Ringtone播放詳解【安卓源碼解析五】

         現在咱們來聊聊android系統中鈴聲的播放,從framework層面說說Ringtone的播放原理,我在android源碼中碰到了播放系統鈴聲中的問題,所以仔細研究了這方面的知識,現在整理一下,給讀者一些幫助,現在我對鈴聲的播放和設置特別親切,不管是短信鈴聲方面的,來電鈴聲,還是日曆鈴聲,email的鈴聲,鬧鐘的鈴聲,都能解決鈴聲方面的bug。前面我說了Notification的播放機制,Android中Notification的framework層講解【安卓源碼解析四】 .  大概給大家說了說,有想了解的可以參考看一看。鈴聲播放的機制都是用MediaPlayer來播放的,通過MediaPlayer來申請AudioManager機制來播放音樂鈴聲的。前面我寫了個調用系統鈴聲和sdcard卡中鈴聲的demo,Android中鈴聲總結【安卓源碼解析一】.這個例子中有一段代碼:通過intent的action來啓動一個選擇系統鈴聲的dialog,這個到底是啓動的哪兒的dialog,今天給大家揭祕一下,希望給大家帶來點幫助:大明原創,轉載請標明出處:http://blog.csdn.net/wdaming1986/article/details/7166134

      寫一個intent來啓動播放ringtone的activity:   

 Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);  

        // Allow user to pick 'Default'   

        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);  

        // Show only ringtones   

        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);  

        //set the default Notification value   

        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));  

        // Don't show 'Silent'   

        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);  

        startActivityForResult(intent, SMS_RINGTONE_PICKED);  


這個activity啓動後就跑到了framework的com.android.internal.app.RingtonePickerActivity裏面去了,在framework部分也可以寫activity類得,這個挺有意思的,哈哈,新發現,有種“山窮水復疑無路,柳暗花明又一村”的感覺!哈哈,這個intent的action發到哪兒去了呢??

 

           在frameworks\base\core\res\AndroidManifest.xml中可以搜到這個action:    

       <activity android:name="com.android.internal.app.RingtonePickerActivity"
                android:theme="@style/Theme.Dialog.Alert"
                android:excludeFromRecents="true"
                android:multiprocess="true">
            <intent-filter>
                <action android:name="android.intent.action.RINGTONE_PICKER" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

<action android:name="android.intent.action.RINGTONE_PICKER" />這個就是接受這個intent的action動作的對應的activity的類,找這個RingtonePickerActivity.java類,這個類的繼承和實現有點特殊,注意觀察:

public final class RingtonePickerActivity extends AlertActivity implements
        AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener,
        AlertController.AlertParams.OnPrepareListViewListener

這個類繼承AlertActivity並實現了這麼多接口,找到點擊每個選項播放的方法監聽:

    private DialogInterface.OnClickListener mRingtoneClickListener =
            new DialogInterface.OnClickListener() {

        /*
         * On item clicked
         */
        public void onClick(DialogInterface dialog, int which) {
            // Save the position of most recently clicked item
            mClickedPos = which;
            
            // Play clip
            playRingtone(which, 0);
        }
        
    };

然後找到playRingtone(which, 0);這個方法去:

 private void playRingtone(int position, int delayMs) {
        mHandler.removeCallbacks(this);
        mSampleRingtonePos = position;
        mHandler.postDelayed(this, delayMs);
    }

這個postDelayed(this, delayMs);this代表當前對象,因爲這個類是實現了Runnable接口,所以這時候會走到run方法中去

 public void run() {
        
        if (mSampleRingtonePos == mSilentPos) {
            mRingtoneManager.stopPreviousRingtone();
            return;
        }
        
        /*
         * Stop the default ringtone, if it's playing (other ringtones will be
         * stopped by the RingtoneManager when we get another Ringtone from it.
         * by bw on start 
         * when it`s not null set the default ringtone is null
         * modify by wangxianming in 2011-12-28
         */
        if (mDefaultRingtone != null) {
///            if(mDefaultRingtone.isPlaying()){
                mDefaultRingtone.stop();
///           }
            mDefaultRingtone = null;
        }
        
        Ringtone ringtone;
        if (mSampleRingtonePos == mDefaultRingtonePos) {
            if (mDefaultRingtone == null) {
                mDefaultRingtone = RingtoneManager.getRingtone(this, mUriForDefaultItem);
            }
            ringtone = mDefaultRingtone;
            
            /*
             * Normally the non-static RingtoneManager.getRingtone stops the
             * previous ringtone, but we're getting the default ringtone outside
             * of the RingtoneManager instance, so let's stop the previous
             * ringtone manually.
             */
            mRingtoneManager.stopPreviousRingtone();
            
        } else {
            ringtone = mRingtoneManager.getRingtone(getRingtoneManagerPosition(mSampleRingtonePos));
        }
        
        if (ringtone != null) {
            ringtone.play();
        }
    }

這時候找到這裏就基本上找到根源了,ringtone.play();就是播放ringtone音樂的方法;注意:15行,16行,我對源碼進行了修改,這個有點問題,源碼對原生態的音樂格式支持的很好,但是對mid格式的音樂支持的不太好,所以如果播放mid格式的音樂不能用多個mediaplayer來播放,只能用一個來播放,只能hold住一個,不能重複hold這個ringtone,所以把這個mDefaultRingtone = null;每次都置爲空,這樣每次都是一個對象就可以了。這樣播放系統鈴聲就沒有問題了!


 

       再去frameworks\base\media\java\android\media\Ringtone.java這個類去找到play()方法:   

public void play() {
        if (mAudio == null) {
            try {
                openMediaPlayer();
            } catch (Exception ex) {
                Log.e(TAG, "play() caught ", ex);
                mAudio = null;
            }
        }
        if (mAudio != null) {
            // do not ringtones if stream volume is 0
            // (typically because ringer mode is silent).
            if (mAudioManager.getStreamVolume(mStreamType) != 0) {
                if (mIsLoop){
                    mAudio.setLooping(true);            
                }   
                mAudio.start();
            }
        }
    }

找到openMediaPlayer()方法 :

private void openMediaPlayer() throws IOException {
        mAudio = new MediaPlayer();
        if (mUri != null) {
            mAudio.setDataSource(mContext, mUri);
        } else if (mFileDescriptor != null) {
            mAudio.setDataSource(mFileDescriptor);
        } else if (mAssetFileDescriptor != null) {
            // Note: using getDeclaredLength so that our behavior is the same
            // as previous versions when the content provider is returning
            // a full file.
            if (mAssetFileDescriptor.getDeclaredLength() < 0) {
                mAudio.setDataSource(mAssetFileDescriptor.getFileDescriptor());
            } else {
                mAudio.setDataSource(mAssetFileDescriptor.getFileDescriptor(),
                        mAssetFileDescriptor.getStartOffset(),
                        mAssetFileDescriptor.getDeclaredLength());
            }
        } else {
            throw new IOException("No data source set.");
        }
        mAudio.setAudioStreamType(mStreamType);
        mAudio.prepare();
    }

這樣就把所有的ringtone播放的機制大致能搞清楚了!希望這個流程給大家一些啓發!有問題的可以留言,我看到會給出解釋的,希望高手指點不足之處!大明原創。

 

發佈了88 篇原創文章 · 獲贊 25 · 訪問量 124萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章