首页 > 其他分享 >Android13下拉状态栏QS面板的加载流程解析

Android13下拉状态栏QS面板的加载流程解析

时间:2024-12-24 10:59:39浏览次数:5  
标签:case QS return 状态栏 get Android13 ...... qs tile

1、QS创建

QSPanel 创建是从 CentralSurfacesImpl#makeStatusBarView 开始的,Qs面板创建这块,与之前版本对比,没啥变化。
com.android.systemui.statusbar.phone.CentralSurfacesImpl.java

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() 方法:
com.android.systemui.qs.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 的构造函数:
com.android.systemui.qs.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 的构造方法:
com.android.systemui.qs.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() 方法。
com.android.systemui.tuner.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():
com.android.systemui.qs.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 有点出入。
com.android.systemui.qs.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():
com.android.systemui.qs.tileimpl.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() 方法:
com.android.systemui.qs.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()
com.android.systemui.qs.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():
com.android.systemui.qs.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() 实现:
com.android.systemui.qs.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 种。其他的展示方法也类似。
res/layout/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>

标签:case,QS,return,状态栏,get,Android13,......,qs,tile
From: https://blog.csdn.net/u010345983/article/details/130883228

相关文章

  • Vscode实现应用qss样式表
    qss简介qss(QtStyleSheets)是一种基于CSS的样式语言,用于描述用户界面元素的外观和感觉。qss可以让用户在不修改代码的情况下,轻松地自定义应用程序的外观。其语法基本如下:objectName{property:value;}其中,objectName是要设置样式的对象名,property是要设置的属性,value是属......
  • 【QSS样式表 - ⑥】:QPushButton控件样式
    文章目录QPushBUtton控件样式QSS示例QPushBUtton控件样式常用子控件常用伪状态QSS示例代码:QPushButton{ background-color:#99B5D1; color:white; font-weigth:bold; border-radius:20px;}QPushButton:hover{ background-color:red;}QPushBu......
  • QT网盘笔记(日志,qss,加密)
    前言:本章为本人在学习QT网盘时学到的知识,在此记录。一、QT日志1、前因(1)网盘服务端需要记录每一个登陆者登录的时间,账号名,在遇到除操作错误的错误时候会统计到日志中,同时客户的意见反馈也写入其中。(2)加上互斥锁防止多线程写入时混乱。2、互斥锁    (1)定义:在多......
  • C语言 qsort 详解
    qsort1.定义:           qsort,基于快速排序(QuickSort)算法的一个库函数,可以将一串整型类型、浮点类型、       字符串类型、结构体类型等的数据进行排序。比冒泡排序,选择法排序好用,且速度更快。2.语法:        具体语法如下:qsort(arr,siz......
  • 详解AQS二:ReentrantLock公平锁原理
    ReentrantLock作为我们使用频率最高的显式锁,它是AQS的经典实现,本篇文章将以ReentrantLock公平锁为例讲解AQS的实现。一、ReentrantLock在之前的文章《线程同步机制一:内部锁和显式锁》中已经提到过关于显式锁ReentrantLock的简单使用privatefinalLocklock=newReentrantLock(......
  • QSpinBox & DoubleQSPinBox
    两个控件QSpinBox&DoubleQSPinBox都是QAbstractSpinBox的子类。其中我们不再举例DoubleQSPinBox ,因为其主要区别在于精度上,也就是说尤如其名DoubleQSPinBox 是double类型的我们来看看QSpinBox的几个主要功能。QSpinBox::value()//读取数据QSpinBox......
  • 深入了解快速排序(qsort)的模拟实现
    这里写目录标题深入了解快速排序(qsort)的模拟实现qsort函数简介排序函数模拟:冒泡排序代替qsort交换函数:灵活处理任意数据类型主函数:结构体排序示例比较函数:定义排序规则深入了解快速排序(qsort)的模拟实现快速排序(qsort)是计算机科学中一个非常著名的排序算法,以其高效和......
  • Android 设置沉浸式状态栏
    原文地址:Android设置沉浸式状态栏-Stars-One的杂货小窝标题所说的沉浸式状态栏实际就是底部有背景图,而状态栏是沉浸效果(可以在背景图上面展示的),如下面的这种效果:官方API实现官方新版本出了个api,名为enableEdgeToEdge(),方便我们使用这种方法主要适用Activity的整......
  • Android13开机向导
    文章目录前言需求-场景第三方资料说明需求思路按照平台思路从配置上去feature换个思路,去feature。SimMissingActivity判断跳过逻辑SetupWizardUtils判断SIM、hasSystemFeatureFEATURE_TELEPHONYPackageManager.FEATURE_TELEPHONYApplicationPackageManagerhasSy......
  • JUC之基-AQS详解
    AQSAQS是JUC学习的基石,是JUC中许多锁的底层实现机制,我们今天从ReentrantLock出发来深入源码解读AQS的设计。AQS底层AQS的几个重要属性://阻塞队列的头privatetransientvolatileNodehead;//阻塞队列的尾privatetransientvolatileNodetail;//核......