700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 来电铃声播放流程总结

来电铃声播放流程总结

时间:2020-11-07 08:59:55

相关推荐

来电铃声播放流程总结

迁移自本人cnblog旧文:

近期发现不少关于来电铃声出现无声问题,分析这个问题,需要先了解来电的流程,本篇先对该流程做个大概的总结。

一、播放流程准备工作

来电的时候,通过telecom那边的Ringer类启动播放:

packages/services/Telecomm/src/com/android/server/telecom/Ringer.java

mRingtonePlayer.play(mRingtoneFactory, foregroundCall);

而mRingtonePlayer通过AsyncRingtonePlayer构造的:

packages/services/Telecomm/src/com/android/server/telecom/AsyncRingtonePlayer.java

/** Plays the ringtone. */public void play(RingtoneFactory factory, Call incomingCall) {Log.d(this, "Posting play.");SomeArgs args = SomeArgs.obtain();args.arg1 = factory;args.arg2 = incomingCall;postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);}

这里通过消息传递在子线程中调用了handlePlaye方法:

@Overridepublic void handleMessage(Message msg) {switch(msg.what) {case EVENT_PLAY:handlePlay((SomeArgs) msg.obj);break;case EVENT_REPEAT:handleRepeat();break;case EVENT_STOP:handleStop();break;}}

handlePlay方法中通过调用factory.getRingtone初始化了铃声播放的设置:

if (mRingtone == null) {mRingtone = factory.getRingtone(incomingCall);if (mRingtone == null) {Uri ringtoneUri = incomingCall.getRingtone();String ringtoneUriString = (ringtoneUri == null) ? "null" :ringtoneUri.toSafeString();Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +"factory. Skipping ringing. Uri was: " + ringtoneUriString);return;}}

在factory.getRingtone中,初始化了默认的uri以及stream类型:

packages/services/Telecomm/src/com/android/server/telecom/RingtoneFactory.java

public Ringtone getRingtone(Call incomingCall) {// Use the default ringtone of the work profile if the contact is a work profile contact.Context userContext = isWorkContact(incomingCall) ?getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :getContextForUserHandle(mCallsManager.getCurrentUserHandle());Uri ringtoneUri = incomingCall.getRingtone();Ringtone ringtone = null;if(ringtoneUri != null && userContext != null) {// Ringtone URI is explicitly specified. First, try to create a Ringtone with that.ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri);}if(ringtone == null) {// Contact didn't specify ringtone or custom Ringtone creation failed. Get default// ringtone for user or profile.Context contextToUse = hasDefaultRingtoneForUser(userContext) ? userContext : mContext;Uri defaultRingtoneUri;if (UserManager.get(contextToUse).isUserUnlocked(contextToUse.getUserId())) {defaultRingtoneUri = RingtoneManager.getActualDefaultRingtoneUri(contextToUse,RingtoneManager.TYPE_RINGTONE);} else {defaultRingtoneUri = Settings.System.DEFAULT_RINGTONE_URI;}if (defaultRingtoneUri == null) {return null;}ringtone = RingtoneManager.getRingtone(contextToUse, defaultRingtoneUri);}if (ringtone != null) {ringtone.setStreamType(AudioManager.STREAM_RING);}return ringtone;}

这里通过获取ringtone,传入了默认的uri,进而在RingtoneManager的getRingtone方法中进行了设置:

frameworks/base/media/java/android/media/RingtoneManager.java

private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) {try {final Ringtone r = new Ringtone(context, true); //set allowRemote trueif (streamType >= 0) {//FIXME deprecated callr.setStreamType(streamType);}r.setUri(ringtoneUri);return r;} catch (Exception ex) {Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);}return null;}

ringtone的setUri创建了了mediaplayer,并设置了相应的参数:

frameworks/base/media/java/android/media/Ringtone.java

try {mLocalPlayer.setDataSource(mContext, mUri);mLocalPlayer.setAudioAttributes(mAudioAttributes);synchronized (mPlaybackSettingsLock) {applyPlaybackProperties_sync();}mLocalPlayer.prepare();} catch (SecurityException | IOException e) {destroyLocalPlayer();if (!mAllowRemote) {Log.w(TAG, "Remote playback not allowed: " + e);}}

这里需要注意,执行setDataSource的时候,传入的uri在外置存储的路径下,会引发SecurityException,不能直接通过system_server进程播放,需要通过远程调用其他进程进行播放,这点后面再说明下。

这里远程调用和本地调用的播放器设置在applyPlayerProperties_sync()中:

if (mLocalPlayer != null) {mLocalPlayer.setVolume(mVolume);mLocalPlayer.setLooping(mIsLooping);} else if (mAllowRemote && (mRemotePlayer != null)) {try {mRemotePlayer.setPlaybackProperties(mRemoteToken, mVolume, mIsLooping);} catch (RemoteException e) {Log.w(TAG, "Problem setting playback properties: ", e);}}

以上基本完成完成了播放的准备工作。

二、播放流程

在AsyncRingtonePlayer的handlePlay方法最后又调用了handleRepeat流程,而该方法中通过调用ringtone的play方法进行播放:

public void play() {if (mLocalPlayer != null) {// do not play ringtones if stream volume is 0// (typically because ringer mode is silent).if (mAudioManager.getStreamVolume(AudioAttributes.toLegacyStreamType(mAudioAttributes)) != 0) {startLocalPlayer();}} else if (mAllowRemote && (mRemotePlayer != null)) {final Uri canonicalUri = mUri.getCanonicalUri();final boolean looping;final float volume;synchronized (mPlaybackSettingsLock) {looping = mIsLooping;volume = mVolume;}try {mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping);} catch (RemoteException e) {if (!playFallbackRingtone()) {Log.w(TAG, "Problem playing ringtone: " + e);}}} else {if (!playFallbackRingtone()) {Log.w(TAG, "Neither local nor remote playback available");}}}

这个涉及到本地和远程播放流程,其判断依据为mAllowRemote的逻辑,而这个判断与ringtone对象的初始化有关,前面通过RingtoneManager的getRingtone的时候已经设置为true,因而创建了mRemotePlayer对象:

public Ringtone(Context context, boolean allowRemote) {mContext = context;mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);mAllowRemote = allowRemote;mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;mRemoteToken = allowRemote ? new Binder() : null;}

下面来分析本地、远程、以及异常播放的流程:

1)若传入的uri为系统内置的音频资源,这个时候在setUri的时候就会成功创建mLocalPlayer,这个时候走的是系统进程播放的流程,会调用本地播放方法:

private void startLocalPlayer() {if (mLocalPlayer == null) {return;}synchronized (sActiveRingtones) {sActiveRingtones.add(this);}mLocalPlayer.setOnCompletionListener(mCompletionListener);mLocalPlayer.start();}

接下来就是Mediaplayer的start方法了,这里的具体逻辑涉及到native的mediaplayerserver的实现,目前我们暂时不关注其实现;

2)若传入的uri为外置存储的音频资源,这个时候在setUri的时候因为抛了SecurityException会执行destroyLocalPlayer,这个时候就会进入远程播放的流程:

try {mRemotePlayer.play(mRemoteToken, canonicalUri, mAudioAttributes, volume, looping);} catch (RemoteException e) {if (!playFallbackRingtone()) {Log.w(TAG, "Problem playing ringtone: " + e);}}

前面我们已经分析了mRemotePlayer的回调方法是通过systemui的RingtonePlayer播放的,这里RingtonePlayer 启动的时候,会注册audioservice的回调:

frameworks/base/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java

@Overridepublic void start() {mAsyncPlayer.setUsesWakeLock(mContext);mAudioService = IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));try {mAudioService.setRingtonePlayer(mCallback);} catch (RemoteException e) {Log.e(TAG, "Problem registering RingtonePlayer: " + e);}}

play的方法在实现回调接口中:

private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {@Overridepublic void play(IBinder token, Uri uri, AudioAttributes aa, float volume, boolean looping)throws RemoteException {if (LOGD) {Log.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="+ Binder.getCallingUid() + ")");}Client client;synchronized (mClients) {client = mClients.get(token);if (client == null) {final UserHandle user = Binder.getCallingUserHandle();client = new Client(token, uri, user, aa);token.linkToDeath(client, 0);mClients.put(token, client);}}client.mRingtone.setLooping(looping);client.mRingtone.setVolume(volume);client.mRingtone.play();}......

这里的mRingtone又通过client的实现来得到的:

public Client(IBinder token, Uri uri, UserHandle user, AudioAttributes aa) {mToken = token;mRingtone = new Ringtone(getContextForUser(user), false);mRingtone.setAudioAttributes(aa);mRingtone.setUri(uri);}

这里可以看到client内部构造Ringtone时,关闭了远程调用,通过传入自己的uri成功调用了本地播放器,所以这里的client.mRingtone.play()最终通过startLocalPlayer启动播放器。

这里需要注意token的状态,每次播放的时候创建的token会到通过mClients的HashMap保存,以便在binderDied和回调stop的时候释放对应的资源,这里的token是在构造ringtone的时候创建的:

mRemoteToken = allowRemote ? new Binder() : null;

3)若本地和远程出现问题,此时会进入下面流程:

在Ringtone的play方法中,若远程和本地均播放失败时,均会执行playFallbackRingtone方法:

AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(com.android.internal.R.raw.fallbackring);if (afd != null) {mLocalPlayer = new MediaPlayer();if (afd.getDeclaredLength() < 0) {mLocalPlayer.setDataSource(afd.getFileDescriptor());} else {mLocalPlayer.setDataSource(afd.getFileDescriptor(),afd.getStartOffset(),afd.getDeclaredLength());}

这里会通过本地调用播放系统预装的fallbackring.ogg音频资源,其路径如下:

frameworks/base/core/res/res/raw/fallbackring.ogg

至此播放流程已大致走完。

三、总结

通过上面大概的流程可以总结如下:

1、播放铃声的时候,根据传入的uri会有不同的策略播放,内置资源通过系统进程播放,外置资源的通过systemui传入新的uri播放,本地和远程均异常时系统会尝试播放系统默认资源;

2、播放外置或内置铃声的判断在于设置数据源的安全异常,若关闭selinux的权限,这两种方式都可以通过系统进程播放;

3、通过RingtonePlayer远程播放铃声时,需要注意传入的token对应的资源是否在播放完成后进行了释放。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。