Android10 SystemUI之RingtonePlayer

RingtonePlayer用来实现铃声的播放逻辑。

涉及的类:

IAudioService:系统服务类,管理音频相关业务。
IRingtonePlayer :RingtonePlayer对外提供的AIDL接口。
Client :由请求播放的信息封装而成,使用Ringtone进行播放。
NotificationPlayer :用于异步播放音频,使用MediaPlayer进行播放。

属性

    private IAudioService mAudioService;//系统音频服务类
    private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);//异步播放器
    private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();//Client的缓存
    private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {//铃声播放器的实现
        ...
    }

初始化

   @Override
    public void start() {//所有SystemUI的初始化入口
        mAsyncPlayer.setUsesWakeLock(mContext);//初始化异步播放器的WakeLock
        mAudioService = IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));//获取IAudioService的代理对象
        try {
            mAudioService.setRingtonePlayer(mCallback);//将IRingtonePlayer的实现设置到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);//创建了Ringtone对象设置
            mRingtone.setAudioAttributes(aa);
            mRingtone.setUri(uri, volumeShaperConfig);
        }

        @Override
        public void binderDied() {//死亡通知回调
            synchronized (mClients) {
                mClients.remove(mToken);//从缓存删除
            }
            mRingtone.stop();//停止播放
        }

Clinet主要是封装Ringtone并实现了死亡通知接口

IRingtonePlayer的实现

        @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);//创建新的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被调用时,首先从缓存中找对应的Client,如果没有创建一个,调用Client中的Ringtone的play方法开始播放
当收到停止播放请求即stop被调用时,首先从缓存中移除对应的Client,调用Client中的Ringtone的stop方法停止播放
当对应的IBinder死亡时,触发死亡通知,Client将从缓存移除,并调Ringtone的stop方法停止播放

使用NotificationPlayer中的MediaPlayer播放

当收到开始播放请求即playAsync被调用时,会调用NotificationPlayer.play方法
当收到停止播放请求即stopAsync被调用时,会调用NotificationPlayer.stop方法

    public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
        Command cmd = new Command();//创建命令对象并进行初始化
        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;//将状态设置为播放
        }
    }

    public void stop() {
        synchronized (mCmdQueue) {
            if (mState != STOP) {//当前不是停止状态
                Command cmd = new Command();
                cmd.requestTime = SystemClock.uptimeMillis();
                cmd.code = STOP;//停止命令
                enqueueLocked(cmd);
                mState = STOP;//将状态设置为停止
            }
        }
    }

    private void enqueueLocked(Command cmd) {
        mCmdQueue.add(cmd);//放入队列
        if (mThread == null) {//CmdThread线程没有启动
            acquireWakeLock();//请求WakeLock
            mThread = new CmdThread();
            mThread.start();//创建CmdThread并启动
        }
    }

Command的处理

无论是play还是stop,都是创建Command对象,由CmdThread线程去处理

         public void run() {//CmdThread的run方法
            while (true) {//死循环
                Command cmd = null;
                synchronized (mCmdQueue) {
                    cmd = mCmdQueue.removeFirst();//取出队列中的Command
                }
                switch (cmd.code) {
                case PLAY:
                    startSound(cmd);//调用startSound方法处理播放命令
                    break;
                case STOP:
                    //此处代码先不看
                    break;
                }

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

很明显,CmdThread进行循环取出队列的Command对象进行处理,处理完成释放WakeLock,并将mThread置空。

播放命令的处理

播放命令调用了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方法结束运行
                }
                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);
        }
    }

这里的逻辑是先结束上一个CreationAndCompletionThread的运行,再创建一个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调用,CmdThread线程继续运行
            }
            Looper.loop();//由于没有消息处理,此方法调用后线程会阻塞
        }
    };

CreationAndCompletionThread根据Command中的参数启动MediaPlayer,并唤醒等待的CmdThread,最后调用Looper.loop()阻塞当前线程,直到CmdThread线程调用mLooper.quit(),CreationAndCompletionThread才会结束运行。
小结:CmdThread处理命令,所有命令处理完成即线程结束;CreationAndCompletionThread负责播放,停止播放/播放完成时线程才会结束。

播放完成的处理

      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();//结束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();//意味着结束CreationAndCompletionThread
                            }
                        }
                    } else {
                        Log.w(mTag, "STOP command without a player");
                    }
                    break;
                }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。