Android MediaSession使用解析

???????前段時間在做項目中接手了一項功能,具體就是有音樂播放時,需要在狀態(tài)欄上進(jìn)行實時展示,并進(jìn)行雙向控制,實現(xiàn)上使用到了MediaSession,為什么使用MediaSession呢?主要是為了解耦,減少了狀態(tài)欄與各音樂應(yīng)用進(jìn)程的直接通信,主流音樂應(yīng)用都會使用MediaSession,閑暇之余看了一下MediaSession的相關(guān)邏輯實現(xiàn),本文針對Android11對MediaSession的主要功能進(jìn)行分析。
???????在開始分析之前,先對本文進(jìn)行概括一下,主要分為4部分:
???????1.創(chuàng)建MediaSession服務(wù)端,實現(xiàn)對應(yīng)的功能;
???????2.客戶端接收MediaSession創(chuàng)建回調(diào),獲取MediaSession對應(yīng)的MediaController;
???????3.MediaSession服務(wù)端控制媒體播放;
???????4.客戶端控制MediaSession服務(wù)端媒體播放;
???????主要涉及的類有:MediaSession、MediaController、MediaSessionManager、MediaSessionService、MediaSessionRecord等,先看一下類關(guān)系圖:


MediaSession類圖.png

1. 創(chuàng)建MediaSession

???????音樂應(yīng)用需要創(chuàng)建MediaSession,簡單看一下實現(xiàn):

    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);
    }

???????在創(chuàng)建MediaSession時,主要做了三件事:
???????1.setCallBack():客戶端控制媒體播放時,會進(jìn)行通知回調(diào),執(zhí)行對應(yīng)的播放控制動作;
???????2.setSessionActivity():傳入媒體播放界面對應(yīng)的PendingIntent,客戶端通過該P(yáng)endingIntent可以快速切換到播放界面;
???????3.setActive():設(shè)置該MediaSession為active即活躍狀態(tài),每次設(shè)置都會觸發(fā)客戶端onActiveSessionChanged(),獲取新的MediaController列表;
???????接下來根據(jù)源碼來看一下具體的邏輯實現(xiàn):

1.1.MediaSession.java

???????首先看一下構(gòu)造方法:

    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);
        }
    }

???????在構(gòu)造方法內(nèi),進(jìn)行一些變量的初始化及實例化:
???????1.mMaxBitmapSize:對應(yīng)Metadata中Bitmap的最大寬度或高度,此處對應(yīng)的是320dp;
???????2.創(chuàng)建CallbackStub實例mCbStub,CallbackStub繼承ISessionCallback.Stub,用來接收客戶端發(fā)生的控制命令,后面章節(jié)再進(jìn)行分析;
???????3.通過MediaSessionManager的createSession()來創(chuàng)建ISession實例mBinder;接下來進(jìn)行分析;
???????4.創(chuàng)建Token實例mSessionToken;
???????5.創(chuàng)建MediaSession對應(yīng)的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內(nèi)部進(jìn)行實現(xiàn)的;

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);
        }
    }
}

???????在該方法內(nèi)主要兩項工作:
???????1.通過createSessionInternal()創(chuàng)建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.先進(jìn)行一些判斷,F(xiàn)ullUserRecord不能為空,sessionCount不能超過100;
???????2.創(chuàng)建MediaSessionRecord實例;
???????3.將進(jìn)程的sessionCount交給mUidToSessionCount進(jìn)行管理,將先創(chuàng)建的session交給mPriorityStack進(jìn)行管理,后續(xù)在客戶端回調(diào)時會用到;
???????4.執(zhí)行mHandler.postSessionsChanged(session)來通知客戶端activeSessions回調(diào),后面再講;

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);
    }

???????在構(gòu)造方法內(nèi),進(jìn)行了實例初始化:
???????1.創(chuàng)建ControllerStub實例mController,ControllerStub繼承了ISessionController.Stub,主要用來接收來自客戶端的控制;
???????2.創(chuàng)建MediaSession.Token實例mSessionToken;
???????3.創(chuàng)建SessionStub實例mSession,SessionStub繼承ISession.Stub,主要用來初始化MediaSession實例變量,比如:setSessionActivity()等;
???????4.創(chuàng)建SessionCb實例mSessionCb,mSessionCb接收到來自客戶端的控制調(diào)用mSessionCb,然后在其內(nèi)部調(diào)用cb最終調(diào)用到MediaSession構(gòu)造方法內(nèi)部的CallbackStub實例mCbStub;
???????前面在MediaSessionService內(nèi)部的createSession()創(chuàng)建MediaSessionRecord實例,然后調(diào)用getSessionBinder()返回ISession實例,即對應(yīng)SessionStub實例mSession;

1.5.總結(jié)

???????MediaSession構(gòu)造方法內(nèi)部的createSession()最終返回的是MediaSessionRecord的實例mSession,用一張圖總結(jié)一下執(zhí)行過程:


MediaSession創(chuàng)建流程.png

2.MediaSession創(chuàng)建完成回調(diào)

???????在MediaSession創(chuàng)建完成后,會回調(diào)給客戶端進(jìn)行實時監(jiān)聽,關(guān)于回調(diào)在上面已經(jīng)分析到,具體實現(xiàn)是在MediaSessionService的createSessionInternal()內(nèi)部創(chuàng)建完MediaSessionRecord實例后會執(zhí)行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;
            }
        }

???????跟隨調(diào)用關(guān)系:

    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.創(chuàng)建MediaSession.Token列表,遍歷執(zhí)行MediaSessionRecord的getSessionToken()方法來獲取對應(yīng)的MediaSession.Token;
???????3.遍歷mSessionsListeners執(zhí)行record.listener.onActiveSessionsChanged(tokens)回調(diào)給客戶端;
???????當(dāng)然了,客戶端必須先注冊,才能接收到回調(diào),先看一下客戶端注冊過程;

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);
            }
        }
    }

???????先創(chuàng)建內(nèi)部類SessionsChangedWrapper實例wrapper,然后將其內(nèi)部變量mStub作為參數(shù)傳遞給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進(jìn)行管理,用來后續(xù)通知回調(diào)(前面可以看到);
???????再看一下SessionsChangedWrapper的實現(xiàn):

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;
    }
}

???????跟隨調(diào)用關(guān)系,最終會調(diào)用到該類的onActiveSessionsChanged()方法,在該方法內(nèi)會遍歷tokens來獲取MediaSessionRecord對應(yīng)的MediaSession.Token,創(chuàng)建MediaController[注意一下:每次有ActivieSession變化,都會返回不同的MediaController列表],最終回調(diào)到客戶端的是與MediaSession相關(guān)聯(lián)的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構(gòu)造方法內(nèi)部,主要執(zhí)行了兩項工作:
???????1.通過getBinder()獲取ISessionController實例mSessionBinder,從名字可以看到是用來控制MediaSession的;
???????2.創(chuàng)建TransportControls()實例mTransportControls,客戶端用來執(zhí)行控制;

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來進(jìn)行命令控制,mSessionBinder是通過token.getBinder()來獲取,反過來看一下MediaSession.Token實現(xiàn):

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內(nèi)部看一下該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創(chuàng)建而來,所以當(dāng)執(zhí)行控制時,調(diào)用的是ControllerStub內(nèi)部的方法:

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內(nèi)部的方法又會調(diào)用到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會調(diào)用到mCb,即MediaSession內(nèi)部的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));
        }
   }
}

???????跟隨調(diào)用關(guān)系,最終會調(diào)用到mCallback的方法,前面已經(jīng)講到,mCallBack是音樂應(yīng)用在創(chuàng)建MediaSession時,傳入的本地實現(xiàn);

    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;

???????音樂應(yīng)用內(nèi)部對應(yīng)實現(xiàn)對應(yīng)的方法,那么控制就生效了。

3.5.總結(jié)

???????客戶端MediaController通過TransportControls來進(jìn)行控制,最終會對應(yīng)到服務(wù)端MediaSession的Callback,對應(yīng)關(guān)系如下:

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

4.MediaSession端控制

4.1.MediaSession.java

???????直接看代碼,播放暫停相關(guān)控制通過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);
        }
    }

???????媒體信息發(fā)生變化時,可以通過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()來返回的,接下來一起看一下執(zhí)行過程:

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.首先判斷當(dāng)前的playState及新的playState,然后來確定shouldUpdatePriority的值,該值表示當(dāng)前的MediaSession處于活躍狀態(tài)(比如:由暫停到播放);
???????2.更新mPlaybackState值為最新的playbackstate;
???????3.執(zhí)行mService.onSessionPlaybackStateChanged()來通知MediaSessionSession來根據(jù)shouldUpdatePriority來確定是否需要更新相關(guān)狀態(tài);

    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.執(zhí)行mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE)來通知客戶端進(jìn)行狀態(tài)更新;

    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);
                }
            }
        }
    }

???????可以看到,發(fā)生消息來通知回調(diào),當(dāng)然了,客戶端要想接收回調(diào),肯定需要先進(jìn)行注冊,通過MediaController來進(jìn)行注冊,一起看一下:

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);
            }
        }
    }

???????跟隨調(diào)用關(guān)系,可以看到:
???????1.首先將cb封裝到MessageHandler內(nèi)部,然后放入mCallbacks進(jìn)行管理;
???????2.執(zhí)行mSessionBinder的registerCallback()方法,mCbStub對應(yīng)的是CallbackStub實例,用來接收回調(diào):

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,看一下對應(yīng)實現(xiàn):

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進(jìn)行管理,根據(jù)前面講到的,有狀態(tài)變化時通知回調(diào)就是對mControllerCallbackHolders進(jìn)行holder.mCallback.onPlaybackStateChanged(mPlaybackState)遍歷通知,再返回到MediaController內(nèi)部的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.總結(jié)

???????服務(wù)端MediaSession進(jìn)行控制,最終會對應(yīng)到客戶端MediaController的Callback,對應(yīng)關(guān)系如下:

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

???????客戶端MediaController與服務(wù)端MediaSession雙向控制圖如下:


MediaController與MediaSession雙向控制.png

???????以上就是MediaSession工作的相關(guān)流程,詳細(xì)邏輯還需要通過源碼進(jìn)一步了解!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容