本文是基于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) {
// ...
// 添加 
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的具体功能打下基础。