Android 12 CarMediaService源码解读

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源码解读的内容就那么多。

相关推荐
m0_548514773 小时前
2024.12.10——攻防世界Web_php_include
android·前端·php
凤邪摩羯3 小时前
Android-性能优化-03-启动优化-启动耗时
android
凤邪摩羯3 小时前
Android-性能优化-02-内存优化-LeakCanary原理解析
android
喀什酱豆腐4 小时前
Handle
android
m0_748232925 小时前
Android Https和WebView
android·网络协议·https
m0_748251725 小时前
Android webview 打开本地H5项目(Cocos游戏以及Unity游戏)
android·游戏·unity
m0_748254667 小时前
go官方日志库带色彩格式化
android·开发语言·golang
zhangphil7 小时前
Android使用PorterDuffXfermode模式PorterDuff.Mode.SRC_OUT橡皮擦实现“刮刮乐”效果,Kotlin(2)
android·kotlin
爱学测试的李木子8 小时前
从0到1搭建 Android 自动化 python+appium 环境
android·软件测试·python·测试工具·自动化
咸芝麻鱼8 小时前
Android Studio | 连接手机设备后,启动App时出现:Waiting For DebuggerApplication (App名)...
android·adb·智能手机·android studio