audio系列之MediaButton--基于Android7.0
来源:互联网 发布:caffe 图片预处理 编辑:程序博客网 时间:2024/06/06 06:41
AudioManager#registreMediaButtonBroadcastReceiver(PendingIntent);
/** * @hide * no-op if (pi == null) or (eventReceiver == null) */ public void registerMediaButtonIntent(PendingIntent pi, ComponentName eventReceiver) { if (pi == null) { Log.e(TAG, "Cannot call registerMediaButtonIntent() with a null parameter"); return; } MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext()); helper.addMediaButtonListener(pi, eventReceiver, getContext()); }
下面看下MediaSessionLegacyHelper#addMediaButtonListener():
public void addMediaButtonListener(PendingIntent pi, ComponentName mbrComponent, Context context) { if (pi == null) { Log.w(TAG, "Pending intent was null, can't addMediaButtonListener."); return; } SessionHolder holder = getHolder(pi, true); if (holder == null) { return; } if (holder.mMediaButtonListener != null) { // Already have this listener registered if (DEBUG) { Log.d(TAG, "addMediaButtonListener already added " + pi); } } holder.mMediaButtonListener = new MediaButtonListener(pi, context); // TODO determine if handling transport performer commands should also // set this flag holder.mFlags |= MediaSession.FLAG_HANDLES_MEDIA_BUTTONS; holder.mSession.setFlags(holder.mFlags); holder.mSession.setMediaButtonReceiver(pi); holder.update(); if (DEBUG) { Log.d(TAG, "addMediaButtonListener added " + pi); } }
其中在getHolder()中用到了mSessions,是一个ArrayMap。
holder.mSession.setMediaButtonReceiver(pi);分析这句:其中mSession是MediaSession类型
/** * Set a pending intent for your media button receiver to allow restarting * playback after the session has been stopped. If your app is started in * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via * the pending intent. * * @param mbr The {@link PendingIntent} to send the media button event to. */ public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { try { mBinder.setMediaButtonReceiver(mbr); } catch (RemoteException e) { Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e); } }
其中用到了mBinder(ISession),这是一个IInterface,就肯定是把Receiver注册到一个远程对象中了,就是一个注册到一个系统服务中,而这个系统服务持有这个ISession实例。MediaSession应该是媒体会话的意思
它的实现者是private final class SessionStub extends ISession.Stub,而SessionStub是MediaSessionRecord的内部类。
SessionStub#setMediaButtonReceiver()
@Override public void setMediaButtonReceiver(PendingIntent pi) { mMediaButtonReceiver = pi; }由上可知SessionStub和一个媒体会话,即一个MediaButtonReceiver对应。MediaSession和MediaSessionRecord也是。
那在AudioManager注册完Receiver后,AudioManager在截取了ACTION_MEDIA_BUTTONG广播后是如果分配给这些Receiver的,下面接着分析:
MediaSessionLegacyHelper#sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock)是入口:
public void sendMediaButtonEvent(KeyEvent keyEvent, boolean needWakeLock) { if (keyEvent == null) { Log.w(TAG, "Tried to send a null key event. Ignoring."); return; } mSessionManager.dispatchMediaKeyEvent(keyEvent, needWakeLock); if (DEBUG) { Log.d(TAG, "dispatched media key " + keyEvent); } }
最终去到class SessionManagerImpl extends ISessionManager.Stub #public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock),
而SessionManagerImpl是MediaSessionService的内部类。
/** * Handles the dispatching of the media button events to one of the * registered listeners, or if there was none, broadcast an * ACTION_MEDIA_BUTTON intent to the rest of the system. * * @param keyEvent a non-null KeyEvent whose key code is one of the * supported media buttons * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held * while this key event is dispatched. */ @Override public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) { Log.w(TAG, "Attempted to dispatch null or non-media key event."); return; } final int pid = Binder.getCallingPid(); final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { if (DEBUG) { Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event=" + keyEvent); } if (!isUserSetupComplete()) { // Global media key handling can have the side-effect of starting new // activities which is undesirable while setup is in progress. Slog.i(TAG, "Not dispatching media key event because user " + "setup is in progress."); return; } synchronized (mLock) { // If we don't have a media button receiver to fall back on // include non-playing sessions for dispatching UserRecord ur = mUserRecords.get(ActivityManager.getCurrentUser()); boolean useNotPlayingSessions = (ur == null) || (ur.mLastMediaButtonReceiver == null && ur.mRestoredMediaButtonReceiver == null); MediaSessionRecord session = mPriorityStack .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions); if (isVoiceKey(keyEvent.getKeyCode())) { handleVoiceKeyEventLocked(keyEvent, needWakeLock, session); } else { dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); } } } finally { Binder.restoreCallingIdentity(token); } }重点分析这个两句:
MediaSessionRecord session = mPriorityStack .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions); if (isVoiceKey(keyEvent.getKeyCode())) { handleVoiceKeyEventLocked(keyEvent, needWakeLock, session); } else { dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session); }mPriorityStack是MediaSessionStack类型,看getDefaultMEdiaButtonSession()源码,这部分非常重要,就是按怎样的规则去选择栈中record(MediaSessionRecord)的:
/** * Get the highest priority session that can handle media buttons. * * @param userId The user to check. * @param includeNotPlaying Return a non-playing session if nothing else is * available * @return The default media button session or null. */ public MediaSessionRecord getDefaultMediaButtonSession(int userId, boolean includeNotPlaying) { if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) { return mGlobalPrioritySession; } if (mCachedButtonReceiver != null) { return mCachedButtonReceiver; } ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId); if (records.size() > 0) { //在records列表中扫描 MediaSessionRecord record = records.get(0); //判断给session(会话)是否正在播放,如果是则把事件给它。 if (record.isPlaybackActive(false)) { // Since we're going to send a button event to this record make // it the last interesting one. mLastInterestingRecord = record; mCachedButtonReceiver = record; } else if (mLastInterestingRecord != null) { if (records.contains(mLastInterestingRecord)) { mCachedButtonReceiver = mLastInterestingRecord; } else { // That record is no longer used. Clear its reference. mLastInterestingRecord = null; } } //如果includeNotPlaying标志位true,且cache为空,则record不是正在播放也分给它。 if (includeNotPlaying && mCachedButtonReceiver == null) { // If we really want a record and we didn't find one yet use the // highest priority session even if it's not playing. mCachedButtonReceiver = record; } } return mCachedButtonReceiver; }
主要分析一下:
ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);下面是MediaSessionStack#getPriorityLocked()源码:
/** * Get a priority sorted list of sessions. Can filter to only return active * sessions or sessions with specific flags. * * @param activeOnly True to only return active sessions, false to return * all sessions. * @param withFlags Only return sessions with all the specified flags set. 0 * returns all sessions. * @param userId The user to get sessions for. {@link UserHandle#USER_ALL} * will return sessions for all users. * @return The priority sorted list of sessions. */ private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags, int userId) { ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>(); int lastLocalIndex = 0; int lastActiveIndex = 0; int lastPublishedIndex = 0; int size = mSessions.size(); for (int i = 0; i < size; i++) { final MediaSessionRecord session = mSessions.get(i); if (userId != UserHandle.USER_ALL && userId != session.getUserId()) { // Filter out sessions for the wrong user continue; } if ((session.getFlags() & withFlags) != withFlags) { // Filter out sessions with the wrong flags continue; } if (!session.isActive()) { if (!activeOnly) { // If we're getting unpublished as well always put them at // the end result.add(session); } continue; } if (session.isSystemPriority()) { // System priority sessions are special and always go at the // front. We expect there to only be one of these at a time. result.add(0, session); lastLocalIndex++; lastActiveIndex++; lastPublishedIndex++; } else if (session.isPlaybackActive(true)) { // TODO this with real local route check if (true) { // Active local sessions get top priority result.add(lastLocalIndex, session); lastLocalIndex++; lastActiveIndex++; lastPublishedIndex++; } else { // Then active remote sessions result.add(lastActiveIndex, session); lastActiveIndex++; lastPublishedIndex++; } } else { // inactive sessions go at the end in order of whoever last did // something. result.add(lastPublishedIndex, session); lastPublishedIndex++; } } return result; }看上面的源码加载arraylist的index为位置的record优先级是最高的。
isSystemPriority() isActive() isPlayBackActive() 然后是其他的,只要是Record的userId对的话都会加入arraylist中,
只是优先级不一样。
其中 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);,看源码:
private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock, MediaSessionRecord session) { if (session != null) { if (DEBUG) { Log.d(TAG, "Sending media key to " + session.toString()); } if (needWakeLock) { mKeyEventReceiver.aquireWakeLockLocked(); } // If we don't need a wakelock use -1 as the id so we // won't release it later session.sendMediaButton(keyEvent, needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mKeyEventReceiver); } else { // Launch the last PendingIntent we had with priority int userId = ActivityManager.getCurrentUser(); UserRecord user = mUserRecords.get(userId); if (user.mLastMediaButtonReceiver != null || user.mRestoredMediaButtonReceiver != null) { if (DEBUG) { Log.d(TAG, "Sending media key to last known PendingIntent " + user.mLastMediaButtonReceiver + " or restored Intent " + user.mRestoredMediaButtonReceiver); } if (needWakeLock) { mKeyEventReceiver.aquireWakeLockLocked(); } Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); try { if (user.mLastMediaButtonReceiver != null) { user.mLastMediaButtonReceiver.send(getContext(), needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1, mediaButtonIntent, mKeyEventReceiver, null); } else { mediaButtonIntent.setComponent(user.mRestoredMediaButtonReceiver); getContext().sendBroadcastAsUser(mediaButtonIntent, new UserHandle(userId)); } } catch (CanceledException e) { Log.i(TAG, "Error sending key event to media button receiver " + user.mLastMediaButtonReceiver, e); } } else { if (DEBUG) { Log.d(TAG, "Sending media key ordered broadcast"); } if (needWakeLock) { mMediaEventWakeLock.acquire(); } // Fallback to legacy behavior Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); if (needWakeLock) { keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); } getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.CURRENT, null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null); } } }
0 0
- audio系列之MediaButton--基于Android7.0
- Audio系列之AudioFocus
- Android7.0之FileProvider
- Audio系列之音量键
- Audio系列之静音控制
- Android7.0 之 行为变更
- Android7.0 之 直接启动
- Android7.0 之 行为变更
- Android7.0之Activty详解
- Android7.0 Settings 源码剖析-系列列表
- WmS详解(一)之token到底是什么?基于Android7.0源码
- Android Audio控制和MediaButton远程控制(音视频控制配合)
- HTML5学习系列之Audio(1)
- [RK3288][Android6.0] 按键(MediaButton)控制音乐播放功能
- 开启Android7.0探索之旅
- Android7.0探索之旅--init进程
- Android7.0之从零开始:Intent及IntentFilter
- Android7.0文件操作之FileProvider
- [LeetCode]232. Implement Queue using Stacks
- eclipse下导入项目源码工程
- Kotlin中功能操作与集合(KAD 11)
- JAVA多线程相关的问题50道
- 倒计时
- audio系列之MediaButton--基于Android7.0
- Linux——CentOS7firewalld取代iptales
- ES6语法中的let
- java定时任务方式详解
- hive1.2.1安装
- SqlServer加序号
- 【Mrpc】Demo4 客户端动态代理的创建
- 通过IDOC实现EDI与SAP集成(一)
- C# 解析Xml文件