Android T About SystemUI
SystemUI服务启动
在 Android 系统之后,系统首先会启动一个名为 Zygote 的进程.
而 Zygote 进程又会启动 SystemServer 进程,SystemServer 又会启动 SystemUI.
SystemServer::startOtherServices(){
mActivityManagerService.systemReady(() -> {
...
try {
startSystemUi(context, windowManagerF);
} catch (Throwable e) {
reportWtf("starting System UI", e);
}
...
}
}
private static void startSystemUi(Context context, WindowManagerService windowManager) {
PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
Intent intent = new Intent();
intent.setComponent(pm.getSystemUiServiceComponent());
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
//通知WMS
windowManager.onSystemUiStarted();
}
可以看到 startSystemUi() 方法首先获取PackageManagerInternal对象实例 pm,
再调用 pm 的 getSystemUiServiceComponent()方法获取SystemUIService 组件的路径,
再调用 startServiceAsUser()方法启动SystemUIService服务.
最后通知 WindowManagerService SystemUI 已经开始启动,这一步确保窗口管理器和其他系统组件知道 SystemUI 的状态.
/frameworks/base/core/res/res/values/config.xml
<!-- SystemUi service component -->
<string name="config_systemUIServiceComponent" translatable="false"
>com.android.systemui/com.android.systemui.SystemUIService</string>
SystemUI初始化
SystemUI是一个系统APP.
它同其他APP一样拥有Application 入口,有 AndroidManifest.xml清单配置文件.
/frameworks/base/packages/SystemUI/AndroidManifest.xml
<application
android:name=".SystemUIApplication"
android:persistent="true"
android:allowClearUserData="false"
android:backupAgent=".backup.BackupHelper"
android:killAfterRestore="false"
android:hardwareAccelerated="true"
android:label="@string/app_label"
android:icon="@drawable/icon"
android:process="com.android.systemui"
android:supportsRtl="true"
android:theme="@style/Theme.SystemUI"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
tools:replace="android:appComponentFactory"
android:appComponentFactory=".SystemUIAppComponentFactory">
<!-- Keep theme in sync with SystemUIApplication.onCreate().
Setting the theme on the application does not affect views inflated by services.
The application theme is set again from onCreate to take effect for those views. -->
...
<!-- Broadcast receiver that gets the broadcast at boot time and starts
up everything else.
TODO: Should have an android:permission attribute
-->
<service android:name="SystemUIService"
android:exported="true"
/>
...
从清单文件可以:
- SystemUI 的入口为 SystemUIApplication,SystemUIService服务是支持外部唤起.
- SystemUI 是 persistent(持久化)应用,当应用异常crash是,系统会重新启动它.
- 应用组件工厂属性 appComponentFactory 指向了 SystemUIAppComponentFactory.
Android AppComponentFactory是Android框架中的一个重要组件,它主要用于依赖注入和组件化架构中.
依赖注入:AppComponentFactory可以与依赖注入框架(如Dagger、Koin或Hilt)结合使用,帮助管理对象创建和依赖关系,从而提高代码的可维护性和可测试性.
组件化架构:在Android的组件化架构中,AppComponentFactory用于创建和管理组件及其依赖,支持应用的模块化和可重用性
工厂方法模式适用于以下场景:
创建复杂对象:当需要创建的对象具有复杂的构造逻辑时,可以使用工厂方法模式来简化对象的创建过程.
解耦代码:工厂方法模式通过将对象的创建与使用分离,有助于减少代码之间的耦合,提高系统的灵活性和可扩展性.
Android 13与Android 11在获取SystemUI所有子模块上有区别.
Android 11是通过读取xml文件的形式获取,<string-array name="config_systemUIServiceComponentsExclude" translatable="false">
.
Android 13是通过Dagger注入的形式加载了所有子模块,将所有子模块放到了Map集合中,关于CoreStartables类.
1-1 SystemUIApplication
/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
...
// SystemUI模块的各种SystemUI组件
private CoreStartable[] mServices;
// 组件是否已经被启动-Flag
private boolean mServicesStarted;
// 回调函数
private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
// SystemUI子组件
private SysUIComponent mSysUIComponent;
//SystemUI初始化器
private SystemUIInitializer mInitializer;
...
@Override
public void onCreate() {
super.onCreate();
...
//成员变量赋值
mInitializer = mContextAvailableCallback.onContextAvailable(this);
mSysUIComponent = mInitializer.getSysUIComponent();
mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
...
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
...
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mBootCompleteCache.isBootComplete()) return;
if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
unregisterReceiver(this);
mBootCompleteCache.setBootComplete();
if (mServicesStarted) {
final int N = mServices.length;
for (int i = 0; i < N; i++) {
notifyBootCompleted(mServices[i]);
}
}
}
}, bootCompletedFilter);
...
} else {
...
startSecondaryUserServicesIfNeeded();
}
}
如果是SystemServer 启动的就会进入到这个分支中,注册监听 boot completed 的通知,
最后完全启动后就会回调各个组件 onBootCompleted.
如果不是系统启动,例如多用户登录使用的情况,这时候系统其实已经启动过了,就会走startSecondaryUserServicesIfNeeded()用于启动
SystemUI所需的服务组件,是根据用户来启动相应的服务的.
1-2 SystemUIService
SystemUIApplication::onCreate() ---> SystemUIService::onCreate(){
super.onCreate();
// Start all of SystemUI 启动SystemUI的全部组件
((SystemUIApplication) getApplication()).startServicesIfNeeded();
...
}
SystemUIService 的 onCreate 方法会重新获取 SystemUIApplication 实例对象,
并调用该对象的 startServicesIfNeeded 方法.
/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
public void startServicesIfNeeded() {
final String vendorComponent = mInitializer.getVendorComponent(getResources());
// Sort the startables so that we get a deterministic ordering.
// TODO: make #start idempotent and require users of CoreStartable to call it.
//CoreStartable类表示在SystemUI启动时,初始化的一组 SystemUI 功能,独立于系统的其余部分
Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
Comparator.comparing(Class::getName));
sortedStartables.putAll(mSysUIComponent.getStartables());
sortedStartables.putAll(mSysUIComponent.getPerUserStartables());
startServicesIfNeeded(
sortedStartables, "StartServices", vendorComponent);
}
CoreStartable类表示在SystemUI启动时,初始化的一组 SystemUI 功能,独立于系统的其余部分.
可以通过Dagger框架在每次构建时自定义包含和运行哪些CoreStartable(替代以前SystemUI模块所需要表示的组件).
基类中包含名义功能,因此构建起来轻量且成本低廉.
与Activity、Services和类似的Android构造不同,CoreStartables没有唯一的上下文,
并且除了start()调用一次的单个方法外没有其他生命周期方法.
/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
private void startServicesIfNeeded(
Map<Class<?>, Provider<CoreStartable>> startables,
String metricsPrefix,
String vendorComponent) {
if (mServicesStarted) {
return;
}
// 具体SystemUI组件类合集
mServices = new CoreStartable[startables.size() + (vendorComponent == null ? 0 : 1)];
...
int i = 0;
for (Map.Entry<Class<?>, Provider<CoreStartable>> entry : startables.entrySet()) {
String clsName = entry.getKey().getName();
int j = i; // Copied to make lambda happy.
//启动各个组件
timeInitialization(
clsName,
() -> mServices[j] = startStartable(clsName, entry.getValue()),
log,
metricsPrefix);
i++;
}
if (vendorComponent != null) {
timeInitialization(
vendorComponent,
() -> mServices[mServices.length - 1] =
startAdditionalStartable(vendorComponent),
log,
metricsPrefix);
}
for (i = 0; i < mServices.length; i++) {
if (mBootCompleteCache.isBootComplete()) {
notifyBootCompleted(mServices[i]);
}
//mServices[i]:具体SystemUI组件类的完整路径
mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
Log.d("txx","mServices[i].getClass().getName():" +mServices[i].getClass().getName());
}
mSysUIComponent.getInitController().executePostInitTasks();
log.traceEnd();
mServicesStarted = true;
}
private static void timeInitialization(String clsName, Runnable init, TimingsTraceLog log,
String metricsPrefix) {
long ti = System.currentTimeMillis();
log.traceBegin(metricsPrefix + " " + clsName);
init.run();
log.traceEnd();
// Warn if initialization of component takes too long
ti = System.currentTimeMillis() - ti;
if (ti > 1000) {
Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms");
}
}
02-06 07:53:36.811 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.broadcast.BroadcastDispatcherStartable
02-06 07:53:36.811 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.clipboardoverlay.ClipboardListener
02-06 07:53:36.811 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.controls.start.ControlsStartable
02-06 07:53:36.811 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.dreams.DreamMonitor
02-06 07:53:36.811 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.power.PowerUI
02-06 07:53:36.811 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.reardisplay.RearDisplayDialogController
02-06 07:53:36.811 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.recents.Recents
02-06 07:53:36.811 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.shortcut.ShortcutKeyDispatcher
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProviderImpl
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.statusbar.phone.CentralSurfacesImpl
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.statusbar.phone.KeyguardLiftController
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigCoreStartable
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.theme.ThemeOverlayController
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.toast.ToastUI
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.util.NotificationChannels
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.volume.VolumeUI
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.wmshell.WMShell
02-06 07:53:36.812 756 756 D txx : mServices[i].getClass().getName():com.android.systemui.VendorServices[任何供应商特定服务的占位符]
...
所有组件都会执行CoreStartable
类的start()
方法启动相关类的服务,启动完成后,再调用onBootCompleted()
.
Android T 以前
这个方法会根据配置文件config_systemUIServiceComponents或config_systemUIServiceComponentsPerUser中的定义使用反射来创建、启动一系列SystemUI的服务,例如StatusBar, NavigationBar, NotificationPanel, Keyguard等.
这些服务每一个都扩展自一个名为SystemUI的接口.
SystemUI会为他们提供了一个Context,并为他们提供onConfigurationChanged和onBootCompleted的回调.
这些服务是SystemUI的主要组件,负责提供各种功能和界面.
Android T 以后
Android T 以前每个SystemUI服务还会依赖于Dependency类提供的自定义依赖注入,来获取一些跨越SystemUI生命周期的对象.
Android T 之后,SystemUI功能组件的创建和依赖注入都是Dagger自动完成.
//An example Dagger Subcomponent for Core SysUI--Core SysUI的Dagger子组件示例
./frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
//SystemUI objects that are injectable should go here.-- 可注射的SystemUI对象应该放在这里
./frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
//dragger Module,用于注入SystemUI所需的各种SystemUI组件
./frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
//CoreStartable合集
./frameworks/base/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
CentralSurfacesImpl,展示Status bar/NaviagtionBar
./frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
...
@Override
public void start() {
...
mStatusBarSignalPolicy.init();//状态栏信号icon初始化
...
try {
result = mBarService.registerStatusBar(mCommandQueue);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
createAndAddWindows(result);
...
}
@Override
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result);
mNotificationShadeWindowController.attach();
mStatusBarWindowController.attach();
}
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
...
inflateStatusBarWindow();//膨胀状态栏窗口
mNotificationShadeWindowView.setOnTouchListener(getStatusBarWindowTouchListener());
...
// 设置 CollapsedStatusBarFragment and PhoneStatusBarView
StatusBarInitializer initializer = mCentralSurfacesComponent.getStatusBarInitializer();
...
createNavigationBar(result);//创建NavigationBar
...
// Set up the quick settings tile panel 创建 QS 下拉布局
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager =
mFragmentService.getFragmentHostManager(container);
ExtensionFragmentListener.attachExtensonToFragment(
mFragmentService,
container,
QS.TAG,
R.id.qs_frame,
mExtensionController
.newExtension(QS.class)
.withPlugin(QS.class)
.withDefault(this::createDefaultQSFragment)
.build());
mBrightnessMirrorController = new BrightnessMirrorController(
...
fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
QS qs = (QS) f;
if (qs instanceof QSFragment) {
mQSPanelController = ((QSFragment) qs).getQSPanelController();
((QSFragment) qs).setBrightnessMirrorController(mBrightnessMirrorController);
}
});
}
}
protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {
mNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);
}
./frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
public void createNavigationBars(final boolean includeDefaultDisplay,
RegisterStatusBarResult result) {
updateAccessibilityButtonModeIfNeeded();
// Don't need to create nav bar on the default display if we initialize TaskBar.
final boolean shouldCreateDefaultNavbar = includeDefaultDisplay
&& !initializeTaskbarIfNecessary();
Display[] displays = mDisplayTracker.getAllDisplays();
for (Display display : displays) {
if (shouldCreateDefaultNavbar
|| display.getDisplayId() != mDisplayTracker.getDefaultDisplayId()) {
createNavigationBar(display, null /* savedState */, result);
}
}
}
void createNavigationBar(Display display, Bundle savedState, RegisterStatusBarResult result) {
if (display == null) {
return;
}
//TaskBar启用检查
if (isOnDefaultDisplay && initializeTaskbarIfNecessary()) {
return;
}
// 检查窗口管理器服务中是否有导航栏
final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();
try {
if (!wms.hasNavigationBar(displayId)) {
return;
}
} catch (RemoteException e) {
// Cannot get wms, just return with warning message.
Log.w(TAG, "Cannot get WindowManager.");
return;
}
//创建 NavigationBar
final Context context = isOnDefaultDisplay
? mContext
: mContext.createDisplayContext(display);
NavigationBarComponent component = mNavigationBarComponentFactory.create(
context, savedState);
NavigationBar navBar = component.getNavigationBar();
navBar.init();//实际调用的是NavigationBar重写的方法onInit()
mNavigationBars.put(displayId, navBar);
// 添加视图状态监听器
navBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (result != null) {
navBar.setImeWindowStatus(display.getDisplayId(), result.mImeToken,
result.mImeWindowVis, result.mImeBackDisposition,
result.mShowImeSwitcher);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
v.removeOnAttachStateChangeListener(this);
}
});
}
void removeNavigationBar(int displayId) {
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
navBar.destroyView();
mNavigationBars.remove(displayId);
}
}
public void hideAllNavigationBar() {
Display[] displays = mDisplayTracker.getAllDisplays();
for (Display display : displays) {
removeNavigationBar(display.getDisplayId());
}
}
./frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
public class NavigationBar extends ViewController<NavigationBarView> implements Callbacks {
1.onInit()-- //重写onInit方法覆盖ViewController中的init方法
2.onViewAttached()
3.prepareNavigationBarView(){
mView.reorient(); // 初始化view,然后会执行NavigationBarView的onConfigurationChanged方法
}
4.onViewDetached()
}
./frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
public class NavigationBarView extends FrameLayout {
1.reorient()
2.onConfigurationChanged()
3.updateIcons()
}
./frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarInflaterView.java
public class NavigationBarInflaterView extends FrameLayout
implements NavigationModeController.ModeChangedListener {
①NavigationBarInflaterView构造方法
public NavigationBarInflaterView(Context context, AttributeSet attrs) {
super(context, attrs);
createInflaters();
mOverviewProxyService = Dependency.get(OverviewProxyService.class);
mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
}
②createInflaters()
③onFinishInflate()//加载按键布局(navigation_bar.xml)结束后调用
④inflateChildren()
⑤clearViews()
⑥inflateLayout()
⑦getDefaultLayout()
}
布局中./frameworks/base/packages/SystemUI/res/layout/navigation_bar.xml:29: <com.android.systemui.navigationbar.NavigationBarInflaterView
,运作NavigationBarInflaterView构造函数.
CarSystemUI
Android Automotive OS 是 Google 专门为汽车开发的操作系统版本,它基于 Android 平台,并针对车辆内部的使用进行了优化.
使用 Android Auto 或 Android Automotive API.
SystemUI 项目位于 /frameworks/base/package/SystemUI
目录,
CarSystemUI 项目位于 /packages/apps/Car/SystemUI
目录.
原生SystemUI 可以独立编译成 SystemUI.apk 文件,CarSystemUI 无法独立编译.
必须在原生 SystemUI 的基础上才能编译出 CarSystemUI.apk 文件,
通过阅读 CarSystemUI 的 Android.bp 文件可以发现,CarSystemUI 在编译的时候,会将原生的 SystemUI 以静态库的方式引入.
车载Android应用开发与分析 - SystemUI
Android车载应用开发指南3 - SystemUI详解-android-12.0.0_r3
先看AndroidManifest.xml清单配置文件,找到对应入口:
./packages/apps/Car/SystemUI/AndroidManifest.xml
<application
tools:replace="android:appComponentFactory"
android:appComponentFactory="com.android.systemui.CarSystemUIAppComponentFactory">
<activity
android:name=".car.activity.ActivityBlockingActivity"
android:documentLaunchMode="always"
android:excludeFromRecents="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:exported="false"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
Android 13 利用 Dagger框架,使用工厂模式来构建CarSystemUI的各个组件,相关的类有:
./packages/apps/Car/SystemUI/src/com/android/systemui/CarSystemUICoreStartableModule.kt
./packages/apps/Car/SystemUI/src/com/android/systemui/CarSysUIComponent.java
./packages/apps/Car/SystemUI/src/com/android/systemui/CarSystemUIAppComponentFactory.java
每个组件都继承或实现CoreStartable类.
Android Dagger2简单使用
Android中的依赖注入(DI)框架Hilt
2-1 CarSystemBar -- CarStatusBar/CarNavBar
./packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBar.java
@Override
public void start() {
...
createSystemBar(result);
...
}
private void createSystemBar(RegisterStatusBarResult result) {
buildNavBarWindows();//构建NavBar容器
buildNavBarContent();//构建NavBar内容
attachNavBarWindows();//将NavBar添加到Window中
...
}
...
private ViewGroup mTopSystemBarWindow;
private void buildNavBarWindows() {
mTopSystemBarWindow = mCarSystemBarController.getTopWindow();
mBottomSystemBarWindow = mCarSystemBarController.getBottomWindow();
mLeftSystemBarWindow = mCarSystemBarController.getLeftWindow();
mRightSystemBarWindow = mCarSystemBarController.getRightWindow();
}
private void attachNavBarWindows() {
mSystemBarConfigs.getSystemBarSidesByZOrder().forEach(this::attachNavBarBySide);
}
private void buildNavBarContent() {
mTopSystemBarView = mCarSystemBarController.getTopBar(isDeviceSetupForUser());
if (mTopSystemBarView != null) {
mSystemBarConfigs.insetSystemBar(SystemBarConfigs.TOP, mTopSystemBarView);
mHvacController.registerHvacViews(mTopSystemBarView);
mTopSystemBarWindow.addView(mTopSystemBarView);
}
mBottomSystemBarView = mCarSystemBarController.getBottomBar(isDeviceSetupForUser());
if (mBottomSystemBarView != null) {
mSystemBarConfigs.insetSystemBar(SystemBarConfigs.BOTTOM, mBottomSystemBarView);
mHvacController.registerHvacViews(mBottomSystemBarView);
mBottomSystemBarWindow.addView(mBottomSystemBarView);
}
...
buildNavBarWindows()
构建顶部栏、底部栏、左侧栏、右侧栏这四种导航栏视图对象,
然后再调用 buildNavBarContent()
构建每种导航栏所对应的具体视图内容,
最后会调用 attachNavBarWindows()
将状态栏和导航栏视图添加到 Window 中.
- 构建NavBar容器
// 车载系统栏控制器
/package/app/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBarController.java
// 车载系统栏控件工厂
/packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBarViewFactory.java
//以顶部栏(TopWindow)为例
CarSystemBarController::getTopWindow()
--> CarSystemBarViewFactory::getTopWindow()
--> CarSystemBarViewFactory::getWindowCached(Type type)
{
if (mCachedContainerMap.containsKey(type)) {
return mCachedContainerMap.get(type);
}
ViewGroup window = (ViewGroup) View.inflate(mContext,
R.layout.navigation_bar_window, /* root= */ null);
mCachedContainerMap.put(type, window);
return mCachedContainerMap.get(type);
}
getWindowCached(Type type)
先判断类型为 ArrayMap<Type, ViewGroup> 的缓存 mCachedContainerMap 中是否存在对应的 CarSystemBarView 视图,
如果存在直接返回。否则会调用 View 的 inflate 方法将 R.layout.navigation_bar_window
布局文件构建成相应的视图对象,
然后存储到 mCachedContainerMap 中,并返回该视图对象.
./frameworks/base/packages/SystemUI/res/layout/navigation_bar_window.xml
<com.android.systemui.navigationbar.NavigationBarFrame
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/navigation_bar_frame"
android:theme="@style/Theme.SystemUI"
android:layout_height="match_parent"
android:layout_width="match_parent">
</com.android.systemui.navigationbar.NavigationBarFrame>
用的是原生SystemUI的布局文件,NavigationBarFrame.java
是一个自定义控件.
/frameworks/base/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarFrame.java
public class NavigationBarFrame extends FrameLayout {
private DeadZone mDeadZone = null;
...
public NavigationBarFrame(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setDeadZone(@NonNull DeadZone deadZone) {
mDeadZone = deadZone;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == ACTION_OUTSIDE) {
if (mDeadZone != null) {
return mDeadZone.onTouchEvent(event);
}
}
return super.dispatchTouchEvent(event);
}
NavigationBarFrame是一个继承自 FrameLayout 的自定义控件,
上面调用的 buildNavBarWindows() 方法所构建的四个视图容器对象,其实就对应了 NavigationBarFrame 对象.
- 构建NavBar内容,填充视图容器
用 buildNavBarContent() 方法来为视图容器构建具体的视图内容,TopBar为例:
private void buildNavBarContent() {
mTopSystemBarView = mCarSystemBarController.getTopBar(isDeviceSetupForUser());
if (mTopSystemBarView != null) {
mSystemBarConfigs.insetSystemBar(SystemBarConfigs.TOP, mTopSystemBarView);
mHvacController.registerHvacViews(mTopSystemBarView);
mTopSystemBarWindow.addView(mTopSystemBarView);
}
...
CarSystemBarController::getTopBar(){
if (!mShowTop) {
return null;
}
// 获取顶部栏具体视图内容
mTopView = mCarSystemBarViewFactory.getTopBar(isSetUp);
// 让mTopView视图内容对象和状态栏触摸监听对象、通知栏控制器、空调面板控制器产生关联
setupBar(mTopView, mTopBarTouchListeners, mNotificationsShadeController,
mHvacPanelController, mHvacPanelOverlayViewController);
if (isSetUp) {
// We do not want the privacy chips or the profile picker to be clickable in
// unprovisioned mode.
mMicPanelController = setupSensorQcPanel(mMicPanelController, R.id.mic_privacy_chip,
R.layout.qc_mic_panel);
mCameraPanelController = setupSensorQcPanel(mCameraPanelController,
R.id.camera_privacy_chip, R.layout.qc_camera_panel);
setupProfilePanel();
}
return mTopView;
}
//getBar() 获取对应视图
CarSystemBarViewFactory::getTopBar() --> CarSystemBarViewFactory::getBar(){
// 从sLayoutMap中获取对应的布局文件资源id
CarSystemBarView view = getBarCached(isSetUp, provisioned, unprovisioned);
if (view == null) {
String name = isSetUp ? provisioned.name() : unprovisioned.name();
Log.e(TAG, "CarStatusBar failed inflate for " + name);
throw new RuntimeException(
"Unable to build " + name + " nav bar due to missing layout");
}
return view;
}
//布局文件
private static final ArrayMap<Type, Integer> sLayoutMap = setupLayoutMapping();
private static ArrayMap<Type, Integer> setupLayoutMapping() {
ArrayMap<Type, Integer> map = new ArrayMap<>();
map.put(Type.TOP, R.layout.car_top_system_bar);
map.put(Type.TOP_UNPROVISIONED, R.layout.car_top_system_bar_unprovisioned);
map.put(Type.BOTTOM, R.layout.car_bottom_system_bar);
map.put(Type.BOTTOM_UNPROVISIONED, R.layout.car_bottom_system_bar_unprovisioned);
map.put(Type.LEFT, R.layout.car_left_system_bar);
map.put(Type.LEFT_UNPROVISIONED, R.layout.car_left_system_bar_unprovisioned);
map.put(Type.RIGHT, R.layout.car_right_system_bar);
map.put(Type.RIGHT_UNPROVISIONED, R.layout.car_right_system_bar_unprovisioned);
return map;
}
//Views
private final ArrayMap<Type, CarSystemBarView> mCachedViewMap = new ArrayMap<>(Type.values().length);
private CarSystemBarView getBarCached(boolean isSetUp, Type provisioned, Type unprovisioned) {
Type type = isSetUp ? provisioned : unprovisioned;
if (mCachedViewMap.containsKey(type)) {
return mCachedViewMap.get(type);
}
// 将布局文件转化为CarSystemBarView类型的View对象。
@LayoutRes int barLayout = sLayoutMap.get(type);
CarSystemBarView view = (CarSystemBarView) View.inflate(mContext, barLayout,
/* root= */ null);
view.setupHvacButton();
view.setupQuickControlsEntryPoints(mQuickControlsEntryPointsController, isSetUp);
view.setupReadOnlyIcons(mReadOnlyIconsController);
//管理焦点的View,当用户导航到另一个窗口时,旋转控制器将焦点“锁定”在此处,防止混乱
view.addView(new FocusParkingView(mContext), 0);
mCachedViewMap.put(type, view);
return mCachedViewMap.get(type);
}
- 将各种SystemBar添加到Window
./packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBar.java
private void attachNavBarWindows() {
mSystemBarConfigs.getSystemBarSidesByZOrder().forEach(this::attachNavBarBySide);
}
./packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/SystemBarConfigs.java
private final List<@SystemBarSide Integer> mSystemBarSidesByZOrder = new ArrayList<>();
//获取到当前存在的所有SystemBar所对应的Side.
protected List<Integer> getSystemBarSidesByZOrder() {
return mSystemBarSidesByZOrder;
}
//数据来源
private void sortSystemBarSidesByZOrder() {
List<SystemBarConfig> systemBarsByZOrder = new ArrayList<>(mSystemBarConfigMap.values());
systemBarsByZOrder.sort(new Comparator<SystemBarConfig>() {
@Override
public int compare(SystemBarConfig o1, SystemBarConfig o2) {
return o1.getZOrder() - o2.getZOrder();// 进行大小比较
}
});
// 存储排序后SystemBar的Side数值
systemBarsByZOrder.forEach(systemBarConfig -> {
mSystemBarSidesByZOrder.add(systemBarConfig.getSide());
});
}
SystemBarConfigs 初始化
./packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/SystemBarConfigs.java
@Inject
public SystemBarConfigs(@Main Resources resources) {
mResources = resources;
populateMaps();// 初始化数据
readConfigs();// 读取SystemBar所对应的SystemBarConfig的配置信息
...
sortSystemBarSidesByZOrder();// 使用SystemBarConfig的ZOrder属性对SystemBarConfig的Size进行排序
}
private void readConfigs() {
//是否可用,根据配置文件来判断config.xml
mTopNavBarEnabled = mResources.getBoolean(R.bool.config_enableTopSystemBar);
mBottomNavBarEnabled = mResources.getBoolean(R.bool.config_enableBottomSystemBar);
mLeftNavBarEnabled = mResources.getBoolean(R.bool.config_enableLeftSystemBar);
mRightNavBarEnabled = mResources.getBoolean(R.bool.config_enableRightSystemBar);
// 顶部栏可用
if (mTopNavBarEnabled) {
SystemBarConfig topBarConfig =
new SystemBarConfigBuilder()
.setSide(TOP)
// 顶部栏高度
.setGirth(mResources.getDimensionPixelSize(
R.dimen.car_top_system_bar_height))
// 系统栏类型
.setBarType(mResources.getInteger(R.integer.config_topSystemBarType))
// 系统栏Z轴序列
.setZOrder(mResources.getInteger(R.integer.config_topSystemBarZOrder))
.setHideForKeyboard(mResources.getBoolean(
R.bool.config_hideTopSystemBarForKeyboard))
.build();
mSystemBarConfigMap.put(TOP, topBarConfig);
}
...
上述可知,CarSystemBar::attachNavBarWindows()最终会循环 mSystemBarSidesByZOrder 集合的内容,
用该集合的子项作为参数,依次调用 CarSystemBar::attachNavBarBySide(),将View填充到Window上.
./packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBar.java
private void attachNavBarBySide(int side) {
switch (side) {
case SystemBarConfigs.TOP:
//如果顶部栏视图容器不为空,将顶部栏视图容器添加到Window中
if (mTopSystemBarWindow != null) {
mWindowManager.addView(mTopSystemBarWindow,
mSystemBarConfigs.getLayoutParamsBySide(SystemBarConfigs.TOP));
}
break;
...
PS:
状态栏配置类SystemBarConfig为 SystemBarConfigs.java的内部类,
作用为配置状态栏类型及对应的视图内容,其中状态栏位置和标题等信息是在上面的 populateMaps() 方法中赋值.
2-2 顶部状态栏 mTopSystemBarView 为例,详细介绍
//mTopSystemBarView布局文件为 car_top_system_bar
/packages/apps/Car/SystemUI/res/layout/car_top_system_bar.xml
//状态栏中的图标布局,例如 Wi-Fi、蓝牙等连接状态的图标
./packages/apps/Car/SystemUI/res/layout/system_icons.xml
//System 插件替换不同的system_icons布局
./packages/apps/Car/SystemUI/samples/sample1/rro/res/layout/system_icons.xml
//管理状态栏中的图标
./packages/apps/Car/SystemUI/src/com/android/systemui/car/statusicon/ui/ReadOnlyIconsController.java
./packages/apps/Car/SystemUI/src/com/android/systemui/car/statusicon/ui/QuickControlsEntryPointsController.java
//状态栏中的图标配置文件
./packages/apps/Car/SystemUI/res/values/config.xml
<!-- List of StatusIconControllers associated with icons to display for QC entry points.
The icons will be added to the view in the order their controllers appear on this list. -->
<string-array name="config_quickControlsEntryPointIconControllers" translatable="false">
<item>com.android.systemui.car.statusicon.ui.BluetoothStatusIconController</item>
<item>com.android.systemui.car.statusicon.ui.SignalStatusIconController</item>
<item>com.android.systemui.car.statusicon.ui.DisplayStatusIconController</item>
</string-array>
<!-- List of StatusIconControllers associated with read-only status icons.
The icons will be added to the view in the order their controllers appear on this list.-->
<string-array name="config_readOnlyIconControllers" translatable="false">
<item>com.android.systemui.car.statusicon.ui.LocationStatusIconController</item>
<item>com.android.systemui.car.statusicon.ui.PhoneCallStatusIconController</item>
</string-array>
//关于进入深色模式状态,system icon图标变化的类
/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/DarkIconDispatcherImpl.java
//DarkIconDispatcher 中只定义了接口,真正实现该接口是在 DarkIconDispatcherImpl 中
public void addDarkReceiver(DarkReceiver receiver) {
mReceivers.put(receiver, receiver);
receiver.onDarkChanged(mTintArea, mDarkIntensity, mIconTint);
}
RRO机制-Runtime Resource Overlay
Android的RRO(Runtime Resource Overlay)机制允许开发者在运行时替换或重写系统资源,
例如布局、图标、字符串等。这个机制的目标是为了支持设备定制和主题化,特别是在不修改系统源代码的情况下.
RRO通过在系统的资源上叠加一个额外的资源层,来实现个性化和品牌定制,而不需要修改原有的资源文件.
通常,RRO被用于OEM厂商在其设备上定制UI和功能或者在Android版本升级时保持兼容性.
CarSystemUI 的RRO's demo
\packages\apps\Car\SystemUI\samples\sample1\rro
\packages\apps\Car\SystemUI\samples\sample2\rro
\packages\apps\Car\SystemUI\samples\sample3\rro
How to build and install RRO
# Enable RRO for the user 0
adb shell cmd overlay enable --user 0 com.android.systemui.rro
# Build all sample RRO's
mmma {path to the samples directory}
# Install one of the sample RRO's
adb install {path to the RRO apk}
# Restart SystemUI
adb shell pkill -TERM -f com.android.systemui
自定义CustomCarSystemBar--Android 12
a.修改状态栏配置
packages/apps/Car/SystemUI/res/values/config.xml
<string-array name="config_systemUIServiceComponentsInclude" translatable="false">
<!--<item>com.android.systemui.car.systembar.CarSystemBar</item>-->
<item>com.custom.systemui.CustomCarSystemBar</item>
<item>com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier</item>
<item>com.android.systemui.car.window.SystemUIOverlayWindowManager</item>
<item>com.android.systemui.car.toast.CarToastUI</item>
<item>com.android.systemui.car.volume.VolumeUI</item>
<item>com.android.systemui.car.cluster.ClusterDisplayController</item>
</string-array>
b.新建CustomCarSystemBar类
//复制CarSystemBar,然后对它删改形成CustomCarSystemBar
package/app/Car/SystemUI/src/com/custom/systemui/CustomCarSystemBar.java
c.绑定CustomCarSystemBar组件
./packages/apps/Car/SystemUI/src/com/android/systemui/CarSystemUIBinder.java
@Module(includes = {RecentsModule.class, StatusBarModule.class, NotificationsModule.class,
KeyguardModule.class, OverlayWindowModule.class, CarNotificationModule.class,
QuickControlsEntryPointsModule.class})
public abstract class CarSystemUIBinder {
// /** Inject Car Navigation Bar. */
// @Binds
// @IntoMap
// @ClassKey(CarSystemBar.class)
// public abstract SystemUI bindCarSystemBar(CarSystemBar sysui);
/** Inject LPCar Navigation Bar. */
@Binds
@IntoMap
@ClassKey(XXCarSystemBar.class)
public abstract SystemUI bindXXCarSystemBar(XXCarSystemBar sysui);
}
d.自定义状态栏/底部导航栏
/packages/apps/Car/SystemUI/src/com/custom/systemui/StatusBarViewHelper.java
public class StatusBarViewHelper{
protected Context mContext;
protected View mRootView;
//为构造方法添加@Inject注解,Dragger自动实例化
@Inject
public StatusBarViewHelper(Context mContext) {
this.mContext = mContext;
mRootView = LayoutInflater.from(mContext).inflate(R.layout.custom_layout_status_bar_view, null, false);
}
public View getRootView() {
return mRootView;
}
public void initView() {
//初始化视图内容
}
}
/packages/apps/Car/SystemUI/src/com/custom/systemui/DockBarViewHelper.java
public class DockBarViewHelper{
protected Context mContext;
protected View mRootView;
// 为构造方法添加@Inject注解
@Inject
public DockBarViewHelper(Context mContext) {
this.mContext = mContext;
mRootView = LayoutInflater.from(mContext).inflate(R.layout.custom_layout_dock_bar_view, null, false);
}
public View getRootView() {
return mRootView;
}
public void initView() {
//初始化视图内容
}
}
e.完整的CustomCarSystemBar
public class CustomCarSystemBar extends SystemUI{
private final WindowManager mWindowManager;
// 顶部状态栏
private StatusBarViewHelper mStatusBarViewHelper;
// 底部Dock栏
private DockBarViewHelper mDockBarViewHelper
private boolean mIsAddStatusBarViewToWindow = false;
private boolean mIsAddDockBarViewToWindow = false;
// 添加Dagger2注解
@Inject
public XXCarSystemBar(Context context, WindowManager windowManager,
StatusBarViewHelper statusBarViewHelper , DockBarViewHelper dockBarViewHelper
……
) {
super(context);
mWindowManager = windowManager;
// 状态栏
mStatusBarViewHelper = statusBarViewHelper;
mDockBarViewHelper = dockBarViewHelper;
……
}
@Override
public void start() {
createSystemBar();
}
private void createSystemBar() {
……
createTopCarSystemBar();
createBottomCarSystemBar();
……
}
private void createTopCarSystemBar() {
//初始化顶部栏视图内容
mStatusBarViewHelper.initView();
//把顶部栏添加到窗口上
addTopCarSystemBarToWindow();
}
private void createBottomCarSystemBar() {
//添加底部栏视图内容
mDockBarViewHelper.initView();
//把底部栏添加到窗口上
addBottomCarSystemBarToWindow();
}
/**
* 把顶部栏添加到窗口上
*/
private void addTopCarSystemBarToWindow() {
if (mIsAddStatusBarViewToWindow) {
return;
}
if (mStatusBarViewHelper != null) {
//将顶部栏添加到Window中
int statusBarHeight = 55;
WindowManager.LayoutParams customTopSystemBar = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight,
WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL,
//WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT); // 半透明
//PixelFormat.TRANSPARENT); //全透明
customTopSystemBar.setTitle("customTopSystemBar");
customTopSystemBar.providesInsetsTypes = new int[]{InsetsState.ITYPE_STATUS_BAR, InsetsState.ITYPE_TOP_MANDATORY_GESTURES};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
customTopSystemBar .setFitInsetsTypes(0);
}
customTopSystemBar.windowAnimations = 0;
customTopSystemBar.gravity = Gravity.TOP;
mWindowManager.addView(mStatusBarViewHelper.getRootView(), customTopSystemBar);
mIsAddStatusBarViewToWindow = true;
}
}
/**
* 把底部栏添加到窗口上
*/
private void addBottomCarSystemBarToWindow() {
if (mIsAddDockBarViewToWindow) {
return;
}
if (mDockBarViewHelper != null) {
// 将底部栏添加到Window中
int navigationBarHeight = 120; // 底部栏高度
WindowManager.LayoutParams customBottomSystemBar = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, navigationBarHeight,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
//WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
//PixelFormat.TRANSLUCENT); // 半透明
PixelFormat.TRANSPARENT); // 全透明
customBottomSystemBar.setTitle("XXBottomCarSystemBar");
customBottomSystemBar.providesInsetsTypes = new int[]{InsetsState.ITYPE_NAVIGATION_BAR, InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
customBottomSystemBar.setFitInsetsTypes(0);
}
customBottomSystemBar.windowAnimations = 0;
customBottomSystemBar.gravity = Gravity.BOTTOM;
mWindowManager.addView(mDockBarViewHelper.getRootView(), customBottomSystemBar);
mIsAddDockBarViewToWindow = true;
}
}
}
Android 13可以参考上面的做法,复制Android 13的CarSystemBar修改完成.
SystemUI Plugin
初试 SystemUI Plugin
SystemUI Plugin 简介及使用
如何定制一个SystemUI?
目前定制SystemUI常见的做法,是从原生SystemUI中移植少量必须的源码,
然后从头制作一个源码、功能完全可控的SystemUI.
如果不从头开发SystemUI,又想快速修改SystemUI的一些模块.
Google官方提供了SystemUI Plugin,SystemUI的功能可以动态替换或修改.
文档路径:./frameworks/base/packages/SystemUI/docs/plugins.md
Plugin Hooks
源码中有对应的文档介绍它,路径:./frameworks/base/packages/SystemUI/docs/plugin_hooks.md
.
以下是文档内容:
Plugin hooks是一些预定义的插件接口,它们可以让应用实现一些特定的功能,
并通过Intent和注解来注册和声明插件的类型和版本.
Plugin hooks有多种类型,例如OverlayPlugin, QSFactory, VolumeDialog等.
每种类型都有一个对应的action和expected interface,用于标识插件的功能和要求.
Android T中Plugin hooks预定义接口主要有以下几种:
- BcSmartspaceDataPlugin: 这个plugin可以让应用提供自定义的数据给锁屏界面上的智能空间[BcSmartspace],例如天气、日历、新闻等.
- QSFactory: 这个plugin可以让应用提供自定义的快速设置工厂[QSFactory],用于创建快速设置图块或面板.
- VolumeDialog: 这个plugin可以让应用自定义音量调节对话框[VolumeDialog]的外观和行为,例如添加新的音量控制选项或改变音量条的样式.
### Action: com.android.systemui.action.PLUGIN_VOLUME
Expected interface: [VolumeDialog](/frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialog.java)
Use: Allows replacement of the volume dialog.
...
ExamplePlugin
源码中有现成的demo:\frameworks\base\packages\SystemUI\plugin\ExamplePlugin
./frameworks/base/packages/SystemUI/plugin/ExamplePlugin/src/com/android/systemui/plugin/testoverlayplugin/SampleOverlayPlugin.java
@Requires(target = OverlayPlugin.class, version = OverlayPlugin.VERSION)
public class SampleOverlayPlugin implements OverlayPlugin {
private static final String TAG = "SampleOverlayPlugin";
private Context mPluginContext;
private View mStatusBarView;
private View mNavBarView;
private boolean mInputSetup;
private boolean mCollapseDesired;
private float mStatusBarHeight;
@Override
public void onCreate(Context sysuiContext, Context pluginContext) {
Log.d(TAG, "onCreate");
mPluginContext = pluginContext;
}
...
@Override
public void setup(View statusBar, View navBar) {
Log.d(TAG, "Setup");
int id = mPluginContext.getResources().getIdentifier("status_bar_height", "dimen",
"android");
mStatusBarHeight = mPluginContext.getResources().getDimension(id);
if (statusBar instanceof ViewGroup) {
mStatusBarView = LayoutInflater.from(mPluginContext)
.inflate(R.layout.colored_overlay, (ViewGroup) statusBar, false);
((ViewGroup) statusBar).addView(mStatusBarView);
}
if (navBar instanceof ViewGroup) {
mNavBarView = LayoutInflater.from(mPluginContext)
.inflate(R.layout.colored_overlay, (ViewGroup) navBar, false);
((ViewGroup) navBar).addView(mNavBarView);
}
}
...
./frameworks/base/packages/SystemUI/plugin/src/com/android/systemui/plugins/OverlayPlugin.java
@ProvidesInterface(action = OverlayPlugin.ACTION, version = OverlayPlugin.VERSION)
public interface OverlayPlugin extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_OVERLAY";
int VERSION = 4;
/**
* Setup overlay plugin
*/
void setup(View statusBar, View navBar);
/**
* Setup overlay plugin with callback and DozeParameters
*/
default void setup(View statusBar, View navBar, Callback callback,
DozeParameters dozeParameters) {
setup(statusBar, navBar);
}
default boolean holdStatusBarOpen() {
return false;
}
/**
* Only called if the plugin has returned true to holdStatusBarOpen().
*/
default void setCollapseDesired(boolean collapseDesired) {
}
/**
* Used to update system ui whether to hold status bar open
*/
interface Callback {
void onHoldStatusBarOpenChange();
}
}
make ExamplePlugin
编译生成ExamplePlugin.apk,
将ExamplePlugin.apk push 到设备的/system/priv-app/
目录下后reboot,
就能看到ExamplePlugin修改SystemUI的效果.
所有Plugin hooks存放的目录,用于对应的自定义插件实现对应的插件接口,修改不同的systemUI 模块.
frameworks\base\packages\SystemUI\plugin\src\com\android\systemui\plugins
快捷设置面板(Quick Settings, QS)
QSFragment:负责整个快捷设置面板的生命周期管理及其视图层次结构
QSTileHost:管理所有 QSTile 的生命周期和服务注册
QSTile:表示单个快捷设置项,负责处理用户交互和状态更新
TileView:定义了每个快捷设置项的视觉表现形式
./frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
...QS
// Set up the quick settings tile panel 构建QS
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
FragmentHostManager fragmentHostManager =
mFragmentService.getFragmentHostManager(container);
ExtensionFragmentListener.attachExtensonToFragment(
mFragmentService,
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);
}
});
}
...
}
Recents模块
源码版本:Android 13(T)
./frameworks/base/packages/SystemUI/src/com/android/systemui/recents/Recents.java
private final RecentsImplementation mImpl;
@Override
public void toggleRecentApps() {
// Ensure the device has been provisioned before allowing the user to interact with
// recents
if (!isUserSetup()) {
return;
}
mImpl.toggleRecentApps();
}
./frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
//代理OverviewProxyService的Recents接口的实现
public class OverviewProxyRecentsImpl implements RecentsImplementation {
...
private final OverviewProxyService mOverviewProxyService;
@Override
public void toggleRecentApps() {
// If connected to launcher service, let it handle the toggle logic
IOverviewProxy overviewProxy = mOverviewProxyService.getProxy();
if (overviewProxy != null) {
final Runnable toggleRecents = () -> {
try {
if (mOverviewProxyService.getProxy() != null) {
mOverviewProxyService.getProxy().onOverviewToggle();
mOverviewProxyService.notifyToggleRecentApps();
}
} catch (RemoteException e) {
Log.e(TAG, "Cannot send toggle recents through proxy service.", e);
}
};
// Preload only if device for current user is unlocked
final Optional<CentralSurfaces> centralSurfacesOptional =
mCentralSurfacesOptionalLazy.get();
if (centralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing).orElse(false)) {
centralSurfacesOptional.get().executeRunnableDismissingKeyguard(
() -> mHandler.post(toggleRecents), null, true /* dismissShade */,
false /* afterKeyguardGone */,
true /* deferred */);
} else {
toggleRecents.run();
}
}
}
...
}
./frameworks/base/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
./frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
...
import com.android.systemui.shared.recents.IOverviewProxy;
...
public class OverviewProxyService implements CallbackController<OverviewProxyListener>,
NavigationModeController.ModeChangedListener, Dumpable {
private IOverviewProxy mOverviewProxy;
...
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
...
mOverviewProxy = IOverviewProxy.Stub.asInterface(service);
try {
Log.d(TAG_OPS, "OverviewProxyService connected, initializing overview proxy");
mOverviewProxy.onInitialize(params);
} catch (RemoteException e) {
mCurrentBoundedUserId = -1;
Log.e(TAG_OPS, "Failed to call onInitialize()", e);
}
}
...
}
public IOverviewProxy getProxy() {
return mOverviewProxy;
}
...
}
OverviewProxyRecentsImpl::toggleRecentApps
在runnable内部会通过
OverviewProxyService的getProxy()来获取到Launcher3端实现的IOveriewProxy 对象引用,
然后调用onOverviewToggle().
./packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java
...
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
...
/**
* Service connected by system-UI for handling touch interaction.
*/
@TargetApi(Build.VERSION_CODES.R)
public class TouchInteractionService extends Service
implements ProtoTraceable<LauncherTraceProto.Builder> {
private final TISBinder mTISBinder = new TISBinder();
/**
* Local IOverviewProxy implementation with some methods for local components
* 本地IOverviewProxy实现,具有本地组件的一些方法
*/
public class TISBinder extends IOverviewProxy.Stub {
@BinderThread
public void onInitialize(Bundle bundle) {
...
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SYSUI_PROXY));
IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_SHELL_RECENT_TASKS));
...
MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
splitscreen, onehanded, shellTransitions, startingWindow,
recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
unfoldTransition);
TouchInteractionService.this.initInputMonitor("TISBinder#onInitialize()");
preloadOverview(true /* fromInit */);
});
}
@BinderThread
public void onOverviewToggle() {
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
// If currently screen pinning, do not enter overview
if (mDeviceState.isScreenPinningActive()) {
return;
}
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
mOverviewCommandHelper.addCommand(OverviewCommandHelper.TYPE_TOGGLE);
}
}
}
上面函数的@BinderThread
注解,表明该方法将在 Binder 线程池中执行.
这里向 mOverviewCommandHelper 对象发送一个命令,指示其执行指定类型的操作.
./packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
public static final int TYPE_TOGGLE = 4;
@BinderThread
public void addCommand(int type) {
if (mPendingCommands.size() >= MAX_QUEUE_SIZE) {
return;
}
CommandInfo cmd = new CommandInfo(type);
MAIN_EXECUTOR.execute(() -> addCommand(cmd));
}
@UiThread
private void addCommand(CommandInfo cmd) {
boolean wasEmpty = mPendingCommands.isEmpty();
mPendingCommands.add(cmd);
if (wasEmpty) {
executeNext();
}
}
将CommandInfo对象放入mPendingCommands队列中,并在队列之前为空的情况下调用 executeNext() 来处理新添加的命令.
./packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@UiThread
private void executeNext() {
if (mPendingCommands.isEmpty()) {
return;
}
CommandInfo cmd = mPendingCommands.get(0);
if (executeCommand(cmd)) {
scheduleNextTask(cmd);
}
}
private <T extends StatefulActivity<?>> boolean executeCommand(CommandInfo cmd) {
// 检查最近任务列表状态
RecentsView recents = activityInterface.getVisibleRecentsView();
if (cmd.type == TYPE_HOME) {//home 回到主界面
mService.startActivity(mOverviewComponentObserver.getHomeIntent());
return true;
}
...
switch (cmd.type) {
case TYPE_TOGGLE:// 切换最近任务列表的显示状态
return launchTask(recents, getNextTask(recents), cmd);
case TYPE_HOME:
recents.startHome();
return true;
...
if (mTaskAnimationManager.isRecentsAnimationRunning()) {
// 继续执行现有动画
cmd.mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(gestureState);
cmd.mActiveCallbacks.addListener(interactionHandler);
mTaskAnimationManager.notifyRecentsAnimationState(interactionHandler);
interactionHandler.onGestureStarted(true /*isLikelyToStartNewTask*/);
cmd.mActiveCallbacks.addListener(recentAnimListener);
mTaskAnimationManager.notifyRecentsAnimationState(recentAnimListener);
} else {
//启动新的动画
Intent intent = new Intent(interactionHandler.getLaunchIntent());
intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, gestureState.getGestureId());
cmd.mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(
gestureState, intent, interactionHandler);
interactionHandler.onGestureStarted(false /*isLikelyToStartNewTask*/);
cmd.mActiveCallbacks.addListener(recentAnimListener);
}
...
return false;
}
最近任务界面是通过 Intent 进行启动的--interactionHandler.getLaunchIntent()
,
通过Intent来启动RecentsActivity.
./packages/apps/Launcher3/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
AbsSwipeUpHandler::getLaunchIntent() --> GestureState::getOverviewIntent()
./packages/apps/Launcher3/quickstep/src/com/android/quickstep/GestureState.java
public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
...
mOverviewIntent = componentObserver.getOverviewIntent();
...
}
public Intent getOverviewIntent() {
return mOverviewIntent;
}
/packages/apps/Launcher3/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
private final Intent mMyHomeIntent;
private Intent mOverviewIntent;
public OverviewComponentObserver(Context context, RecentsAnimationDeviceState deviceState) {
mContext = context;
...
//home 主界面
mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
ComponentName myHomeComponent =
new ComponentName(context.getPackageName(), info.activityInfo.name);
mMyHomeIntent.setComponent(myHomeComponent);
mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
mSetupWizardPkg = context.getString(R.string.setup_wizard_pkg);
//RecentsActivity跳转
ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
mFallbackIntent = new Intent(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_DEFAULT)
.setComponent(fallbackComponent)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
...
updateOverviewTargets();
}
private void updateOverviewTargets() {
...
if (!mIsHomeDisabled && (defaultHome == null || mIsDefaultHome)) {
// User default home is same as out home app. Use Overview integrated in Launcher.
mActivityInterface = LauncherActivityInterface.INSTANCE;
mIsHomeAndOverviewSame = true;
mOverviewIntent = mMyHomeIntent;
mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
// Remove any update listener as we don't care about other packages.
unregisterOtherHomeAppUpdateReceiver();
} else {
// The default home app is a different launcher. Use the fallback Overview instead.
mActivityInterface = FallbackActivityInterface.INSTANCE;
mIsHomeAndOverviewSame = false;
mOverviewIntent = mFallbackIntent;//重点,将启动RecentsActivity的Intent赋值到mOverviewIntent
mCurrentHomeIntent.setComponent(defaultHome);
...
}
Intent getOverviewIntentIgnoreSysUiState() {
return mIsDefaultHome ? mMyHomeIntent : mOverviewIntent;
}
/**
* Get the current intent for going to the overview activity.
*
* @return the overview intent
*/
public Intent getOverviewIntent() {
return mOverviewIntent;
}
PS:Android注解中@MainThread,@UiThread,@WorkerThread,@BinderThread的区别
线程注解可以检查某个方法是否从特定类型的线程调用,支持以下线程注解:
//注解包
implementation 'androidx.annotation:annotation:1.2.0'
@MainThread: 应用程序启动时运行的第一个线程,表示标记的方法只应在主线程调用.如果标记的是一个类,那么该类中的所有方法都应该是在主线程被调用.
(通常,应用程序的主线程也是 Ui 线程.但是在特殊情况下,应用程序的主线程可能不是其 Ui 线程.)
@MainThread
public void deliverResult(D data) { ... }
@UiThread: 从 MainThread 运行用于 UI 工作,表示标记的方法或构造函数只应该在 Ui 线程上调用.
如果标记的是一个类,那么该类中的所有方法都应是在 Ui 线程被调用.
@UiThread
public abstract void setText(@NonNull String text) {...}
@WorkerThread: 在程序员定义线程时运行,表示标记的方法只应该在工作线程上调用.如果标记的是一个类,那么该类中的所有方法都应是在一个工作线程上调用.
@WorkerThread
protected abstract FilterResults performFiltering(CharSequence constraint);
@BinderThread: 用于 ContentProvider 中的 query()/insert()/update()/delete() 方法,
表示标记的方法只应在绑定线程上调用,如果标记的是一个类,那么该类中的所有方法都应是在绑定线程被调用.
@BinderThread
public BeamShareData createBeamShareData() { ... }
构建工具会将 @MainThread 和 @UiThread 注解视为可以互换,因此,我们可以从 @MainThread 方法调用 @UiThread 方法,反之亦然.
不过如果系统应用在不同线程上带有多个视图(View),Ui 线程可与主线程不同.
所以,我们应该使用 @UiThread 标注于应用的视图层次结构关联的方法.
使用 @MainThread 仅标注于应用生命周期关联的方法.
@Nullable & @NonNull
@Nullable: 注解的元素可以为 null
@NonNull: 注解的元素不可以为 null
.aidl 与 .stub
AIDL[Android Interface Definition Language]-Android接口定义语言,
实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信.
什么是stub?
它是一个存根类,它实现了一个接口,但是实现后的每个方法都是空的.
如果一个接口有很多方法,如果要实现这个接口,就要实现所有的方法.
但是一个类从业务来说,可能只需要其中一两个方法.
如果直接去实现这个接口,除了实现所需的方法,还要实现其他所有的无关方法.
而如果通过继承存根类就实现接口,就免去了这种麻烦.
demo:
//接口类 IRepo.aidl
public interface IRepo{
public void remove(String ... sarr);
public void add(String ... sarr);
//Lots of other methds I don't need now
}
//stub 类
public class Repo extends IRepo.Stub{
@Overread
public void add(String ... sarr){
//to do
}
}
它的产生就是为了跨进程通信(IPC),通过Binder框架来实现.
从Binder通信来看:
stub是service端的中介.
stub类是为了方便client,service交互而生成出来的代码.
交互过程client<-->proxy<-->stub<-->service
,stub和proxy是为了方便client/service交互而生成出来的代码.
如何生成:
IaidlData.aidl会在gen中自动生成一个同名的IaidlData.java接口文件.
该接口文件包含一个抽象类stub,其继承了android.os.Binder、实现IaidlData接口.
我们需要继承IaidlData.Stub类,完成对IaidlData.java文件是的实现.
PS:这里只是很粗略的引申一下,看到Launcher3和SystemUI进行IPC,所以描述一下.跟详细的请看下面链接.
Android Binder--关于AIDL以及Proxy-Stub设计模式-待定
更多看Binder-百度云...
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!