Android MediaSession使用解析

2022-12-08  本文已影响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工作的相关流程,详细逻辑还需要通过源码进一步了解!

上一篇下一篇

猜你喜欢

热点阅读