二 QS面板内部实现梳理
2.1 QS面板开关集合构建流程
QsPanel 中除了创建各个开关View,还创建了亮度条,Footer等元素,本文重心放在开关的构建上,故不会分析其他元素的创建流程. 另外QsFragment / QSHost 等元素是在 SystemUI 启动流程中通过注入或反射构建的,其前期构建流程跳过分析
先看看整体的流程图
流程比较简单,对照源码阅读即可.简单说就是QSTileHost
对象在构建初期就借助QSFactoryImpl
工具对象提前创建好了各个开关的后端对象QSTile
,而后QSPanel
在初始化的过程中,再次利用QSTileHost
去构建各个开关的视图对象QSTileView
,至此一个完整的开关就构建完成,最后add到开关容器PagedTileLayout
中去.若对上述各个类的作用不清楚的话可回头看看前面对各个类簇的介绍
2.2 Tile后端 是如何与 Tile视图层 产生联系的
前面QSTile
的类簇中我们可以看到其有多个内部类,与此相关的内部类包括 Callback 和 State,
[图片上传失败...(image-be33f9-1669866239444)]
Tile 视图与后端的联系就是借助这两个内部类以及QSPanel
这个中介产生联系的,我们来看看代码.
前面 2.1小节我们在梳理QS面板开关集合构建流程时可以看到步骤10通过addTile
函数来构建Tile
开关对象,其代码细节如下
[packages/SystemUI/src/com/android/systemui/qs/QSPanel.java] public static final class TileRecord extends Record { public QSTile tile; public com.android.systemui.plugins.qs.QSTileView tileView; public boolean scanState; public QSTile.Callback callback; } protected TileRecord addTile(final QSTile tile, boolean collapsedView) { final TileRecord r = new TileRecord(); r.tile = tile; r.tileView = createTileView(tile, collapsedView); // 构建开关视图层 final QSTile.Callback callback = new QSTile.Callback() { @Override public void onStateChanged(QSTile.State state) { drawTile(r, state); } ...... }; r.tile.addCallback(callback); // 向开关后端注册回调 r.callback = callback; r.tileView.init(r.tile); // 初始化点击事件 r.tile.refreshState(); // 首次刷新 mRecords.add(r); mCachedSpecs = getTilesSpecs(); if (mTileLayout != null) { mTileLayout.addTile(r); // add到开关容器 } return r; } protected void drawTile(TileRecord r, QSTile.State state) { r.tileView.onStateChanged(state); // 将变化交给开关视图层 }
可以看到QSPanel
向r.tile
即后端注册了一个回调器,并在回调发生时将开关状态State
传递给r.tileView
即开关视图层去做视图刷新.
至于这个State
是在哪里刷新的这个我们2.3 小节再分析.
2.3 Tile的一次点击事件背后的流程是怎么样的
前面 2.2 小结介绍addTile
函数时我们可以看到这么一句r.tileView.init(r.tile);
,这里完成了将视图层的点击事件转交给后端的操作
[packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java] @Override public void init(QSTile tile) { init(v -> tile.click(), v -> tile.secondaryClick(), view -> { tile.longClick(); return true; }); } public void init(OnClickListener click, OnClickListener secondaryClick, OnLongClickListener longClick) { setOnClickListener(click); setOnLongClickListener(longClick); }
即将QSTileView
收到的点击事件分别交给QSTile
对应的点击函数处理
[packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java] public void click() { mHandler.sendEmptyMessage(H.CLICK); } public void handleMessage(Message msg) { if (msg.what == CLICK) { if (mState.disabledByPolicy) { ...... } else { handleClick(); } } } abstract protected void handleClick();
QSTileImpl
中handleClick()
函数是个抽象方法,分别由对应的开关子类去实现,例如Wifi开关
[packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java] @Override protected void handleClick() { ...... refreshState(wifiEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING); ...... }
又回到QSTileImpl
[packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java] protected final void refreshState(Object arg) { mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget(); } public void handleMessage(Message msg) { if (msg.what == REFRESH_STATE) { handleRefreshState(msg.obj); } } protected void handleRefreshState(Object arg) { handleUpdateState(mTmpState, arg); final boolean changed = mTmpState.copyTo(mState); if (changed) { handleStateChanged(); } ... } abstract protected void handleUpdateState(TState state, Object arg);
可以看到QSTileImpl
用一个State
类型的临时变量去handleUpdateState
函数中收集当前开关的最新状态,而这个函数是个抽象方法,实现依旧是在各个开关子类中,我们看下Wifi开关
[packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java] protected final void refreshState(Object arg) { mHandler.obtainMessage(H.REFRESH_STATE, arg).sendToTarget(); } public void handleMessage(Message msg) { if (msg.what == REFRESH_STATE) { handleRefreshState(msg.obj); } } protected void handleRefreshState(Object arg) { handleUpdateState(mTmpState, arg); final boolean changed = mTmpState.copyTo(mState); if (changed) { handleStateChanged(); } ... } abstract protected void handleUpdateState(TState state, Object arg);
这里抽取了一些代码片段,可以看到开关后端会根据当前开关状态对 state 进行赋值,这些赋值会在后续开关视图刷新时产生作用
handleUpdateState
函数收集完开关状态后,我们回到前面
[packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java] protected void handleRefreshState(Object arg) { handleUpdateState(mTmpState, arg); final boolean changed = mTmpState.copyTo(mState); if (changed) { handleStateChanged(); } ... }
可以看到假如开关状态发生了改变则会导致handleStateChanged()
被调用
[packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java] private void handleStateChanged() { if (mCallbacks.size() != 0) { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).onStateChanged(mState); } } }
这里就和前面 2.2 小节关联起来了,即 state 会被传递到视图层
[packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java] public void onStateChanged(QSTile.State state) { mHandler.obtainMessage(H.STATE_CHANGED, state).sendToTarget(); } private class H extends Handler { private static final int STATE_CHANGED = 1; public H() { super(Looper.getMainLooper()); } @Override public void handleMessage(Message msg) { if (msg.what == STATE_CHANGED) { handleStateChanged((QSTile.State) msg.obj); } } }
handleStateChanged
函数不展开讲,该函数是真正做开关视图显示刷新的地方,细节看源码.
至此,我们就将开关点击背后的流程梳理清楚了,相信阅读完这三条主线的代码流程后,对整个QS面板的整体实现就很清晰了
设计思考
阅读前面 2.3小节的代码我们可以看到,不管是在Tile
的逻辑层还是视图层,其内部均通过Handler来组织开关的状态刷新,因为整个QS面板有各种各样的开关,各个开关的刷新时机是不确定的,而通过消息机制则可以有条不紊得将所有开关的刷新有序组织起来,这里体现了Android中很重要的一个特性有序性,这是我们值得借鉴和参考的.