Quick Settings行內人簡稱qs,是SystemUI必不可少的一部分。它主要負責打開關閉各個系統的功能模塊,如wifi、藍牙、手電筒等。android
從圖中能夠看出,每個系統功能都對應這一個按鈕,而這個按鈕的學名就是Tile。數據庫
SystemUI是如何加載每一個Tile的呢?在Statusbar初始化的時候,在makeStatusbarBarView()中對QsTile進行了初始化:app
protected void makeStatusBarView() { ...... // Set up the quick settings tile panel View container = mStatusBarWindow.findViewById(R.id.qs_frame); if (container != null) { FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame, Dependency.get(ExtensionController.class) .newExtension(QS.class) .withPlugin(QS.class) .withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new) .withDefault(QSFragment::new) .build()); final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, mIconController); mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow, (visible) -> { mBrightnessMirrorVisible = visible; updateScrimController(); }); fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> { QS qs = (QS) f; if (qs instanceof QSFragment) { ((QSFragment) qs).setHost(qsh); mQSPanel = ((QSFragment) qs).getQsPanel(); mQSPanel.setBrightnessMirror(mBrightnessMirrorController); mKeyguardStatusBar.setQSPanel(mQSPanel); } }); } ...... }
經過初始化QsTileHost,進行QsTile的加載。在QsTileHost初始化的時候,增長Tunable監聽:ide
public QSTileHost(Context context, StatusBar statusBar, StatusBarIconController iconController) { mIconController = iconController; mContext = context; mStatusBar = statusBar; mServices = new TileServices(this, Dependency.get(Dependency.BG_LOOPER)); mQsFactories.add(new QSFactoryImpl(this)); Dependency.get(PluginManager.class).addPluginListener(this, QSFactory.class, true); Dependency.get(TunerService.class).addTunable(this, TILES_SETTING); // AutoTileManager can modify mTiles so make sure mTiles has already been initialized. mAutoTiles = new AutoTileManager(context, this); }
這裏的TunerSerivce的做用是註冊對字段"sysui_qs_tiles"的系統數據庫監聽:post
private void addTunable(Tunable tunable, String key) { if (!mTunableLookup.containsKey(key)) { mTunableLookup.put(key, new ArraySet<Tunable>()); } mTunableLookup.get(key).add(tunable); if (LeakDetector.ENABLED) { mTunables.add(tunable); Dependency.get(LeakDetector.class).trackCollection(mTunables, "TunerService.mTunables"); } Uri uri = Settings.Secure.getUriFor(key); if (!mListeningUris.containsKey(uri)) { mListeningUris.put(uri, key); mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser); } // Send the first state. String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser); tunable.onTuningChanged(key, value); }
經過addTunable觸發QsTileHost中onTuningChanged:fetch
@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); } 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 = createTile(tileSpec); if (tile != null) { if (tile.isAvailable()) { tile.setTileSpec(tileSpec); 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(); mTiles.putAll(newTiles); for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onTilesChanged(); } }
以上步驟進行了讀取配置,並creatTile的操做。經過loadTileSpecs()獲取到須要createTile的list:ui
protected List<String> loadTileSpecs(Context context, String tileList) { final Resources res = context.getResources(); String defaultTileList = res.getString(R.string.quick_settings_tiles_default); if (tileList == null) { 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; for (String tile : tileList.split(",")) { tile = tile.trim(); if (tile.isEmpty()) continue; if (tile.equals("default")) { if (!addedDefault) { tiles.addAll(Arrays.asList(defaultTileList.split(","))); addedDefault = true; } } else { tiles.add(tile); } } return tiles; }
經過"quick_settings_tiles_default"進入默認tile的配置,這裏的tile list不包括可編輯的。獲取到list以後,經過creatTile()進行建立:this
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; } } return null; }
這裏經過循環,在QsFactoryImpl中進行tile的建立:google
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) { switch (tileSpec) { case "wifi": return new WifiTile(mHost); case "bt": return new BluetoothTile(mHost); case "cell": return new CellularTile(mHost); case "dnd": return new DndTile(mHost); case "inversion": return new ColorInversionTile(mHost); case "airplane": return new AirplaneModeTile(mHost); case "work": return new WorkModeTile(mHost); case "rotation": return new RotationLockTile(mHost); case "flashlight": return new FlashlightTile(mHost); case "location": return new LocationTile(mHost); case "cast": return new CastTile(mHost); case "hotspot": return new HotspotTile(mHost); case "user": return new UserTile(mHost); case "battery": return new BatterySaverTile(mHost); case "saver": return new DataSaverTile(mHost); case "night": return new NightDisplayTile(mHost); case "nfc": return new NfcTile(mHost); } // Intent tiles. if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(mHost, tileSpec); if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(mHost, tileSpec); // Debug tiles. if (Build.IS_DEBUGGABLE) { if (tileSpec.equals(GarbageMonitor.MemoryTile.TILE_SPEC)) { return new GarbageMonitor.MemoryTile(mHost); } } // Broken tiles. Log.w(TAG, "Bad tile spec: " + tileSpec); return null; }
根據tileSpec進行對應tile的初始化。spa
當用戶點擊qs區域的編輯按鈕時,能夠看到一個這樣的界面:
從圖中能夠看出,進入qs編輯模式以後,會出現更多tile,而且能夠拖動到上方供用戶平時下拉後直接使用,或者將不經常使用的放到待選區域。這裏從UI上能夠看出待選區域由兩部分組成,一部分是SystemUI內部的Tile,而相似google Nearby這種Tile則是第三應用本身註冊的Tile。接下來,就分析一下這些Tile是如何加載的?
當進入編輯模式以後,QsCustomer會對tile有一個query操做,由TileQueryHelper實現:
public void queryTiles(QSTileHost host) { mTiles.clear(); mSpecs.clear(); mFinished = false; // Enqueue jobs to fetch every system tile and then ever package tile. addStockTiles(host); addPackageTiles(host); }
從代碼中也能夠看出,加載Tile分紅了StockTiles和PackageTiles,分別表明SystemUI內置Tile和第三註冊Tile,先來看下內置Tile:
private void addStockTiles(QSTileHost host) { String possible = mContext.getString(R.string.quick_settings_tiles_stock); final ArrayList<String> possibleTiles = new ArrayList<>(); possibleTiles.addAll(Arrays.asList(possible.split(","))); if (Build.IS_DEBUGGABLE) { possibleTiles.add(GarbageMonitor.MemoryTile.TILE_SPEC); } final ArrayList<QSTile> tilesToAdd = new ArrayList<>(); for (String spec : possibleTiles) { final QSTile tile = host.createTile(spec); if (tile == null) { continue; } else if (!tile.isAvailable()) { tile.destroy(); continue; } tile.setListening(this, true); tile.clearState(); tile.refreshState(); tile.setListening(this, false); tile.setTileSpec(spec); tilesToAdd.add(tile); } mBgHandler.post(() -> { for (QSTile tile : tilesToAdd) { final QSTile.State state = tile.getState().copy(); // Ignore the current state and get the generic label instead. state.label = tile.getTileLabel(); tile.destroy(); addTile(tile.getTileSpec(), null, state, true); } notifyTilesChanged(false); }); }
"quick_settings_tiles_stock"對應的就是SystemUI中可加載的全部Tile,若是但願客製化也能夠在此處進行修改。接下來,再看一下如何加載第三方Tile(如Nearby):
private void addPackageTiles(final QSTileHost host) { mBgHandler.post(() -> { Collection<QSTile> params = host.getTiles(); PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> services = pm.queryIntentServicesAsUser( new Intent(TileService.ACTION_QS_TILE), 0, ActivityManager.getCurrentUser()); String stockTiles = mContext.getString(R.string.quick_settings_tiles_stock); for (ResolveInfo info : services) { String packageName = info.serviceInfo.packageName; ComponentName componentName = new ComponentName(packageName, info.serviceInfo.name); // Don't include apps that are a part of the default tile set. if (stockTiles.contains(componentName.flattenToString())) { continue; } final CharSequence appLabel = info.serviceInfo.applicationInfo.loadLabel(pm); String spec = CustomTile.toSpec(componentName); State state = getState(params, spec); if (state != null) { addTile(spec, appLabel, state, false); continue; } if (info.serviceInfo.icon == 0 && info.serviceInfo.applicationInfo.icon == 0) { continue; } Drawable icon = info.serviceInfo.loadIcon(pm); if (!permission.BIND_QUICK_SETTINGS_TILE.equals(info.serviceInfo.permission)) { continue; } if (icon == null) { continue; } icon.mutate(); icon.setTint(mContext.getColor(android.R.color.white)); CharSequence label = info.serviceInfo.loadLabel(pm); addTile(spec, icon, label != null ? label.toString() : "null", appLabel); } notifyTilesChanged(true); }); }
從代碼中不難看出,經過PackageManager來query帶有「TileService.ACTION_QS_TILE」的Intent的應用,獲取到它們的resolveinfo獲取第三方註冊的Tile的信息,如Tile的名稱、icon、應用名稱等。
到這裏,Qs Tile加載的數據流程已經講完,後面有時間再講一下UI的加載流程。若有什麼問題歡迎指正。