企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
尽管SystemUI的表现形式与普通的Android应用程序大相径庭,但它却是以一个APK的形式存在于系统之中,即它与普通的Android应用程序并没有本质上的区别。无非是通过Android四大组件中的Activity、Service、BroadcastReceiver接受外界的请求并执行相关的操作,只不过它们所接受到的请求主要来自各个系统服务而已。 SystemUI包罗万象,并且大部分功能之间相互独立,比如RecentPanel、TakeScreenshotService等均是按需启动,并在完成其既定任务后退出,这与普通的Activity以及Service别无二致。比较特殊的是状态栏、导航栏等组件的启动方式。它们运行于一个称之为SystemUIService的一个Service之中。因此讨论状态栏与导航栏的启动过程其实就是SystemUIService的启动过程。 #### 1.SystemUIService的启动时机 那么SystemUIService在何时由谁启动的呢?作为一个系统级别的UI组件,自然要在系统的启动过程中来寻找答案了。 在负责启动各种系统服务的ServerThread中,当核心系统服务启动完成后ServerThread会通过调用ActivityManagerService.systemReady()方法通知AMS系统已经就绪。这个systemReady()拥有一个名为goingCallback的Runnable实例作为参数。顾名思义,当AMS完成对systemReady()的处理后将会回调这一Runnable的run()方法。而在这一run()方法中可以找到SystemUI的身影: **SystemServer.java-->ServerThread** ``` ActivityManagerService.self().systemReady(newRunnable() { publicvoid run() { // 调用startSystemUi() if(!headless) startSystemUi(contextF); ...... } } ``` 进一步地,在startSystemUI()方法中: **SystemServer.java-->ServerThread.startSystemUi()** ``` static final void startSystemUi(Context context) { Intentintent = new Intent(); // 设置SystemUIService作为启动目标 intent.setComponent(new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService")); // 启动SystemUIService context.startServiceAsUser(intent, UserHandle.OWNER); } ``` 可见,当核心的系统服务启动完毕后,ServerThread通过Context.startServiceAsUser()方法完成了SystemUIService的启动。 #### 2.SystemUIService的创建 参考SystemUIService的onCreate()的实现: **SystemUIService.java-->SystemUIService.onCreate()** ``` /* **①SERVICES数组定义了运行于SystemUIService之中的子服务列表。**当SystemUIService服务启动 时将会依次启动列表中所存储的子服务 */ final Object[] SERVICES = new Object[] { 0,// 0号元素存储的其实是一个字符串资源号,这个字符串资源存储了实现了状态栏/导航栏的类名 com.android.systemui.power.PowerUI.class, com.android.systemui.media.RingtonePlayer.class, }; public void onCreate() { ...... IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); try { /* **② 根据IWindowManager.hasSystemNavBar()的返回值选择一个合适的** ** 状态栏与导航栏的实现** */ SERVICES[0] = wm.hasSystemNavBar() ? R.string.config_systemBarComponent : R.string.config_statusBarComponent; } catch(RemoteException e) {......} finalint N = SERVICES.length; //mServices数组中存储了子服务的实例 mServices = new SystemUI[N]; for (inti=0; i<N; i++) { Class cl = chooseClass(SERVICES[i]); try{ // **③ 实例化子服务并将其存储在mServices数组中** mServices[i] = (SystemUI)cl.newInstance(); }catch (IllegalAccessException ex) {......} // **④ 设置Context,并通过调用其start()方法运行它** mServices[i].mContext = this; mServices[i].start(); } } ``` 除了onCreate()方法之外,SystemUIService没有其他有意义的代码了。显而易见,SystemUIService是一个容器。在其启动时,将会逐个实例化定义在SERVICIES列表中的继承自SystemUI抽象类的子服务。在调用了子服务的start()方法之后,SystemUIService便不再做任何其他的事情,任由各个子服务自行运行。而状态栏导航栏则是这些子服务中的一个。 值得注意的是,onCreate()方法根据IWindowManager.hasSystemNavBar()方法的返回值为状态栏/导航栏选择了不同的实现。进行这一选择的原因为了能够在大尺寸的设备中更有效地利用屏幕空间。在小屏幕设备如手机中,由于屏幕宽度有限,Android采取了状态栏与导航栏分离的布局方案,也就是说导航栏与状态栏占用了更多的垂直空间,使得导航栏的虚拟按键尺寸足够大以及状态栏的信息量足够多。而在大屏幕设备如平板电脑中,由于屏幕宽度比较大,足以在一个屏幕宽度中同时显示足够大的虚拟按键以及足够多的状态栏信息量,此时可以选择将状态栏与导航栏功能集成在一起成为系统栏作为大屏幕下的布局方案,以节省对垂直空间的占用。 hasSystemNavBar()的返回值取决于PhoneWindowManager.mHasSystemNavBar成员的取值。因此在PhoneWindowManager.setInitialDisplaySize()方法中可以得知Android在两种布局方案中进行选择的策略。 **PhoneWindowManager.java-->PhoneWindowManager.setInitialDisplaySize()** ``` public void setInitialDisplaySize(Display display,int width , intheight, int density) { ...... // **① 计算屏幕短边的DP宽度** intshortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density; // **② 屏幕宽度在720dp以内时,使用分离的布局方案** if(shortSizeDp < 600) { mHasSystemNavBar= false; mNavigationBarCanMove = true; } elseif (shortSizeDp < 720) { mHasSystemNavBar = false; mNavigationBarCanMove = false; } ...... } ``` 在SystemUI中,分离布局方案的实现者是PhoneStatusBar,而集成布局方案的实现者则是TabletStatusBar。二者的本质功能是一致的,即提供虚拟按键、显示通知信息等,区别仅在于布局的不同、以及由此所衍生出的定制行为而已。因此不难想到,它们是从同一个父类中继承出来的。这一父类的名字是BaseStatusBar。本章将主要介绍PhoneStatusBar的实现,读者可以类比地对TabletStatusBar进行研究。