1、Qs创建
QSPanel 创建是从 StatusBar#makeStatusBarView 开始的,Qs面板创建这块,与之前版本对比,没啥变化。
protected void makeStatusBarView() { // 省略其他代码...... // 设置快速设置面板 // R.id.qs_frame 是一个 FrameLayout 布局,将 QSFragment 布局添加到其中。所以 R.id.qs_frame 最终显示的是 QSFragment 。 final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame); if (container != null) { FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame, mExtensionController .newExtension(QS.class) .withPlugin(QS.class) .withDefault(this::createDefaultQSFragment) .build()); // 亮度控制器 mBrightnessMirrorController = new BrightnessMirrorController( mNotificationShadeWindowView, mNotificationPanelViewController, mNotificationShadeDepthControllerLazy.get(), mBrightnessSliderFactory, (visible) -> { mBrightnessMirrorVisible = visible; updateScrimController(); }); fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { QS qs = (QS) f; if (qs instanceof QSFragment) { mQSPanelController = ((QSFragment) qs).getQSPanelController(); ((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController); } }); } // 省略其他代码...... }
接下来就先看看 QSFragment 的 onCreateView() 方法:
QSFragment#onCreateView()
// QSFragment.java @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { try { Trace.beginSection("QSFragment#onCreateView"); inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.Theme_SystemUI_QuickSettings)); // 在这里返回了布局,R.layout.qs_panel return inflater.inflate(R.layout.qs_panel, container, false); } finally { Trace.endSection(); } }
再看 QSFragment 的构造函数:
// QSFragment.java @Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, QSTileHost qsTileHost, StatusBarStateController statusBarStateController, CommandQueue commandQueue, @Named(QS_PANEL) MediaHost qsMediaHost, @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, KeyguardBypassController keyguardBypassController, QSFragmentComponent.Factory qsComponentFactory, QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, FalsingManager falsingManager, DumpManager dumpManager) { // 省略其他代码...... mHost = qsTileHost; // 省略其他代码...... }
这里注意 @Inject 注解,这个是 Android dagger里的一种注解。
在这里,与Android 9.0及其以下版本实例化 QSTileHost类的方式不一样,这里是通dagger来实例化的。所以这里实例化了 QSTileHost 。
下面我们就进入到 QSTileHost 的构造方法:
// QSTileHost.java @Inject public QSTileHost(Context context, StatusBarIconController iconController, QSFactory defaultFactory, @Main Handler mainHandler, @Background Looper bgLooper, PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, DumpManager dumpManager, BroadcastDispatcher broadcastDispatcher, Optional<CentralSurfaces> centralSurfacesOptional, QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, TileServiceRequestController.Builder tileServiceRequestControllerBuilder, TileLifecycleManager.Factory tileLifecycleManagerFactory ) { // 省略其他代码...... mainHandler.post(() -> { // This is technically a hack to avoid circular dependency of // QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation // 在创建任何图块之前完成。 tunerService.addTunable(this, TILES_SETTING); // AutoTileManager 可以修改 mTiles,因此请确保 mTiles 已经初始化。 mAutoTiles = autoTiles.get(); mTileServiceRequestController.init(); }); }
在 QSTileHost 的构造函数里,我们主要看 tunerService.addTunable(this, TILES_SETTING); 很明显,调用 tunerService 里的 addTunabe() 方法,跟进去会发现,最终的是调用的 TunerServiceImpl 里面的 addTunabe() 方法。
TunerServiceImpl#addTunable()
// TunerServiceImpl.java public void addTunable(Tunable tunable, String... keys) { for (String key : keys) { addTunable(tunable, key); } } private void addTunable(Tunable tunable, String key) { // 省略其他代码...... // 从数据库读取数据;刷机第一次数据库为空,这里也会空,后面程序会从配置文件读取; String value = DejankUtils.whitelistIpcs(() -> Settings.Secure .getStringForUser(mContentResolver, key, mCurrentUser)); tunable.onTuningChanged(key, value); }
tunable.onTuningChanged() 回调 QSTileHost#onTuningChanged():
// QSTileHost.java @Override public void onTuningChanged(String key, String newValue) { if (!TILES_SETTING.equals(key)) { return; } if (DEBUG) Log.d(TAG, "Recreating tiles"); if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) { newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode); } //调用 QSTileHost#loadTileSpecs,获得 config 里字符串信息 final List<String> tileSpecs = loadTileSpecs(mContext, newValue); int currentUser = ActivityManager.getCurrentUser(); if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return; //进行了过滤 mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach( tile -> { if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey()); tile.getValue().destroy(); }); 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)) { if (tile.isAvailable()) { if (DEBUG) Log.d(TAG, "Adding " + tile); tile.removeCallbacks(); if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) { tile.userSwitch(currentUser); } newTiles.put(tileSpec, tile); } else { tile.destroy(); } } else { if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec); try { //这里通过 字符串 一个个实例化 Tile tile = createTile(tileSpec); if (tile != null) { if (tile.isAvailable()) { tile.setTileSpec(tileSpec); // put 到 Map 中 newTiles.put(tileSpec, tile); } else { tile.destroy(); } } } catch (Throwable t) { Log.w(TAG, "Error creating tile for spec: " + tileSpec, t); } } } mCurrentUser = currentUser; mTileSpecs.clear(); mTileSpecs.addAll(tileSpecs); mTiles.clear(); // put 到 Map 中 mTiles.putAll(newTiles); for (int i = 0; i < mCallbacks.size(); i++) { //注册,当开发状态改变时回调 mCallbacks.get(i).onTilesChanged(); } }
这里有两个重要的方法:一个是获取 config 里字符串信息 loadTileSpecs(mContext, newValue);一个实例化 Tile 的 createTile(tileSpec)。
先看第一个 QSTileHost#loadTileSpecs() 这里和Android 10 有点出入。
// QSTileHost.java protected static List<String> loadTileSpecs(Context context, String tileList) { final Resources res = context.getResources(); // tileList 为空,则获取一个 “default” 字符串 if (TextUtils.isEmpty(tileList)) { tileList = res.getString(R.string.quick_settings_tiles); if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList); } else { if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList); } final ArrayList<String> tiles = new ArrayList<String>(); boolean addedDefault = false; Set<String> addedSpecs = new ArraySet<>(); for (String tile : tileList.split(",")) { tile = tile.trim(); if (tile.isEmpty()) continue; // 第一次 tileList 为空,取了默认值, if (tile.equals("default")) { if (!addedDefault) { // 从 config 文件获取 List<String> defaultSpecs = getDefaultSpecs(context); for (String spec : defaultSpecs) { if (!addedSpecs.contains(spec)) { tiles.add(spec); addedSpecs.add(spec); } } addedDefault = true; } } else { if (!addedSpecs.contains(tile)) { tiles.add(tile); addedSpecs.add(tile); } } } // 省略其他代码...... return tiles; }
上述代码中第一次 tileList 为空,调用了 getDefaultSpecs(context) 获取字符串,该方法比较简单,这里就不做分析了。
接着看第二个 QSTileHost#createTile(tileSpec) 方法:
public QSTile createTile(String tileSpec) { for (int i = 0; i < mQsFactories.size(); i++) { QSTile t = mQsFactories.get(i).createTile(tileSpec); if (t != null) { return t; } } // M: @ { if (mQuickSettingsExt != null && mQuickSettingsExt.doOperatorSupportTile(tileSpec)) { // WifiCalling return (QSTile) mQuickSettingsExt.createTile(this, tileSpec); } // @ } return null; }
这里调用 QSFactory#createTile(),而 QSFactory 接口又由 QSFactoryImpl 实现。所以这里直接看 QSFactoryImpl #createTile():
// QSFactoryImpl.java public QSTile createTile(String tileSpec) { QSTileImpl tile = createTileInternal(tileSpec); if (tile != null) { tile.handleStale(); // Tile was just created, must be stale. } return tile; } private QSTileImpl createTileInternal(String tileSpec) { // 省略其他代码...... // Stock tiles. switch (tileSpec) { case "wifi": return mWifiTileProvider.get(); case "internet": return mInternetTileProvider.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 "battery": return mBatterySaverTileProvider.get(); case "saver": return mDataSaverTileProvider.get(); case "night": return mNightDisplayTileProvider.get(); case "nfc": return mNfcTileProvider.get(); case "dark": return mUiModeNightTileProvider.get(); case "screenrecord": return mScreenRecordTileProvider.get(); // 省略其他代码...... } // 省略其他代码...... return null; }
看到这里通过对应的字符串分别实例化了对应的 Tile。
2、Qs显示
以上涉及资源文件加载及对应实例化,接下来看看如何显示出来的。和Android 11 对比出入有点大,加了一个控制器。
这里要回到 QSFragment#onViewCreated() 方法:
// QSFragment.java @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { QSFragmentComponent qsFragmentComponent = mQsComponentFactory.create(this); mQSPanelController = qsFragmentComponent.getQSPanelController(); mQuickQSPanelController = qsFragmentComponent.getQuickQSPanelController(); mQSFooterActionController = qsFragmentComponent.getQSFooterActionController(); // 一些初始化,init() 是 抽象类 ViewController 的 public 方法。 mQSPanelController.init(); mQuickQSPanelController.init(); mQSFooterActionController.init(); // 扩展的 qs 滚动视图 mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view); // 省略其他代码...... }
经过上述分析,我们来看看 ViewController#init():
public void init() { if (mInited) { return; } onInit(); // 要在 onViewAttached() 方法之前运行, mInited = true; if (isAttachedToWindow()) { // 调用内部 onViewAttachedToWindow() 方法,去添加视图。 mOnAttachStateListener.onViewAttachedToWindow(mView); } addOnAttachStateChangeListener(mOnAttachStateListener); } private OnAttachStateChangeListener mOnAttachStateListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { // 调用自己的抽象方法 onViewAttached() ,在子类具体实现,添加 Tiles. ViewController.this.onViewAttached(); } @Override public void onViewDetachedFromWindow(View v) { ViewController.this.onViewDetached(); } };
这个添加在 QSPanelController 的父类 QSPanelControllerBase 中的 onViewAttached() 方法中。
QSPanelControllerBase#onViewAttached()
// QSPanelControllerBase.java @Override protected void onViewAttached() { mQsTileRevealController = createTileRevealController(); if (mQsTileRevealController != null) { mQsTileRevealController.setExpansion(mRevealExpansion); } mMediaHost.addVisibilityChangeListener(mMediaHostVisibilityListener); mView.addOnConfigurationChangedListener(mOnConfigurationChangedListener); mHost.addCallback(mQSHostCallback); // 这里设置 Tiles setTiles(); mLastOrientation = getResources().getConfiguration().orientation; switchTileLayout(true); mDumpManager.registerDumpable(mView.getDumpableTag(), this); } /** */ public void setTiles() { // 这里 getTiles() 就是获取,前面我们说的 Tiles 实例,在 QSTileHost 中。 setTiles(mHost.getTiles(), false); } /** */ public void setTiles(Collection<QSTile> tiles, boolean collapsedView) { // TODO(b/168904199): move this logic into QSPanelController. if (!collapsedView && mQsTileRevealController != null) { mQsTileRevealController.updateRevealedTiles(tiles); } for (QSPanelControllerBase.TileRecord record : mRecords) { mView.removeTile(record); record.tile.removeCallback(record.callback); } mRecords.clear(); mCachedSpecs = ""; for (QSTile tile : tiles) { addTile(tile, collapsedView); } } private void addTile(final QSTile tile, boolean collapsedView) { // 这里会创建对应的视图。 final TileRecord r = new TileRecord(tile, mHost.createTileView(getContext(), tile, collapsedView)); // 注意:这个 mView 是 QSPanel,在 QSPanelController 的构造方法通过super传到 QSPanelControllerBase 的。这里也是视图的添加。 mView.addTile(r); mRecords.add(r); mCachedSpecs = getTilesSpecs(); }
这里只需关注QSPanel#addTile():
// QSPanel.java void addTile(QSPanelControllerBase.TileRecord tileRecord) { final QSTile.Callback callback = new QSTile.Callback() { @Override public void onStateChanged(QSTile.State state) { drawTile(tileRecord, state); } }; tileRecord.tile.addCallback(callback); tileRecord.callback = callback; tileRecord.tileView.init(tileRecord.tile); tileRecord.tile.refreshState(); if (mTileLayout != null) { mTileLayout.addTile(tileRecord); } }
由 TileLayout#addTile() 实现:
// TileLayout.java public void addTile(TileRecord tile) { mRecords.add(tile); tile.tile.setListening(this, mListening); addTileView(tile); } protected void addTileView(TileRecord tile) { // 注:TileLayout 继承的是 ViewGroup。 addView(tile.tileView); }
至此,SystemUI 下拉状态栏快捷开关模块代码流程分析完毕。
Qs一个有3种呈现方式,如图:
我这分析的是第 2 种。
其他的展示方法也类似。
qs_panel.xml 布局文件
<com.android.systemui.qs.QSContainerImpl xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/quick_settings_container" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" android:clipChildren="false"> <!-- 第二种布局 --> <com.android.systemui.qs.NonInterceptingScrollView android:id="@+id/expanded_qs_scroll_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="@dimen/qs_panel_elevation" android:importantForAccessibility="no" android:scrollbars="none" android:clipChildren="false" android:clipToPadding="false" android:layout_weight="1"> <com.android.systemui.qs.QSPanel android:id="@+id/quick_settings_panel" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/transparent" android:focusable="true" android:accessibilityTraversalBefore="@android:id/edit" android:clipToPadding="false" android:clipChildren="false"> <include layout="@layout/qs_footer_impl" /> </com.android.systemui.qs.QSPanel> </com.android.systemui.qs.NonInterceptingScrollView> <!-- 第一种布局 --> <include layout="@layout/quick_status_bar_expanded_header" /> <include layout="@layout/footer_actions" android:id="@+id/qs_footer_actions" android:layout_height="@dimen/footer_actions_height" android:layout_width="match_parent" android:layout_gravity="bottom" /> <!-- 第三种布局 --> <include android:id="@+id/qs_customize" layout="@layout/qs_customize_panel" android:visibility="gone" /> </com.android.systemui.qs.QSContainerImpl>