美文网首页
Android MediaSession使用解析

Android MediaSession使用解析

作者: 雷涛赛文 | 来源:发表于2022-12-08 15:54 被阅读0次

           前段时间在做项目中接手了一项功能,具体就是有音乐播放时,需要在状态栏上进行实时展示,并进行双向控制,实现上使用到了MediaSession,为什么使用MediaSession呢?主要是为了解耦,减少了状态栏与各音乐应用进程的直接通信,主流音乐应用都会使用MediaSession,闲暇之余看了一下MediaSession的相关逻辑实现,本文针对Android11对MediaSession的主要功能进行分析。
           在开始分析之前,先对本文进行概括一下,主要分为4部分:
           1.创建MediaSession服务端,实现对应的功能;
           2.客户端接收MediaSession创建回调,获取MediaSession对应的MediaController;
           3.MediaSession服务端控制媒体播放;
           4.客户端控制MediaSession服务端媒体播放;
           主要涉及的类有:MediaSession、MediaController、MediaSessionManager、MediaSessionService、MediaSessionRecord等,先看一下类关系图:


    MediaSession类图.png

    1. 创建MediaSession

           音乐应用需要创建MediaSession,简单看一下实现:

        private void initMediaSession() {
            MediaSession mediaSession = new MediaSession(this, getPackageName());
            mediaSession.setCallback(new MediaSession.Callback() {
                @Override
                public void onCommand(@NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
                    super.onCommand(command, args, cb);
                }
    
                @Override
                public void onPlay() {
                    super.onPlay();
                }
    
                @Override
                public void onPause() {
                    super.onPause();
                }
            });
            Intent intent = new Intent();
            intent.setComponent(new ComponentName(getPackageName(), "com.hly.testActivity"));
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            mediaSession.setSessionActivity(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
            mediaSession.setActive(true);
        }
    

           在创建MediaSession时,主要做了三件事:
           1.setCallBack():客户端控制媒体播放时,会进行通知回调,执行对应的播放控制动作;
           2.setSessionActivity():传入媒体播放界面对应的PendingIntent,客户端通过该PendingIntent可以快速切换到播放界面;
           3.setActive():设置该MediaSession为active即活跃状态,每次设置都会触发客户端onActiveSessionChanged(),获取新的MediaController列表;
           接下来根据源码来看一下具体的逻辑实现:

    1.1.MediaSession.java

           首先看一下构造方法:

        public MediaSession(@NonNull Context context, @NonNull String tag,
                @Nullable Bundle sessionInfo) {
            ........
            mMaxBitmapSize = context.getResources().getDimensionPixelSize(
                    com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
            mCbStub = new CallbackStub(this);
            MediaSessionManager manager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
            try {
                mBinder = manager.createSession(mCbStub, tag, sessionInfo);
                mSessionToken = new Token(Process.myUid(), mBinder.getController());
                mController = new MediaController(context, mSessionToken);
            } catch (RemoteException e) {
                throw new RuntimeException("Remote error creating session.", e);
            }
        }
    

           在构造方法内,进行一些变量的初始化及实例化:
           1.mMaxBitmapSize:对应Metadata中Bitmap的最大宽度或高度,此处对应的是320dp;
           2.创建CallbackStub实例mCbStub,CallbackStub继承ISessionCallback.Stub,用来接收客户端发生的控制命令,后面章节再进行分析;
           3.通过MediaSessionManager的createSession()来创建ISession实例mBinder;接下来进行分析;
           4.创建Token实例mSessionToken;
           5.创建MediaSession对应的MediaController;
           接下来先分析createSession():

    1.2.MediaSessionManager.java

       public ISession createSession(@NonNull MediaSession.CallbackStub cbStub, @NonNull String tag,
                @Nullable Bundle sessionInfo) {
            try {
                return mService.createSession(mContext.getPackageName(), cbStub, tag, sessionInfo,
                        UserHandle.myUserId());
            } catch (RemoteException e) {
                throw new RuntimeException(e);
            }
        }
    

           mService是ISessionManager.Stub实例,ISessionManager.Stub是在MediaSessionService内部进行实现的;

    1.3.MediaSessionService.java

    class SessionManagerImpl extends ISessionManager.Stub {
        .....................
        .....................
        Override
        public ISession createSession(String packageName, ISessionCallback cb, String tag,
                    Bundle sessionInfo, int userId) throws RemoteException {
            .....................
            try {
                enforcePackageName(packageName, uid);
                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
                        false /* allowAll */, true /* requireFull */, "createSession", packageName);
                if (cb == null) {
                    throw new IllegalArgumentException("Controller callback cannot be null");
                }
                MediaSessionRecord session = createSessionInternal(
                        pid, uid, resolvedUserId, packageName, cb, tag, sessionInfo);
                if (session == null) {
                     hrow new IllegalStateException("Failed to create a new session record");
                }
                ISession sessionBinder = session.getSessionBinder();
                if (sessionBinder == null) {
                    throw new IllegalStateException("Invalid session record");
                }
                return sessionBinder;
            } catch (Exception e) {
                Slog.w(TAG, "Exception in creating a new session", e);
                throw e;
            } finally {
                Binder.restoreCallingIdentity(token);
            }
        }
    }
    

           在该方法内主要两项工作:
           1.通过createSessionInternal()创建MediaSessionRecord实例session;
           2.通过session的getSessionBinder()返回ISession实例;

        private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
                String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
            synchronized (mLock) {
                int policies = 0;
                if (mCustomSessionPolicyProvider != null) {
                    policies = mCustomSessionPolicyProvider.getSessionPoliciesForApplication(
                            callerUid, callerPackageName);
                }
    
                FullUserRecord user = getFullUserRecordLocked(userId);
                if (user == null) {
                    Log.w(TAG, "Request from invalid user: " +  userId + ", pkg=" + callerPackageName);
                    throw new RuntimeException("Session request from invalid user.");
                }
    
                final int sessionCount = user.mUidToSessionCount.get(callerUid, 0);
                if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID
                        && !hasMediaControlPermission(callerPid, callerUid)) {
                    throw new RuntimeException("Created too many sessions. count="
                            + sessionCount + ")");
                }
    
                final MediaSessionRecord session;
                try {
                    session = new MediaSessionRecord(callerPid, callerUid, userId,
                            callerPackageName, cb, tag, sessionInfo, this,
                            mRecordThread.getLooper(), policies);
                } catch (RemoteException e) {
                    throw new RuntimeException("Media Session owner died prematurely.", e);
                }
    
                user.mUidToSessionCount.put(callerUid, sessionCount + 1);
    
                user.mPriorityStack.addSession(session);
                mHandler.postSessionsChanged(session);
    
                if (DEBUG) {
                    Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
                }
                return session;
            }
        }
    

           通过以上代码可以看到:
           1.先进行一些判断,FullUserRecord不能为空,sessionCount不能超过100;
           2.创建MediaSessionRecord实例;
           3.将进程的sessionCount交给mUidToSessionCount进行管理,将先创建的session交给mPriorityStack进行管理,后续在客户端回调时会用到;
           4.执行mHandler.postSessionsChanged(session)来通知客户端activeSessions回调,后面再讲;

    1.4.MediaSessionRecord.java

        public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
                ISessionCallback cb, String tag, Bundle sessionInfo,
                MediaSessionService service, Looper handlerLooper, int policies)
                throws RemoteException {
            mOwnerPid = ownerPid;
            mOwnerUid = ownerUid;
            mUserId = userId;
            mPackageName = ownerPackageName;
            mTag = tag;
            mSessionInfo = sessionInfo;
            mController = new ControllerStub();
            mSessionToken = new MediaSession.Token(ownerUid, mController);
            mSession = new SessionStub();
            mSessionCb = new SessionCb(cb);
            mService = service;
            mContext = mService.getContext();
            mHandler = new MessageHandler(handlerLooper);
            mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
            mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class);
            mAudioAttrs = DEFAULT_ATTRIBUTES;
            mPolicies = policies;
    
            // May throw RemoteException if the session app is killed.
            mSessionCb.mCb.asBinder().linkToDeath(this, 0);
        }
    

           在构造方法内,进行了实例初始化:
           1.创建ControllerStub实例mController,ControllerStub继承了ISessionController.Stub,主要用来接收来自客户端的控制;
           2.创建MediaSession.Token实例mSessionToken;
           3.创建SessionStub实例mSession,SessionStub继承ISession.Stub,主要用来初始化MediaSession实例变量,比如:setSessionActivity()等;
           4.创建SessionCb实例mSessionCb,mSessionCb接收到来自客户端的控制调用mSessionCb,然后在其内部调用cb最终调用到MediaSession构造方法内部的CallbackStub实例mCbStub;
           前面在MediaSessionService内部的createSession()创建MediaSessionRecord实例,然后调用getSessionBinder()返回ISession实例,即对应SessionStub实例mSession;

    1.5.总结

           MediaSession构造方法内部的createSession()最终返回的是MediaSessionRecord的实例mSession,用一张图总结一下执行过程:


    MediaSession创建流程.png

    2.MediaSession创建完成回调

           在MediaSession创建完成后,会回调给客户端进行实时监听,关于回调在上面已经分析到,具体实现是在MediaSessionService的createSessionInternal()内部创建完MediaSessionRecord实例后会执行mHandler.postSessionsChanged(session),一起看一下:

    2.1.MediaSessionService.java

        public void postSessionsChanged(MediaSessionRecordImpl record) {
                // Use object instead of the arguments when posting message to remove pending requests.
                Integer userIdInteger = mIntegerCache.get(record.getUserId());
                if (userIdInteger == null) {
                    userIdInteger = Integer.valueOf(record.getUserId());
                    mIntegerCache.put(record.getUserId(), userIdInteger);
                }
    
                int msg = (record instanceof MediaSessionRecord)
                        ? MSG_SESSIONS_1_CHANGED : MSG_SESSIONS_2_CHANGED;
                removeMessages(msg, userIdInteger);
                obtainMessage(msg, userIdInteger).sendToTarget();
            }
    
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_SESSIONS_1_CHANGED:
                        pushSession1Changed((int) msg.obj);
                        break;
                    case MSG_SESSIONS_2_CHANGED:
                        pushSession2Changed((int) msg.obj);
                        break;
                }
            }
    

           跟随调用关系:

        private void pushSession1Changed(int userId) {
            synchronized (mLock) {
                FullUserRecord user = getFullUserRecordLocked(userId);
                if (user == null) {
                    Log.w(TAG, "pushSession1ChangedOnHandler failed. No user with id=" + userId);
                    return;
                }
                List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
                int size = records.size();
                ArrayList<MediaSession.Token> tokens = new ArrayList<>();
                for (int i = 0; i < size; i++) {
                    tokens.add(records.get(i).getSessionToken());
                }
                pushRemoteVolumeUpdateLocked(userId);
                for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
                    SessionsListenerRecord record = mSessionsListeners.get(i);
                    if (record.userId == USER_ALL || record.userId == userId) {
                        try {
                            record.listener.onActiveSessionsChanged(tokens);
                        } catch (RemoteException e) {
                            Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
                                    e);
                            mSessionsListeners.remove(i);
                        }
                    }
                }
            }
        }
    

           1.先通过getActiveSessionsLocked()获取到MediaSessionRecord列表,是通过mPriorityStack中获取的;
           2.创建MediaSession.Token列表,遍历执行MediaSessionRecord的getSessionToken()方法来获取对应的MediaSession.Token;
           3.遍历mSessionsListeners执行record.listener.onActiveSessionsChanged(tokens)回调给客户端;
           当然了,客户端必须先注册,才能接收到回调,先看一下客户端注册过程;

    2.2.MediaSessionManager.java

        public void addOnActiveSessionsChangedListener(
                @NonNull OnActiveSessionsChangedListener sessionListener,
                @Nullable ComponentName notificationListener, int userId, @Nullable Handler handler) {
            if (sessionListener == null) {
                throw new IllegalArgumentException("listener may not be null");
            }
            if (handler == null) {
                handler = new Handler();
            }
            synchronized (mLock) {
                if (mListeners.get(sessionListener) != null) {
                    Log.w(TAG, "Attempted to add session listener twice, ignoring.");
                    return;
                }
                SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener,
                        handler);
                try {
                    mService.addSessionsListener(wrapper.mStub, notificationListener, userId);
                    mListeners.put(sessionListener, wrapper);
                } catch (RemoteException e) {
                    Log.e(TAG, "Error in addOnActiveSessionsChangedListener.", e);
                }
            }
        }
    

           先创建内部类SessionsChangedWrapper实例wrapper,然后将其内部变量mStub作为参数传递给MediaSessionService的addSessionsListener方法;

        @Override
        public void addSessionsListener(IActiveSessionsListener listener,
                    ComponentName componentName, int userId) throws RemoteException {
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final long token = Binder.clearCallingIdentity();
    
            try {
                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
                synchronized (mLock) {
                    int index = findIndexOfSessionsListenerLocked(listener);
                    if (index != -1) {
                        Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
                        return;
                    }
                    SessionsListenerRecord record = new SessionsListenerRecord(listener,
                            componentName, resolvedUserId, pid, uid);
                    try {
                        listener.asBinder().linkToDeath(record, 0);
                    } catch (RemoteException e) {
                        Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
                        return;
                    }
                    mSessionsListeners.add(record);
                }
           } finally {
                Binder.restoreCallingIdentity(token);
           }
       }
    

           将listener封装成SessionsListenerRecord对象,最终存入mSessionsListeners进行管理,用来后续通知回调(前面可以看到);
           再看一下SessionsChangedWrapper的实现:

    2.2.1.SessionsChangedWrapper

    private static final class SessionsChangedWrapper {
        private Context mContext;
        private OnActiveSessionsChangedListener mListener;
        private Handler mHandler;
    
        public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener,
                    Handler handler) {
            mContext = context;
            mListener = listener;
            mHandler = handler;
        }
    
        private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() {
            @Override
            public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) {
                final Handler handler = mHandler;
                if (handler != null) {
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            final Context context = mContext;
                            if (context != null) {
                                ArrayList<MediaController> controllers = new ArrayList<>();
                                int size = tokens.size();
                                for (int i = 0; i < size; i++) {
                                    controllers.add(new MediaController(context, tokens.get(i)));
                                }
                                final OnActiveSessionsChangedListener listener = mListener;
                                if (listener != null) {
                                    listener.onActiveSessionsChanged(controllers);
                                }
                            }
                        }
                    });
                }
            }
        };
    
        private void release() {
            mListener = null;
            mContext = null;
            mHandler = null;
        }
    }
    

           跟随调用关系,最终会调用到该类的onActiveSessionsChanged()方法,在该方法内会遍历tokens来获取MediaSessionRecord对应的MediaSession.Token,创建MediaController[注意一下:每次有ActivieSession变化,都会返回不同的MediaController列表],最终回调到客户端的是与MediaSession相关联的MediaController列表;

    3.客户端控制

    3.1.MediaController.java

        public MediaController(@NonNull Context context, @NonNull MediaSession.Token token) {
            if (context == null) {
                throw new IllegalArgumentException("context shouldn't be null");
            }
            if (token == null) {
                throw new IllegalArgumentException("token shouldn't be null");
            }
            if (token.getBinder() == null) {
                throw new IllegalArgumentException("token.getBinder() shouldn't be null");
            }
            mSessionBinder = token.getBinder();
            mTransportControls = new TransportControls();
            mToken = token;
            mContext = context;
        }
    

           在MediaController构造方法内部,主要执行了两项工作:
           1.通过getBinder()获取ISessionController实例mSessionBinder,从名字可以看到是用来控制MediaSession的;
           2.创建TransportControls()实例mTransportControls,客户端用来执行控制;

    3.1.1. TransportControls

    public final class TransportControls {
        ..............
        public void play() {
            try {
                mSessionBinder.play(mContext.getPackageName());
            } catch (RemoteException e) {
                Log.wtf(TAG, "Error calling play.", e);
            }
        }
        .....................
        public void pause() {
            try {
                mSessionBinder.pause(mContext.getPackageName());
            } catch (RemoteException e) {
                Log.wtf(TAG, "Error calling pause.", e);
            }
        }
        .....................
    }
    

           TransportControls最终是通过mSessionBinder来进行命令控制,mSessionBinder是通过token.getBinder()来获取,反过来看一下MediaSession.Token实现:

    3.2.MediaSession.Token

    public static final class Token implements Parcelable {
    
        private final int mUid;
        private final ISessionController mBinder;
    
        public Token(int uid, ISessionController binder) {
            mUid = uid;
            mBinder = binder;
        }
    
        Token(Parcel in) {
            mUid = in.readInt();
            mBinder = ISessionController.Stub.asInterface(in.readStrongBinder());
        }
    
        public ISessionController getBinder() {
            return mBinder;
        }
    
    }
    

           再回到MediaSessionRecord内部看一下该mBinder的由来:

    3.3.MediaSessionRecord.java

    public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
                ISessionCallback cb, String tag, Bundle sessionInfo,
                MediaSessionService service, Looper handlerLooper, int policies)
                throws RemoteException {
        ..............
        mController = new ControllerStub();
        mSessionToken = new MediaSession.Token(ownerUid, mController);
        mSessionCb = new SessionCb(cb);
        ............
    }
    

            ISessionController实例是由ControllerStub创建而来,所以当执行控制时,调用的是ControllerStub内部的方法:

    3.3.1.ControllerStub

    class ControllerStub extends ISessionController.Stub {
        @Override
        public void sendCommand(String packageName, String command, Bundle args,
                    ResultReceiver cb) {
            mSessionCb.sendCommand(packageName, Binder.getCallingPid(), Binder.getCallingUid(),
                    command, args, cb);
        }
        ..................
        @Override
        public void play(String packageName) {
            mSessionCb.play(packageName, Binder.getCallingPid(), Binder.getCallingUid());
        }
        ....................
       @Override
        public void pause(String packageName) {
            mSessionCb.pause(packageName, Binder.getCallingPid(), Binder.getCallingUid());
       }
    }
    

            在ControllerStub内部的方法又会调用到SessionCb的方法:

    3.3.2.SessionCb

    class SessionCb {
        private final ISessionCallback mCb;
    
        SessionCb(ISessionCallback cb) {
            mCb = cb;
        }
    
        public void sendCommand(String packageName, int pid, int uid, String command, Bundle args,
                    ResultReceiver cb) {
                try {
                    mCb.onCommand(packageName, pid, uid, command, args, cb);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Remote failure in sendCommand.", e);
                }
            }
    
    
        public void play(String packageName, int pid, int uid) {
                try {
                    mCb.onPlay(packageName, pid, uid);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Remote failure in play.", e);
                }
            }
    
        public void pause(String packageName, int pid, int uid) {
                try {
                    mCb.onPause(packageName, pid, uid);
                } catch (RemoteException e) {
                    Slog.e(TAG, "Remote failure in pause.", e);
                }
            }
    

            SessionCb会调用到mCb,即MediaSession内部的CallbackStub实例;

    3.4.MediaSession.CallbackStub

    public static class CallbackStub extends ISessionCallback.Stub {
        .............
        @Override
        public void onCommand(String packageName, int pid, int uid, String command, Bundle args,
                    ResultReceiver cb) {
            MediaSession session = mMediaSession.get();
            if (session != null) {
                session.dispatchCommand(createRemoteUserInfo(packageName, pid, uid),
                        command, args, cb);
            }
        }
    
        @Override
        public void onPlay(String packageName, int pid, int uid) {
            MediaSession session = mMediaSession.get();
            if (session != null) {
                session.dispatchPlay(createRemoteUserInfo(packageName, pid, uid));
            }
        }
    
        @Override
        public void onPause(String packageName, int pid, int uid) {
            MediaSession session = mMediaSession.get();
            if (session != null) {
                session.dispatchPause(createRemoteUserInfo(packageName, pid, uid));
            }
       }
    }
    

           跟随调用关系,最终会调用到mCallback的方法,前面已经讲到,mCallBack是音乐应用在创建MediaSession时,传入的本地实现;

        case MSG_COMMAND:
             Command cmd = (Command) obj;
              mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
              break;
         case MSG_PLAY:
              mCallback.onPlay();
              break;
          case MSG_PAUSE:
              mCallback.onPause();
              break;
    

           音乐应用内部对应实现对应的方法,那么控制就生效了。

    3.5.总结

           客户端MediaController通过TransportControls来进行控制,最终会对应到服务端MediaSession的Callback,对应关系如下:

    TransportControls MediaSession.Callback
    play() onPlay()
    pause() onPause
    stop() onStop
    skipToNext() onSkipToNext()

    4.MediaSession端控制

    4.1.MediaSession.java

           直接看代码,播放暂停相关控制通过setPlaybackState(),PlaybackState:STATE_PAUSED = 2、STATE_PLAYING = 3;

        public void setPlaybackState(@Nullable PlaybackState state) {
            mPlaybackState = state;
            try {
                mBinder.setPlaybackState(state);
            } catch (RemoteException e) {
                Log.wtf(TAG, "Dead object in setPlaybackState.", e);
            }
        }
    

           媒体信息发生变化时,可以通过setMetadata来更新MediaMetadata信息就可以了;

        public void setMetadata(@Nullable MediaMetadata metadata) {
            long duration = -1;
            int fields = 0;
            MediaDescription description = null;
            if (metadata != null) {
                metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
                if (metadata.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
                    duration = metadata.getLong(MediaMetadata.METADATA_KEY_DURATION);
                }
                fields = metadata.size();
                description = metadata.getDescription();
            }
            String metadataDescription = "size=" + fields + ", description=" + description;
    
            try {
                mBinder.setMetadata(metadata, duration, metadataDescription);
            } catch (RemoteException e) {
                Log.wtf(TAG, "Dead object in setPlaybackState.", e);
            }
        }
    

           通过上面的分析,mBinder是通过MediaSessionRecord的getSessionBinder()来返回的,接下来一起看一下执行过程:

    4.2.MediaSessionRecord.java

           getSessionBinder()返回的是SessionStub实例:

    private final class SessionStub extends ISession.Stub {
        .............
        @Override
        public void setPlaybackState(PlaybackState state) throws RemoteException {
            int oldState = mPlaybackState == null
                    ? PlaybackState.STATE_NONE : mPlaybackState.getState();
            int newState = state == null
                    ? PlaybackState.STATE_NONE : state.getState();
            boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
                    || (!TRANSITION_PRIORITY_STATES.contains(oldState)
                    && TRANSITION_PRIORITY_STATES.contains(newState));
            synchronized (mLock) {
                mPlaybackState = state;
            }
            final long token = Binder.clearCallingIdentity();
            try {
                mService.onSessionPlaybackStateChanged(
                        MediaSessionRecord.this, shouldUpdatePriority);
            } finally {
                Binder.restoreCallingIdentity(token);
            }
            mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
        }
        .................
    }
    

           通过该方法可以看到,主要有四项工作:
           1.首先判断当前的playState及新的playState,然后来确定shouldUpdatePriority的值,该值表示当前的MediaSession处于活跃状态(比如:由暂停到播放);
           2.更新mPlaybackState值为最新的playbackstate;
           3.执行mService.onSessionPlaybackStateChanged()来通知MediaSessionSession来根据shouldUpdatePriority来确定是否需要更新相关状态;

        void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
                boolean shouldUpdatePriority) {
            synchronized (mLock) {
                FullUserRecord user = getFullUserRecordLocked(record.getUserId());
                if (user == null || !user.mPriorityStack.contains(record)) {
                    Log.d(TAG, "Unknown session changed playback state. Ignoring.");
                    return;
                }
                user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
            }
        }
    

           如果shouldUpdatePriority为true,则将record放在mSessions的首位,反之将mCachedVolumeDefault置空;

        public void onPlaybackStateChanged(
                MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
            if (shouldUpdatePriority) {
                mSessions.remove(record);
                mSessions.add(0, record);
                clearCache(record.getUserId());
            } else if (record.checkPlaybackActiveState(false)) {
                // Just clear the volume cache when a state goes inactive
                mCachedVolumeDefault = null;
            }
            ..........
        }
    

           4.执行mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE)来通知客户端进行状态更新;

        private void pushPlaybackStateUpdate() {
            synchronized (mLock) {
                if (mDestroyed) {
                    return;
                }
                for (int i = mControllerCallbackHolders.size() - 1; i >= 0; i--) {
                    ISessionControllerCallbackHolder holder = mControllerCallbackHolders.get(i);
                    try {
                        holder.mCallback.onPlaybackStateChanged(mPlaybackState);
                    } catch (DeadObjectException e) {
                        mControllerCallbackHolders.remove(i);
                        logCallbackException("Removing dead callback in pushPlaybackStateUpdate",
                                holder, e);
                    } catch (RemoteException e) {
                        logCallbackException("unexpected exception in pushPlaybackStateUpdate",
                                holder, e);
                    }
                }
            }
        }
    

           可以看到,发生消息来通知回调,当然了,客户端要想接收回调,肯定需要先进行注册,通过MediaController来进行注册,一起看一下:

    4.3.MediaController.java

        public void registerCallback(@NonNull Callback callback) {
            registerCallback(callback, null);
        }
    
        public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
            .........
            synchronized (mLock) {
                addCallbackLocked(callback, handler);
            }
        }
    
        private void addCallbackLocked(Callback cb, Handler handler) {
            if (getHandlerForCallbackLocked(cb) != null) {
                Log.w(TAG, "Callback is already added, ignoring");
                return;
            }
            MessageHandler holder = new MessageHandler(handler.getLooper(), cb);
            mCallbacks.add(holder);
            holder.mRegistered = true;
    
            if (!mCbRegistered) {
                try {
                    mSessionBinder.registerCallback(mContext.getPackageName(), mCbStub);
                    mCbRegistered = true;
                } catch (RemoteException e) {
                    Log.e(TAG, "Dead object in registerCallback", e);
                }
            }
        }
    

           跟随调用关系,可以看到:
           1.首先将cb封装到MessageHandler内部,然后放入mCallbacks进行管理;
           2.执行mSessionBinder的registerCallback()方法,mCbStub对应的是CallbackStub实例,用来接收回调:

    private static final class CallbackStub extends ISessionControllerCallback.Stub {
        private final WeakReference<MediaController> mController;
    
        CallbackStub(MediaController controller) {
            mController = new WeakReference<MediaController>(controller);
        }
        .................
        @Override
        public void onPlaybackStateChanged(PlaybackState state) {
            MediaController controller = mController.get();
            if (controller != null) {
                controller.postMessage(MSG_UPDATE_PLAYBACK_STATE, state, null);
            }
        }
    
        @Override
        public void onMetadataChanged(MediaMetadata metadata) {
            MediaController controller = mController.get();
            if (controller != null) {
                controller.postMessage(MSG_UPDATE_METADATA, metadata, null);
            }
        }
        ..................
    }
    

    前面分析到,mSessionBinders是通过token.getBinder()来获得的,最终返回的是MediaSessionRecord的ControllerStub实例mController,看一下对应实现:

    4.4.MediaSessionRecord.ControllerStub

    class ControllerStub extends ISessionController.Stub {
        ................
        @Override
        public void registerCallback(String packageName, ISessionControllerCallback cb) {
            synchronized (mLock) {
                // If this session is already destroyed tell the caller and
                // don't add them.
                if (mDestroyed) {
                    try {
                        cb.onSessionDestroyed();
                    } catch (Exception e) {
                        // ignored
                    }
                    return;
                }
                if (getControllerHolderIndexForCb(cb) < 0) {
                    mControllerCallbackHolders.add(new ISessionControllerCallbackHolder(cb,
                            packageName, Binder.getCallingUid()));
                }
            }
        }
    }
    

           将ISessionControllerCallback封装到ISessionControllerCallbackHolder中,然后加入到mControllerCallbackHolders进行管理,根据前面讲到的,有状态变化时通知回调就是对mControllerCallbackHolders进行holder.mCallback.onPlaybackStateChanged(mPlaybackState)遍历通知,再返回到MediaController内部的CallbackStub:

        public void onPlaybackStateChanged(PlaybackState state) {
            MediaController controller = mController.get();
            if (controller != null) {
                controller.postMessage(MSG_UPDATE_PLAYBACK_STATE, state, null);
            }
        }
    
        case MSG_UPDATE_PLAYBACK_STATE:
            mCallback.onPlaybackStateChanged((PlaybackState) msg.obj);
            break;
    

    4.5.总结

           服务端MediaSession进行控制,最终会对应到客户端MediaController的Callback,对应关系如下:

    MediaSession MediaController.Callback
    setMetadata(MediaMetadata) onMetadataChanged(MediaMetadata)
    setPlaybackState(PlaybackState) onPlaybackStateChanged(PlaybackState)

           客户端MediaController与服务端MediaSession双向控制图如下:


    MediaController与MediaSession双向控制.png

           以上就是MediaSession工作的相关流程,详细逻辑还需要通过源码进一步了解!

    相关文章

      网友评论

          本文标题:Android MediaSession使用解析

          本文链接:https://www.haomeiwen.com/subject/noinfdtx.html