Android10 SystemUI之RingtonePlayer

RingtonePlayer用來實現(xiàn)鈴聲的播放邏輯宪卿。

涉及的類:

IAudioService:系統(tǒng)服務類福澡,管理音頻相關(guān)業(yè)務甫贯。
IRingtonePlayer :RingtonePlayer對外提供的AIDL接口涛漂。
Client :由請求播放的信息封裝而成篇梭,使用Ringtone進行播放氢橙。
NotificationPlayer :用于異步播放音頻,使用MediaPlayer進行播放恬偷。

屬性

    private IAudioService mAudioService;//系統(tǒng)音頻服務類
    private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);//異步播放器
    private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();//Client的緩存
    private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {//鈴聲播放器的實現(xiàn)
        ...
    }

初始化

   @Override
    public void start() {//所有SystemUI的初始化入口
        mAsyncPlayer.setUsesWakeLock(mContext);//初始化異步播放器的WakeLock
        mAudioService = IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));//獲取IAudioService的代理對象
        try {
            mAudioService.setRingtonePlayer(mCallback);//將IRingtonePlayer的實現(xiàn)設置到IAudioService中
        } catch (RemoteException e) {
            Log.e(TAG, "Problem registering RingtonePlayer: " + e);
        }
    }

Clinet

        //implements IBinder.DeathRecipient
        Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa,
                @Nullable VolumeShaper.Configuration volumeShaperConfig) {
            mToken = token;
            mRingtone = new Ringtone(getContextForUser(user), false);//創(chuàng)建了Ringtone對象設置
            mRingtone.setAudioAttributes(aa);
            mRingtone.setUri(uri, volumeShaperConfig);
        }

        @Override
        public void binderDied() {//死亡通知回調(diào)
            synchronized (mClients) {
                mClients.remove(mToken);//從緩存刪除
            }
            mRingtone.stop();//停止播放
        }

Clinet主要是封裝Ringtone并實現(xiàn)了死亡通知接口

IRingtonePlayer的實現(xiàn)

        @Override
        public void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)
                throws RemoteException {
            playWithVolumeShaping(token, uri, aa, volume, looping, null);
        }

        @Override
        public void playWithVolumeShaping(IBinder token, Uri uri, AudioAttributes aa, float volume,
                boolean looping, @Nullable VolumeShaper.Configuration volumeShaperConfig)
                throws RemoteException {
            Client client;
            synchronized (mClients) {
                client = mClients.get(token);//緩存中獲取
                if (client == null) {//緩存中沒找到
                    final UserHandle user = Binder.getCallingUserHandle();
                    client = new Client(token, uri, user, aa, volumeShaperConfig);//創(chuàng)建新的Client
                    token.linkToDeath(client, 0);//注冊死亡通知
                    mClients.put(token, client);//加入緩存
                }
            }
            client.mRingtone.setLooping(looping);
            client.mRingtone.setVolume(volume);
            client.mRingtone.play();//播放
        }

        @Override
        public void stop(IBinder token) {
            Client client;
            synchronized (mClients) {
                client = mClients.remove(token);//從緩存移除
            }
            if (client != null) {
                client.mToken.unlinkToDeath(client, 0);//注銷死亡通知
                client.mRingtone.stop();//停止播放
            }
        }

        //...

        @Override
        public void playAsync(Uri uri, UserHandle user, boolean looping, AudioAttributes aa) {
            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                throw new SecurityException("Async playback only available from system UID.");
            }
            if (UserHandle.ALL.equals(user)) {
                user = UserHandle.SYSTEM;
            }
            mAsyncPlayer.play(getContextForUser(user), uri, looping, aa);//使用異步播放器進行播放
        }

        @Override
        public void stopAsync() {
            if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                throw new SecurityException("Async playback only available from system UID.");
            }
            mAsyncPlayer.stop();//使用異步播放器停止播放
        }

從上可知悍手,播放方式有2種:使用Clinet中的Ringstone播放,使用NotificationPlayer中的MediaPlayer播放

使用Client中的Ringtone播放

當收到開始播放請求即play/playWithVolumeShaping被調(diào)用時袍患,首先從緩存中找對應的Client坦康,如果沒有創(chuàng)建一個,調(diào)用Client中的Ringtone的play方法開始播放
當收到停止播放請求即stop被調(diào)用時协怒,首先從緩存中移除對應的Client涝焙,調(diào)用Client中的Ringtone的stop方法停止播放
當對應的IBinder死亡時,觸發(fā)死亡通知孕暇,Client將從緩存移除仑撞,并調(diào)Ringtone的stop方法停止播放

使用NotificationPlayer中的MediaPlayer播放

當收到開始播放請求即playAsync被調(diào)用時,會調(diào)用NotificationPlayer.play方法
當收到停止播放請求即stopAsync被調(diào)用時妖滔,會調(diào)用NotificationPlayer.stop方法

    public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
        Command cmd = new Command();//創(chuàng)建命令對象并進行初始化
        cmd.requestTime = SystemClock.uptimeMillis();
        cmd.code = PLAY;//播放命令
        cmd.context = context;
        cmd.uri = uri;
        cmd.looping = looping;
        cmd.attributes = attributes;
        synchronized (mCmdQueue) {
            enqueueLocked(cmd);
            mState = PLAY;//將狀態(tài)設置為播放
        }
    }

    public void stop() {
        synchronized (mCmdQueue) {
            if (mState != STOP) {//當前不是停止狀態(tài)
                Command cmd = new Command();
                cmd.requestTime = SystemClock.uptimeMillis();
                cmd.code = STOP;//停止命令
                enqueueLocked(cmd);
                mState = STOP;//將狀態(tài)設置為停止
            }
        }
    }

    private void enqueueLocked(Command cmd) {
        mCmdQueue.add(cmd);//放入隊列
        if (mThread == null) {//CmdThread線程沒有啟動
            acquireWakeLock();//請求WakeLock
            mThread = new CmdThread();
            mThread.start();//創(chuàng)建CmdThread并啟動
        }
    }

Command的處理

無論是play還是stop隧哮,都是創(chuàng)建Command對象,由CmdThread線程去處理

         public void run() {//CmdThread的run方法
            while (true) {//死循環(huán)
                Command cmd = null;
                synchronized (mCmdQueue) {
                    cmd = mCmdQueue.removeFirst();//取出隊列中的Command
                }
                switch (cmd.code) {
                case PLAY:
                    startSound(cmd);//調(diào)用startSound方法處理播放命令
                    break;
                case STOP:
                    //此處代碼先不看
                    break;
                }

                synchronized (mCmdQueue) {
                    if (mCmdQueue.size() == 0) {//隊列命令處理完成
                        mThread = null;
                        releaseWakeLock();//釋放WakeLock
                        return;
                    }
                }
            }
        }

很明顯座舍,CmdThread進行循環(huán)取出隊列的Command對象進行處理沮翔,處理完成釋放WakeLock,并將mThread置空曲秉。

播放命令的處理

播放命令調(diào)用了startSound(cmd)來進一步進行處理

    private void startSound(Command cmd) {
        try {
            synchronized(mCompletionHandlingLock) {
                if((mLooper != null)
                        && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
                    mLooper.quit();//這里的mLooper是CreationAndCompletionThread的Looper采蚀,這行代碼意味著mLooper.loop方法結(jié)束運行
                }
                mCompletionThread = new CreationAndCompletionThread(cmd);//這是真正播放的線程
                synchronized (mCompletionThread) {
                    mCompletionThread.start();//啟動線程
                    mCompletionThread.wait();//等待 CmdThread等待 將會在MediaPlayer啟動后喚醒
                }
            }
            //...
        }
        catch (Exception e) {
            Log.w(mTag, "error loading sound for " + cmd.uri, e);
        }
    }

這里的邏輯是先結(jié)束上一個CreationAndCompletionThread的運行疲牵,再創(chuàng)建一個CreationAndCompletionThread并啟動。

      public void run() {//CreationAndCompletionThread的run方法
            Looper.prepare();//初始化線程的Looper
            mLooper = Looper.myLooper();
            MediaPlayer player = null;
            synchronized(this) {
                AudioManager audioManager =
                    (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
                try {
                    player = new MediaPlayer();
                    if (mCmd.attributes == null) {
                        mCmd.attributes = new AudioAttributes.Builder()
                                .setUsage(AudioAttributes.USAGE_NOTIFICATION)
                                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                                .build();
                    }
                    player.setAudioAttributes(mCmd.attributes);
                    player.setDataSource(mCmd.context, mCmd.uri);
                    player.setLooping(mCmd.looping);
                    player.setOnCompletionListener(NotificationPlayer.this);
                    player.setOnErrorListener(NotificationPlayer.this);
                    player.prepare();
                    if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
                            && (mCmd.uri.getEncodedPath().length() > 0)) {
                        if (!audioManager.isMusicActiveRemotely()) {
                            synchronized (mQueueAudioFocusLock) {
                                if (mAudioManagerWithAudioFocus == null) {
                                    int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
                                    if (mCmd.looping) {
                                        focusGain = AudioManager.AUDIOFOCUS_GAIN;
                                    }
                                    mNotificationRampTimeMs = audioManager.getFocusRampTimeMs(
                                            focusGain, mCmd.attributes);
                                    audioManager.requestAudioFocus(null, mCmd.attributes,
                                                focusGain, 0);
                                    mAudioManagerWithAudioFocus = audioManager;
                                } else {
                                    if (DEBUG) Log.d(mTag, "AudioFocus was previously requested");
                                }
                            }
                        }
                    }
                    try {
                        Thread.sleep(mNotificationRampTimeMs);
                    } catch (InterruptedException e) {
                        Log.e(mTag, "Exception while sleeping to sync notification playback"
                                + " with ducking", e);
                    }
                    player.start();//播放
                } catch (Exception e) {
                    if (player != null) {
                        player.release();//異常釋放
                        player = null;
                    }
                    abandonAudioFocusAfterError();
                }
                final MediaPlayer mp;
                synchronized (mPlayerLock) {
                    mp = mPlayer;
                    mPlayer = player;
                }
                if (mp != null) {
                    mp.release();//釋放上一次使用的MediaPlayer
                }
                this.notify();//喚醒等待 也就是喚醒startSound方法中的wait調(diào)用榆鼠,CmdThread線程繼續(xù)運行
            }
            Looper.loop();//由于沒有消息處理纲爸,此方法調(diào)用后線程會阻塞
        }
    };

CreationAndCompletionThread根據(jù)Command中的參數(shù)啟動MediaPlayer,并喚醒等待的CmdThread妆够,最后調(diào)用Looper.loop()阻塞當前線程识啦,直到CmdThread線程調(diào)用mLooper.quit(),CreationAndCompletionThread才會結(jié)束運行神妹。
小結(jié):CmdThread處理命令颓哮,所有命令處理完成即線程結(jié)束;CreationAndCompletionThread負責播放鸵荠,停止播放/播放完成時線程才會結(jié)束冕茅。

播放完成的處理

      public void onCompletion(MediaPlayer mp) {
        synchronized(mQueueAudioFocusLock) {
            if (mAudioManagerWithAudioFocus != null) {
                if (DEBUG) Log.d(mTag, "onCompletion() abandonning AudioFocus");
                mAudioManagerWithAudioFocus.abandonAudioFocus(null);
                mAudioManagerWithAudioFocus = null;
            } else {
                if (DEBUG) Log.d(mTag, "onCompletion() no need to abandon AudioFocus");
            }
        }
        synchronized (mCmdQueue) {
            synchronized(mCompletionHandlingLock) {
                if ((mCmdQueue.size() == 0)) {
                    if (mLooper != null) {
                        mLooper.quit();//結(jié)束CreationAndCompletionThread
                    }
                    mCompletionThread = null;
                }
            }
        }
        synchronized (mPlayerLock) {
            if (mp == mPlayer) {
                mPlayer = null;
            }
        }
        if (mp != null) {
            mp.release();//釋放
        }
    }

停止命令的處理

                  case STOP:
                    final MediaPlayer mp;
                    synchronized (mPlayerLock) {
                        mp = mPlayer;
                        mPlayer = null;
                    }
                    if (mp != null) {
                        long delay = SystemClock.uptimeMillis() - cmd.requestTime;
                        if (delay > 1000) {
                            Log.w(mTag, "Notification stop delayed by " + delay + "msecs");
                        }
                        try {
                            mp.stop();//停止播放
                        } catch (Exception e) { }
                        mp.release();
                        synchronized(mQueueAudioFocusLock) {
                            if (mAudioManagerWithAudioFocus != null) {
                                if (DEBUG) { Log.d(mTag, "in STOP: abandonning AudioFocus"); }
                                mAudioManagerWithAudioFocus.abandonAudioFocus(null);
                                mAudioManagerWithAudioFocus = null;
                            }
                        }
                        synchronized (mCompletionHandlingLock) {
                            if ((mLooper != null) &&
                                    (mLooper.getThread().getState() != Thread.State.TERMINATED))
                            {
                                if (DEBUG) { Log.d(mTag, "in STOP: quitting looper "+ mLooper); }
                                mLooper.quit();//意味著結(jié)束CreationAndCompletionThread
                            }
                        }
                    } else {
                        Log.w(mTag, "STOP command without a player");
                    }
                    break;
                }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腰鬼,隨后出現(xiàn)的幾起案子嵌赠,更是在濱河造成了極大的恐慌,老刑警劉巖熄赡,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姜挺,死亡現(xiàn)場離奇詭異,居然都是意外死亡彼硫,警方通過查閱死者的電腦和手機炊豪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拧篮,“玉大人词渤,你說我怎么就攤上這事〈ǎ” “怎么了缺虐?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長礁凡。 經(jīng)常有香客問我高氮,道長,這世上最難降的妖魔是什么顷牌? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任剪芍,我火速辦了婚禮,結(jié)果婚禮上窟蓝,老公的妹妹穿的比我還像新娘罪裹。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布状共。 她就那樣靜靜地躺著套耕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪口芍。 梳的紋絲不亂的頭發(fā)上箍铲,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音鬓椭,去河邊找鬼。 笑死关划,一個胖子當著我的面吹牛小染,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贮折,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼裤翩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了调榄?” 一聲冷哼從身側(cè)響起踊赠,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎每庆,沒想到半個月后筐带,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡缤灵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年伦籍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腮出。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡帖鸦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胚嘲,到底是詐尸還是另有隱情作儿,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布馋劈,位于F島的核電站攻锰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏侣滩。R本人自食惡果不足惜口注,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望君珠。 院中可真熱鬧寝志,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乐导,卻和暖如春苦丁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背物臂。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工旺拉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棵磷。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓蛾狗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親仪媒。 傳聞我的和親對象是個殘疾皇子沉桌,可洞房花燭夜當晚...
    茶點故事閱讀 44,947評論 2 355