Android 12 CarMediaService源码解读
路径为
android12/packages/services/Car/service/src/com/android/car/CarMediaService
前言
CarMediaService是管理汽车应用程序的当前活动媒体源的服务,继承于☞CarServiceBase;在车里同时有且只有一个活跃源允许浏览和播放,值得注意的是,正在播放或浏览的媒体源可能不遵循MediaSession框架,但它依然被视为拥有活跃源。
初始化initUser()
首先一进来先看init()方法,注释写的很清楚了创建CarMediaService后,从ICarImpl调用了此方法。然后判断要不要initUser(int userId)我们来看下这个方法
kotlin
private void initUser(int userId) {
// SharedPreferences are shared among different users thus only need initialized once. And
// they should be initialized after user 0 is unlocked because SharedPreferences in
// credential encrypted storage are not available until after user 0 is unlocked.
// initUser() is called when the current foreground user is unlocked, and by that time user
// 0 has been unlocked already, so initializing SharedPreferences in initUser() is fine.
synchronized (mLock) {
//sp为null初始化一下
if (mSharedPrefs == null) {
mSharedPrefs = mContext.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
}
//之前已经注册广播接收器过的话先解绑再重新注册
if (mIsPackageUpdateReceiverRegistered) {
mContext.unregisterReceiver(mPackageUpdateReceiver);
}
UserHandle currentUser = new UserHandle(userId);
mContext.registerReceiverAsUser(mPackageUpdateReceiver, currentUser,
mPackageUpdateFilter, null, null);
//字面意思的标志位
mIsPackageUpdateReceiverRegistered = true;
//关键的媒体组件MEDIA_SOURCE_MODE_PLAYBACK表示上一次播放的;MEDIA_SOURCE_MODE_BROWSE表示上一次浏览的
mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] = isCurrentUserEphemeral()
? getDefaultMediaSource() : getLastMediaSource(MEDIA_SOURCE_MODE_PLAYBACK);
mPrimaryMediaComponents[MEDIA_SOURCE_MODE_BROWSE] = isCurrentUserEphemeral()
? getDefaultMediaSource() : getLastMediaSource(MEDIA_SOURCE_MODE_BROWSE);
mActiveUserMediaController = null;
updateMediaSessionCallbackForCurrentUser();
//通过CarMediaManager的回调通知播放源的变化
notifyListeners(MEDIA_SOURCE_MODE_PLAYBACK);
notifyListeners(MEDIA_SOURCE_MODE_BROWSE);
//启动服务
startMediaConnectorService(shouldStartPlayback(mPlayOnBootConfig), currentUser);
}
}
顺着来看下getDefaultMediaSource()和getLastMediaSource()
getDefaultMediaSource()和getLastMediaSource()
getDefaultMediaSource
kotlin
private ComponentName getDefaultMediaSource() {
//默认的活动源,一般来说是蓝牙
String defaultMediaSource = mContext.getString(R.string.config_defaultMediaSource);
ComponentName defaultComponent = ComponentName.unflattenFromString(defaultMediaSource);
//如果包名没有一个MediaBrowseService服务,则返回null
if (isMediaService(defaultComponent)) {
return defaultComponent;
}
return null;
}
getLastMediaSource
kotlin
private @NonNull ComponentName getLastMediaSource(int mode) {
//判断sp初始化没有,如果初始化了去sp中取当前user的上一次的活跃源
if (sharedPrefsInitialized()) {
String key = getMediaSourceKey(mode);
String serialized = mSharedPrefs.getString(key, "");
//取出的值是一个string数组,如果是null则走getDefaultMediaSource()
if (!TextUtils.isEmpty(serialized)) {
for (String name : getComponentNameList(serialized)) {
//不为空,取包名返回。也必须带有一个MediaBrowseService服务
ComponentName componentName = ComponentName.unflattenFromString(name);
if (isMediaService(componentName)) {
return componentName;
}
}
}
}
return getDefaultMediaSource();
}
顺着再看一下updateMediaSessionCallbackForCurrentUser()
updateMediaSessionCallbackForCurrentUser()
kotlin
private void updateMediaSessionCallbackForCurrentUser() {
if (mSessionsListener != null) {
mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsListener);
}
mSessionsListener = new SessionChangedListener(ActivityManager.getCurrentUser());
UserHandle currentUserHandle = new UserHandle(ActivityManager.getCurrentUser());
mMediaSessionManager.addOnActiveSessionsChangedListener(null, currentUserHandle,
new HandlerExecutor(mHandler), mSessionsListener);
mMediaSessionUpdater.registerCallbacks(mMediaSessionManager.getActiveSessionsForUser(null,
currentUserHandle));
}
主要看addOnActiveSessionsChangedListener方法,更新了一下我们的mSessionsListener,通知我们活跃的MediaSession现在是哪个,然后就是通知回调更新,启动服务,至此CarMediaService的初始化就完成了。
设置媒体源 setMediaSource
kotlin
@Override
public void setMediaSource(@NonNull ComponentName componentName,
@MediaSourceMode int mode) {
//检查媒体源控制权限
ICarImpl.assertPermission(mContext, android.Manifest.permission.MEDIA_CONTENT_CONTROL);
if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) {
Slog.d(CarLog.TAG_MEDIA, "Changing media source to: " + componentName.getPackageName());
}
setPrimaryMediaSource(componentName, mode);
}
}
setPrimaryMediaSource
kotlin
/**
* Updates the primary media source, then notifies content observers of the change
* Will update both the playback and browse sources if independent playback is not supported
*/
private void setPrimaryMediaSource(@NonNull ComponentName componentName,
@CarMediaManager.MediaSourceMode int mode) {
synchronized (mLock) {
//如果已经是此包名的mode则直接返回
if (mPrimaryMediaComponents[mode] != null
&& mPrimaryMediaComponents[mode].equals((componentName))) {
return;
}
}
如果不是独立播放的的配置,则将媒体播放源与媒体浏览源都更新,否则根据mode更新独立播放的的配置
if (!mIndependentPlaybackConfig) {
setPlaybackMediaSource(componentName);
setBrowseMediaSource(componentName);
} else if (mode == MEDIA_SOURCE_MODE_PLAYBACK) {
setPlaybackMediaSource(componentName);
} else if (mode == MEDIA_SOURCE_MODE_BROWSE) {
setBrowseMediaSource(componentName);
}
}
}
接着看下setPlaybackMediaSource,setBrowseMediaSource也是一样的逻辑,就不赘述了
setPlaybackMediaSource
kotlin
private void setPlaybackMediaSource(ComponentName playbackMediaSource) {
//尝试注销mMediaControllerCallback回调,并通过TransportControls停止
stopAndUnregisterCallback();
//将此包名保存为上一个播放的音源包名
synchronized (mLock) {
mActiveUserMediaController = null;
mPrimaryMediaComponents[MEDIA_SOURCE_MODE_PLAYBACK] = playbackMediaSource;
}
if (playbackMediaSource != null
&& !TextUtils.isEmpty(playbackMediaSource.flattenToString())) {
if (!isCurrentUserEphemeral()) {
//保存上一个播放的音源
saveLastMediaSource(playbackMediaSource, MEDIA_SOURCE_MODE_PLAYBACK);
}
if (playbackMediaSource
.equals(mRemovedMediaSourceComponents[MEDIA_SOURCE_MODE_PLAYBACK])) {
mRemovedMediaSourceComponents[MEDIA_SOURCE_MODE_PLAYBACK] = null;
}
}
notifyListeners(MEDIA_SOURCE_MODE_PLAYBACK);
startMediaConnectorService(shouldStartPlayback(mPlayOnMediaSourceChangedConfig),
new UserHandle(ActivityManager.getCurrentUser()));
// 当应用程序出错,重置新源的当前播放状态
//(例如未登录)。此状态将从注册的应用程序回调中更新
//以确保mCurrentPlaybackState仅反映当前源。
synchronized (mLock) {
mCurrentPlaybackState = PlaybackState.STATE_NONE;
updateActiveMediaControllerLocked(mMediaSessionManager
.getActiveSessionsForUser(null,
new UserHandle(ActivityManager.getCurrentUser())));
}
}
接着看下saveLastMediaSource
saveLastMediaSource
kotlin
private void saveLastMediaSource(@NonNull ComponentName component, int mode) {
if (!sharedPrefsInitialized()) {
return;
}
//获取包名和key
String componentName = component.flattenToString();
String key = getMediaSourceKey(mode);
String serialized = mSharedPrefs.getString(key, null);
//如果没有就添加包名,如果有就将包名放到第一个
if (serialized == null) {
mSharedPrefs.edit().putString(key, componentName).apply();
} else {
Deque<String> componentNames = new ArrayDeque<>(getComponentNameList(serialized));
componentNames.remove(componentName);
componentNames.addFirst(componentName);
mSharedPrefs.edit().putString(key, serializeComponentNameList(componentNames)).apply();
}
}
getMediaService
最后重点说下getMediaService方法
kotlin
/*
* 获取与当前用户的包名匹配的MediaBrowseService
*/
private ComponentName getMediaService(@NonNull ComponentName componentName) {
//获取包名和类名
String packageName = componentName.getPackageName();
String className = componentName.getClassName();
PackageManager packageManager = mContext.getPackageManager();
Intent mediaIntent = new Intent();
mediaIntent.setPackage(packageName);
mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE);
//通过packageManager查询此包名的所有服务
List<ResolveInfo> mediaServices = packageManager.queryIntentServicesAsUser(mediaIntent,
PackageManager.GET_RESOLVED_FILTER, ActivityManager.getCurrentUser());
//找到则正常返回,找不到则返回null,不继续执行,输出No MediaBrowseService with ComponentName:日志
for (ResolveInfo service : mediaServices) {
String serviceName = service.serviceInfo.name;
if (!TextUtils.isEmpty(serviceName)
// If className is not specified, returns the first service in the package;
// otherwise returns the matched service.
// TODO(b/136274456): find a proper way to handle the case where there are
// multiple services and the className is not specified.
&& (TextUtils.isEmpty(className) || serviceName.equals(className))) {
return new ComponentName(packageName, serviceName);
}
}
if (Log.isLoggable(CarLog.TAG_MEDIA, Log.DEBUG)) {
Slog.d(CarLog.TAG_MEDIA, "No MediaBrowseService with ComponentName: "
+ componentName.flattenToString());
}
return null;
}
总结
最后提一嘴CarMediaService是CarMediaManager的实现,我们APP正常通过CarMediaManager调用到CarMediaService的,关于CarMediaService源码解读的内容就那么多。