SystemUI之Quick Settings创建

2,962 阅读5分钟

本文是基于Android 10源码分析的。

前面三篇文章基本上都是介绍状态栏,当然其中也穿插了通知栏视图的创建过程。从这篇文章开始,来分析SystemUI另一个模块Quick Settings,首先还是来看下它的创建过程。

Quick Settings模块大量使用了Dagger2这个依赖注入框架,但是本文并不会去分析一个类的对象来自哪里,如果你有疑惑,那么你需要先去学习这个框架,可以通过Dagger2指导学习。

另外,如果你想深入学习SystemUI如何使用Dagger2的,可以从frameworks/base/packages/SystemUI/docs/dagger.md这个文档进行学习。

QSTileHost

首先介绍一个类QSTileHost,从名字你应该就知道这个类的用途。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
    
    @Inject
    public QSTileHost(Context context,
            StatusBarIconController iconController,
            QSFactoryImpl defaultFactory,
            @Named(Dependency.MAIN_HANDLER_NAME) Handler mainHandler,
            @Named(Dependency.BG_LOOPER_NAME) Looper bgLooper,
            PluginManager pluginManager,
            TunerService tunerService,
            Provider<AutoTileManager> autoTiles,
            DumpController dumpController) {
        // ... 省略一大堆的变量初始化

        // 主线程中做一次操作
        mainHandler.post(() -> {
            // 实现类为TileServiceImpl
            // 调用这个方法后,会查询一次数据库,然后做一次回调 onTuningChanged()
            tunerService.addTunable(this, TILES_SETTING);
            
            // ...
        });
    }

在创建QSTileHost的时候,会在主线程做一次更新操作,是为了获取Quick Settings中各种Tile的名字的集合,例如wifi,bt,cell,flashlight,rotation,battery,dnd,airplane,cast

    // frameworks/base/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
    
    @Override
    public void addTunable(Tunable tunable, String... keys) {
        for (String key : keys) {
            addTunable(tunable, key);
        }
    }
    
    private void addTunable(Tunable tunable, String key) {
        // ...

        // 监听数据库
        Uri uri = Settings.Secure.getUriFor(key);
        if (!mListeningUris.containsKey(uri)) {
            mListeningUris.put(uri, key);
            mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
        }
        
        // 发送一次数据库查询的值
        String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
        tunable.onTuningChanged(key, value);
    }    

通过查询数据库,获取到各种Tile的名字的集合,名字之间以逗号分隔,然后通过回调返回给调用者。

    // frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
    
    @Override
    public void onTuningChanged(String key, String newValue) {
        if (!TILES_SETTING.equals(key)) {
            return;
        }

        // 把以逗号分隔的Tile名字集合解析成List<String>
        final List<String> tileSpecs = loadTileSpecs(mContext, newValue);

        // ...

        // 创建新tile集合
        final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
        for (String tileSpec : tileSpecs) {
            QSTile tile = mTiles.get(tileSpec);
            if (tile != null && (!(tile instanceof CustomTile)
                    || ((CustomTile) tile).getUser() == currentUser)) {
                // ...
            } else {
                try {
                    // 创建tile
                    tile = createTile(tileSpec);
                    if (tile != null) {
                        if (tile.isAvailable()) {
                            tile.setTileSpec(tileSpec);
                            // 保存
                            newTiles.put(tileSpec, tile);
                        } else {
                            tile.destroy();
                        }
                    }
                } 
            }
        }

        // 保存Tile名字集合
        List<String> currentSpecs = new ArrayList(mTileSpecs);
        mTileSpecs.clear();
        mTileSpecs.addAll(tileSpecs);

        // 保存Tile对象集合
        mTiles.clear();
        mTiles.putAll(newTiles);


        if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
            // ...
        } else {
            for (int i = 0; i < mCallbacks.size(); i++) {
                // 回调通知Tile改变, QSPanel是其中一个实现
                mCallbacks.get(i).onTilesChanged();
            }
        }
    }

根据返回的数据库的时候,先解析为List<String>,并最终保存到mTileSpecs中,然后根据Tile名字创建QSTile,并保存到mTiles中,然后回调通知那些监听者。现在分析的是QSTileHost构造函数,不用管有哪些监听者。

QS视图创建

QS的创建与Status Bar类似,这里我就不贴StatusBar#makeStatusBarView()代码,它就是由QSFragment创建的

    // frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
    
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
            Bundle savedInstanceState) {
        inflater = mInjectionInflater.injectable(
                inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)));
        return inflater.inflate(R.layout.qs_panel, container, false);
    }

这个就是整个QS的布局,可以大致看下布局信息

<!--根布局是一个FrameLayout-->
<com.android.systemui.qs.QSContainerImpl
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/quick_settings_container" >

    <!-- Main QS background -->
    <!--主要QS的背景,不包括顶部的状态栏-->
    <View
        android:id="@+id/quick_settings_background" />

    <!-- Black part behind the status bar -->
    <!--QS顶部状态栏(显示时间)背景-->
    <View
        android:id="@+id/quick_settings_status_bar_background" />

    <!-- Gradient view behind QS -->
    <!--折叠QS后面的一个视图-->
    <View
        android:id="@+id/quick_settings_gradient_view" />


    <com.android.systemui.qs.QSPanel
        android:id="@+id/quick_settings_panel"
    />

    <!--这是一个展开的顶部状态栏-->
    <include layout="@layout/quick_status_bar_expanded_header" />

    <!--这个是QS的底部视图,折叠情况下显示一个横杠,展开情况下显示修改和设置按钮-->
    <include layout="@layout/qs_footer_impl" />

    <!--详情界面-->
    <include android:id="@+id/qs_detail" layout="@layout/qs_detail" />

    <!--这个是点击修改按钮后的界面-->
    <include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel"
        android:visibility="gone" />

</com.android.systemui.qs.QSContainerImpl>

为了简洁,这里省略了布局的一些属性。

QS视图创建完成后,在onViewCreate()中,会初始化各种控制,然后最重要的是为各个组件设置QSTileHost

    // frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
    
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        
        // ... 省略组件初始化代码
        
        // 为各个组件设置QSTileHost
        setHost(mHost);
    }
    
    public void setHost(QSTileHost qsh) {
        // mQSPanel就是各个Tile的父容器
        mQSPanel.setHost(qsh, mQSCustomizer);
        
        // ... 省略其它组件设置QSTileHost代码
    }    

为各个组件设置QSTileHost的过程,只分析各个Tile如何添加到布局容器中

    // frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
    
    public void setHost(QSTileHost host, QSCustomizer customizer) {
        mHost = host;
        // 向QSTileHost注册回调,当数据库值改变时,接收回调
        mHost.addCallback(this);
        // 根据QSTileHost获取的QSTile集合,来向布局添加各种Tile View
        setTiles(mHost.getTiles());

        // ...
    }
    
    public void setTiles(Collection<QSTile> tiles, boolean collapsedView) {
        // ...
        
        // 添加&emsp;
        for (QSTile tile : tiles) {
            addTile(tile, collapsedView);
        }
    }    

addTile()实现了向布局添加Tile View

    // frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
    
    protected TileRecord addTile(final QSTile tile, boolean collapsedView) {
        final TileRecord r = new TileRecord();
        r.tile = tile;
        // 创建Tile View
        r.tileView = createTileView(tile, collapsedView);

        // ...

        if (mTileLayout != null) {
            // 添加加父容器中
            mTileLayout.addTile(r);
        }

        return r;
    }

首先需要创建Tile View,这是由createTileView()实现的,创建之后,就添加到mTileLayout中。这个mTileLayout才是Tile View直接父容器。

添加View操作调用的是Android API,而这个Tile View创建过程是由QSFactoryImpl#createTileInternal()实现

    // frameworks/base/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
    
    private QSTileImpl createTileInternal(String tileSpec) {
        // 通过名字创建
        switch (tileSpec) {
            case "wifi":
                return mWifiTileProvider.get();
            case "bt":
                return mBluetoothTileProvider.get();
            case "cell":
                return mCellularTileProvider.get();
            case "dnd":
                return mDndTileProvider.get();
            case "inversion":
                return mColorInversionTileProvider.get();
            case "airplane":
                return mAirplaneModeTileProvider.get();
            case "work":
                return mWorkModeTileProvider.get();
            case "rotation":
                return mRotationLockTileProvider.get();
            case "flashlight":
                return mFlashlightTileProvider.get();
            case "location":
                return mLocationTileProvider.get();
            case "cast":
                return mCastTileProvider.get();
            case "hotspot":
                return mHotspotTileProvider.get();
            case "user":
                return mUserTileProvider.get();
            case "battery":
                return mBatterySaverTileProvider.get();
            case "saver":
                return mDataSaverTileProvider.get();
            case "night":
                return mNightDisplayTileProvider.get();
            case "nfc":
                return mNfcTileProvider.get();
            case "dark":
                return mUiModeNightTileProvider.get();
        }

        // 这是通过Intent创建Tile
        if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec);
        if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec);

        return null;
    }

结束

Quick Settings的创建过程比较简单,通过本文,你应该可以分析出如何创建自定义的Tile,这为以后分析Quick Settings的具体功能打下基础。