美文网首页Android进阶之路
Android音量调节(二)VolumeUI 更新

Android音量调节(二)VolumeUI 更新

作者: 孤街酒客0911 | 来源:发表于2022-07-28 16:57 被阅读0次

    学习笔记:Android音量调节(一)Volume音量键的处理流程

     VolumeUI 模块使用了 MVP 架构完成设计的,如下图: VolumeUI MVP的架构图.png
    1 VolumeUI 的启动

     由于VolumeUI 是继承 SystemUI 的,所以它的启动方式和 SystemUI 的启动方式一样,见SystemUI 启动流程

     直接看 VolumeUI 的start()方法

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
    
        @Override
        public void start() {
            boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
            boolean enableSafetyWarning =
                mContext.getResources().getBoolean(R.bool.enable_safety_warning);
            mEnabled = enableVolumeUi || enableSafetyWarning;
            if (!mEnabled) return;
    
            mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
            setDefaultVolumeController();
        }
        private void setDefaultVolumeController() {
            DndTile.setVisible(mContext, true);
            if (D.BUG) Log.d(TAG, "Registering default volume controller");
            mVolumeComponent.register();
         }
    

     VolumeUI 启动的时候做了一些初始化的操作、并且会创建一个 VolumeDialogComponent 对象,从名字可以看出,它代表 VolumeUI 组件,通过它可以创建整个MVP。

     VolumeDialogComponent 对象创建完成后,就会调用它的register()方法启动 VolumeUI 功能。它其实就是关联 Presenter 层和 Model 层。

    两件事情:
     1.VolumeDialogComponent里面会去创建我们的音量条UI的实例对象,也就是VolumeDialogImpl。
     2.setDefaultVolumeController方法会设置AudioService的回调接口。

    2 创建VolumeDialogImpl

     首先来看看 VolumeDialogComponent 的构造函数:

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
    
        @Inject
        public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
                VolumeDialogControllerImpl volumeDialogController) {
            mContext = context;
            mKeyguardViewMediator = keyguardViewMediator;
            mController = volumeDialogController;
            mController.setUserActivityListener(this);
            // Allow plugins to reference the VolumeDialogController.
            Dependency.get(PluginDependencyProvider.class)
                    .allowPluginDependency(VolumeDialogController.class);
            Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
                    .withPlugin(VolumeDialog.class)
                    .withDefault(this::createDefault)
                    .withCallback(dialog -> {
                        if (mDialog != null) {
                            mDialog.destroy();
                        }
                        mDialog = dialog;
                        mDialog.init(LayoutParams.TYPE_VOLUME_OVERLAY, mVolumeDialogCallback);
                    }).build();
            applyConfiguration();
            Dependency.get(TunerService.class).addTunable(this, VOLUME_DOWN_SILENT, VOLUME_UP_SILENT,
                    VOLUME_SILENT_DO_NOT_DISTURB);
        }
        protected VolumeDialog createDefault() {
            VolumeDialogImpl impl = new VolumeDialogImpl(mContext);
            impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
            impl.setAutomute(true);
            impl.setSilentMode(false);
            return impl;
        }
    
    

     VolumeDialogComponent 通过 createDefault() 创建 VolumeDialogImpl 对象,它代表 View 层,然后通过init() 进行了初始化。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
    
        public VolumeDialogImpl(Context context) {
            // VolumeDialogControllerImpl
            mController = Dependency.get(VolumeDialogController.class);
        }
        public void init(int windowType, Callback callback) {
            initDialog();
    
            mAccessibility.init();
    
            mController.addCallback(mControllerCallbackH, mHandler);
            mController.getState();
    
            Dependency.get(ConfigurationController.class).addCallback(this);
        }
        private final VolumeDialogController.Callbacks mControllerCallbackH
                = new VolumeDialogController.Callbacks() {
    
            @Override
            public void onStateChanged(State state) {
                onStateChangedH(state);
            }
            ...
        };
              
      private final class H extends Handler {
            ...
            private static final int STATE_CHANGED = 7;
    
            public H() {
                super(Looper.getMainLooper());
            }
    
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    ...
                    case STATE_CHANGED: onStateChangedH(mState); break;
                }
            }
        }
        ...
    
    

     在 VolumeDialogImpl (View层)的构造函数中,创建了 VolumeDialogControllerImpl 对象,它代表了 Presenter 层。

     在 init() 中,会向 VolumeDialogControllerImpl (Presenter层) 注册一个回调,也就是 View 层与 Presenter 层建立关联,从而可以通过 Presenter 层控制 View 层。

     现在 View 层已经和 Presenter 层关联了,那么 Model 层呢?还记得前面提到的启动 VolumeUI 功能的代码吗?它调用的是 VolumeDialogComponent#register(),它完成的就是 Model 层与 Presenter 的关联,具体调用的是 VolumeDialogControllerImpl#register()。

    这一段代码做了如下几件事情:
     1.初始化dialog,设置dialog的布局等等。
     2.添加VolumeDialogController的回调,当VolumeDialogController接收到AudioService的回调之后,通过Callback将事件继续通知给Dialog去做出响应的处理。这里的两个参数,一个是回调各个状态的接口,一个是在主线程初始化的Handler。


     通过init()方法里的 mController.addCallback(mControllerCallbackH, mHandler);进入到VolumeDialogControllerImpl,代码如下:

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
    @Singleton
    public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
        
        protected C mCallbacks = new C();
    
        ...
        // 添加回调监听
        public void addCallback(Callbacks callback, Handler handler) {
            mCallbacks.add(callback, handler);
            callback.onAccessibilityModeChanged(mShowA11yStream);
        }
      
        class C implements Callbacks {
            // Callbacks作为key,Handler为value
            private final HashMap<Callbacks, Handler> mCallbackMap = new HashMap<>();
    
            public void add(Callbacks callback, Handler handler) {
                if (callback == null || handler == null) throw new IllegalArgumentException();
                mCallbackMap.put(callback, handler);
            }
    
            @Override
            public void onStateChanged(final State state) {
                final long time = System.currentTimeMillis();
                final State copy = state.copy();
                for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
                    entry.getValue().post(new Runnable() {
                        @Override
                        public void run() {
                            entry.getKey().onStateChanged(copy);
                        }
                    });
                }
                Events.writeState(time, copy);
            }
        }
      
        ...
    }
    
    

     这里C是Callbacks的实现类,并且在内部有一个Map,用来存放对应的Callbacks以及Handler

     在VolumeDialogControllerImpl收到来自AudioService的方法之后,就会调用mCallbacks的方法,由于调用的地方是在工作线程,所以在这里通过Handler转化为了UI线程去调用,在对应的实现地方就可以直接改变UI了。

    Callbacks代码如下:

    // frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
    @ProvidesInterface(version = VolumeDialogController.VERSION)
    @DependsOn(target = StreamState.class)
    @DependsOn(target = State.class)
    @DependsOn(target = Callbacks.class)
    public interface VolumeDialogController {
    
        @ProvidesInterface(version = Callbacks.VERSION)
        public interface Callbacks {
            int VERSION = 1;
    
            void onShowRequested(int reason);
            void onDismissRequested(int reason);
            void onStateChanged(State state);
            void onLayoutDirectionChanged(int layoutDirection);
            void onConfigurationChanged();
            void onShowVibrateHint();
            void onShowSilentHint();
            void onScreenOff();
            void onShowSafetyWarning(int flags);
            void onAccessibilityModeChanged(Boolean showA11yStream);
            void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip);
        }
    }
    
    
    3 注册VolumeController

     接着来看setDefaultVolumeController,这个比较重要:

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
    @Singleton
    public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
            VolumeDialogControllerImpl.UserActivityListener{
        
        private final VolumeDialogControllerImpl mController;
        
        @Inject
        public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator,
                VolumeDialogControllerImpl volumeDialogController) {
            mController = volumeDialogController;
            ...
        }
              
        ...  
        @Override
        public void register() {
            mController.register();
        }
        ...
    }
    
    

     VolumeDialogComponent调用VolumeDialogControllerImpl的方法:

        // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
     
        protected final VC mVolumeController = new VC();
         
        public void register() {
            try {
                // 向Audio Manager注册了一个Binder,其实就是一个回调
                mAudio.setVolumeController(mVolumeController);
            } catch (SecurityException e) {
                Log.w(TAG, "Unable to set the volume controller", e);
                return;
            }
        }
    

     Audio Manager 就是 Model 层,VolumeDialogControllerImpl 向 Audio Manager 注册了一个回调,其实就是 Presenter 层与 Model 层的关联。

    4 音量UI显示

     现在MVP框架已经形成,现在就来分析下当按下 Power 键后,VolumeUI 是如何显示UI的。

     这里调用AudioManager的setVolumeController方法去设置了音量控制的回调接口:

    // frameworks/base/services/core/java/com/android/server/audio/AudioService.java
    public class AudioService extends IAudioService.Stub
            implements AccessibilityManager.TouchExplorationStateChangeListener,
                AccessibilityManager.AccessibilityServicesStateChangeListener {
    
        private final VolumeController mVolumeController = new VolumeController();
                  
        @Override
        public void setVolumeController(final IVolumeController controller) {
            ...
            mVolumeController.setController(controller);
        }
        
        public static class VolumeController {
            private IVolumeController mController;
    
            public void setController(IVolumeController controller) {
                mController = controller;
                mVisible = false;
            }
    
            // 音量发生改变就会调用这个方法
            public void postVolumeChanged(int streamType, int flags) {
                if (mController == null)
                    return;
                try {
                    mController.volumeChanged(streamType, flags);
                } catch (RemoteException e) {
                    Log.w(TAG, "Error calling volumeChanged", e);
                }
            }
            ...
        }
                  
    }
    

     在AudioService里面定义了一个内部类VolumeController,持有IVolumeController的引用,当音量发生改变就会调用VolumeController的方法,然后调用IVolumeController的方法,最终回调到SystemUI的VolumeDialogControllerImpl的VC类中。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
    @Singleton
    public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpable {
        ...
        private final class VC extends IVolumeController.Stub {
            @Override
            public void volumeChanged(int streamType, int flags) throws RemoteException {
                // 收到AudioService调用的方法
                mWorker.obtainMessage(W.VOLUME_CHANGED, streamType, flags).sendToTarget();
            }
        }
      
        private final class W extends Handler {
            private static final int VOLUME_CHANGED = 1;
    
            W(Looper looper) {
                super(looper);
            }
    
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case VOLUME_CHANGED: onVolumeChangedW(msg.arg1, msg.arg2); break;
                    ...
                }
            }
        }
      
        boolean onVolumeChangedW(int stream, int flags) {
            final boolean showUI = shouldShowUI(flags);
            final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
            final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
            final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
            boolean changed = false;
            if (showUI) {
                changed |= updateActiveStreamW(stream);
            }
            int lastAudibleStreamVolume = getAudioManagerStreamVolume(stream);
            changed |= updateStreamLevelW(stream, lastAudibleStreamVolume);
            changed |= checkRoutedToBluetoothW(showUI ? AudioManager.STREAM_MUSIC : stream);
            if (changed) {
                // 调用mCallbacks的onStateChanged方法
                mCallbacks.onStateChanged(mState);
            }
            ...
            return changed;
        }
        
        ...
    }
    

     这里的mWork是通过子线程的Looper去初始化的,所以onVolumeChangedW也是在子线程执行的,那么我们mCallbacks的方法也是在子线程执行的,这里的分析也是和上面的第2小点的分析对应上了。

     根据 flags 决定要执行哪个回调,如果要显示UI,就会回调 onShowRequested() , 而这个回调当然是由 View 层实现的。

        // frameworks/base/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
         
        private final VolumeDialogController.Callbacks mControllerCallbackH
                = new VolumeDialogController.Callbacks() {
            @Override
            public void onShowRequested(int reason) {
                showH(reason);
            }
        }
         
        private void showH(int reason) {
            // 显示Dialog
            mDialog.show();
        } 
    

     View 层就完成了一个 Dialog 的显示。

    5 VolumeUI小结

    这里我们来分析一下VolumeUI整理流程:

     1、VolumeUI持有VolumeDialogComponent的引用,在调用VolumeUI的start方法时,会判断音量条和安全音量提示是否打开,然后会去注册AudioService的监听。
     2、VolumeDialogComponent的构造函数会去创建音量条实例-VolumeDialogImpl,同时VolumeDialogImpl会去执行一些初始化的操作,同时添加VolumeDialogControllerImpl的监听回调。
     3、注册AudioService的监听是在VolumeDialogControllerImpl里面注册的,当AudioService进行了调整音量的操作后,VolumeDialogControllerImpl会收到通知,同时会将收到的消息回调给VolumeDialogImpl,做出相应的UI调整,这样就完成了一轮操作。

    相关文章

      网友评论

        本文标题:Android音量调节(二)VolumeUI 更新

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