Android 13 Qs面板的加载流程

简介: Android 13 Qs面板的加载流程

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种呈现方式,如图:

image.png

我这分析的是第 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>


相关文章
|
25天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
70 6
|
22天前
|
Android开发 UED
Android 中加载 Gif 动画
【10月更文挑战第20天】加载 Gif 动画是 Android 开发中的一项重要技能。通过使用第三方库或自定义实现,可以方便地在应用中展示生动的 Gif 动画。在实际应用中,需要根据具体情况进行合理选择和优化,以确保用户体验和性能的平衡。可以通过不断的实践和探索,进一步掌握在 Android 中加载 Gif 动画的技巧和方法,为开发高质量的 Android 应用提供支持。
|
23天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
|
26天前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
Android面试高频知识点(4) 详解Activity的启动流程
25 3
|
27天前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
Android面试高频知识点(3) 详解Android View的绘制流程
24 2
|
1月前
|
XML 前端开发 Android开发
Android View的绘制流程和原理详细解说
Android View的绘制流程和原理详细解说
35 3
|
26天前
|
Android开发
Android面试之Activity启动流程简述
Android面试之Activity启动流程简述
17 0
|
2月前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
讲解Activity的启动流程了,Activity的启动流程相对复杂一下,涉及到了Activity中的生命周期方法,涉及到了Android体系的CS模式,涉及到了Android中进程通讯Binder机制等等, 首先介绍一下Activity,这里引用一下Android guide中对Activity的介绍:
49 4
|
2月前
|
Android开发 开发者
Android面试之Activity启动流程简述
每个Android开发者都熟悉的Activity,但你是否了解它的启动流程呢?本文将带你深入了解。启动流程涉及四个关键角色:Launcher进程、SystemServer的AMS、应用程序的ActivityThread及Zygote进程。核心在于AMS与ActivityThread间的通信。文章详细解析了从Launcher启动Activity的过程,包括通过AIDL获取AMS、Zygote进程启动以及ActivityThread与AMS的通信机制。接着介绍了如何创建Application及Activity的具体步骤。整体流程清晰明了,帮助你更深入理解Activity的工作原理。
51 0
|
3月前
|
Android开发
我的Android进阶修炼:安卓启动流程之init(1)
本文深入分析了Android系统中的init进程,包括其源码结构、主要功能以及启动流程的详细注解,旨在帮助读者理解init作为用户空间的1号进程在Android启动过程中的关键作用。
63 1