眾所周知SystemUI包含基本的StatusBar、VolumeBar、NavigationBar等部分,在手機開機時就已經為我們加載好,但是有時候會出現對StatusBar,DropList等進行定制化的任務,那么就需要了解SystemUI的啟動流程,了解StatusBar,DropList等view是如何加載在系統界面上,下文是從SystemUI啟動入口、SystemUI的加載機制以及以StatusBar為例來分析整個流程。下圖為SystemUI啟動的整個時序圖:
一、SystemUI的啟動入口
SystemUI的加載是在Android系統啟動的時候,那么我們可以知道SystemUI的入口可能是在系統啟動的流程中。通過調查,發現在SyetemServer進程中開始啟動系統服務,如AMS,PMS,藍牙,窗口管理服務等,其中就包括SystemUI,那么就來看看代碼中是如何實現的。
/frameworks/base/services/java/com/android/server/SystemServer.java
public final class SystemServer {
...
/**
* The main entry point from zygote.
*/
public static void main(String[] args) {
new SystemServer().run();
}
...
private void run() {
...
// Initialize native services.
System.loadLibrary("android_servers");
// Check whether we failed to shut down last time we tried.
// This call may not return.
performPendingShutdown();
// Initialize the system context.
createSystemContext();
// Create the system service manager.
mSystemServiceManager = new SystemServiceManager(mSystemContext);
mSystemServiceManager.setStartInfo(mRuntimeRestart,
mRuntimeStartElapsedTime, mRuntimeStartUptime);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// Prepare the thread pool for init tasks that can be parallelized
SystemServerInitThreadPool.get();
// Start services.
try {
traceBeginAndSlog("StartServices");
startBootstrapServices();
startCoreServices();
startOtherServices();
SystemServerInitThreadPool.shutdown();
} catch (Throwable ex) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
} finally {
traceEnd();
}
...
}
在SystemServer中可以發現啟動SystemServer的是zygote進程,這個不屬于本文范疇先不做探討。在SystemServer的Main函數中,調用了run(),那么跟進到run方法中(上述代碼省略了一部分,只保留主線),首先初始化了native services和system context,接著創建一個SystemServiceManager對象用于后續系統服務的啟動和管理。初始化完成,接下來就開始系統服務的啟動,這里調用了startBootstrapServices()、startCoreServices()、startOtherServices()三個方法,從名字來看,分別是啟動引導service、啟動中心service、啟動其他的services,這三個方法就是開啟不同的系統服務的入口,那就分別進入到三個方法中。
- startBootstrapServices()
private void startBootstrapServices() {
...
Installer installer = mSystemServiceManager.startService(Installer.class);
mSystemServiceManager.startService(DeviceIdentifiersPolicyService.class);
mActivityManagerService = mSystemServiceManager.startService(
ActivityManagerService.Lifecycle.class).getService();
mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
mActivityManagerService.setInstaller(installer);
mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);
mSystemServiceManager.startService(LightsService.class);
mSystemServiceManager.startService(new OverlayManagerService(mSystemContext, installer));
...
}
在startBootstrapServices()方法中,可以發現mSystemServiceManager.startServic()為核心所在,方法中傳入不同的service作為參數,以實現不同services的開啟,包括AMS,PMS,LightsService等系統所需的一些小的關鍵服務;
- startCoreServices()
/**
* Starts some essential services that are not tangled up in the bootstrap process.
*/
private void startCoreServices() {
traceBeginAndSlog("StartBatteryService");
// Tracks the battery level. Requires LightService.
mSystemServiceManager.startService(BatteryService.class);
traceEnd();
// Tracks application usage stats.
traceBeginAndSlog("StartUsageService");
mSystemServiceManager.startService(UsageStatsService.class);
mActivityManagerService.setUsageStatsManager(
LocalServices.getService(UsageStatsManagerInternal.class));
traceEnd();
// Tracks whether the updatable WebView is in a ready state and watches for update installs.
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
traceBeginAndSlog("StartWebViewUpdateService");
mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
traceEnd();
}
...
}
同樣的,代碼中以mSystemServiceManager.startService()來開啟服務,只不過里面的參數不同,就不做詳細的探討,繼續看第三個方法;
- startOtherServices()
private void startOtherServices() {
...
traceBeginAndSlog("StartSystemUI");
try {
startSystemUi(context, windowManagerF);
} catch (Throwable e) {
reportWtf("starting System UI", e);
}
}
在第三個startOtherServices()方法中,除掉開啟一些系統所需的服務外,最主要的核心在于startSystemUi()方法,里面傳入systemContext和WindowManagerService兩個參數,也就是說我們已經找到systemUI啟動的入口,那么就繼續進入到startSystemUi()方法中。
- startSystemUi()
static final void startSystemUi(Context context, WindowManagerService windowManager) {
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
//Slog.d(TAG, "Starting service: " + intent);
context.startServiceAsUser(intent, UserHandle.SYSTEM);
windowManager.onSystemUiStarted();
}
在上面代碼中可以看見創建了一個Intent,然后通過設置組件名稱來開啟SystemUIService,至此,SystemUI才只是找到啟動的入口,對于系統啟動完全完成,需要進入到SystemUIService中查看詳細的啟動流程。
二、SystemUI開始加載
第一部分找到了SystemUIService的啟動,那么就先進入到SystemUIService類中。如下所示:
/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemUIService.java
public class SystemUIService extends Service {
@Override
public void onCreate() {
super.onCreate();
((SystemUIApplication) getApplication()).startServicesIfNeeded();
...
}
}
在onCreate()中獲取了SystemUIApplication并且調用了它的startServicesIfNeeded()方法,那么接著就進入到SystemUIApplication類中,在SystemUIApplication類中找到startServicesIfNeeded方法,如下。
- startServicesIfNeeded()
public class SystemUIApplication extends Application implements SysUiServiceProvider {
...
public void startServicesIfNeeded() {
String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
startServicesIfNeeded(names);
}
...
}
startServicesIfNeeded()方法中,首先創建了一個包含services的名字的數組,接著將獲取的數組作為參數調用startServicesIfNeeded(String[] services)方法,這里先不看這個方法內部的構造,先來看看上面數組獲取的都有哪些services,根據代碼中提供的id R.array.config_systemUIServiceComponents我們在xml中找到config.xml這個文件,其中發現了一些數據,如下:
/frameworks/base/packages/SystemUI/res/values/config.xml
<string-array name="config_systemUIServiceComponents" translatable="false">
<item>com.android.systemui.Dependency</item>
<item>com.android.systemui.util.NotificationChannels</item>
<item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
<item>com.android.systemui.keyguard.KeyguardViewMediator</item>
<item>com.android.systemui.recents.Recents</item>
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.stackdivider.Divider</item>
<item>com.android.systemui.SystemBars</item>
<item>com.android.systemui.usb.StorageNotification</item>
<item>com.android.systemui.power.PowerUI</item>
<item>com.android.systemui.media.RingtonePlayer</item>
<item>com.android.systemui.keyboard.KeyboardUI</item>
<item>com.android.systemui.pip.PipUI</item>
<item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
<item>@string/config_systemUIVendorServiceComponent</item>
<item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
<item>com.android.systemui.LatencyTester</item>
<item>com.android.systemui.globalactions.GlobalActionsComponent</item>
<item>com.android.systemui.ScreenDecorations</item>
<item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
</string-array>
在上面這些item中,可以發現都是一些我們所熟悉的類,例如VolumeUI、SystemBars、PowerUI、KeyboardUI等,也就是我們手機界面常看見的系統音量,鎖屏,狀態欄等,而這些UI正是SystemUI的構造部分。
在startServicesIfNeeded()方法中先將這些小部件集合在一起,然后調用startServicesIfNeeded(String[] services),那么我們可以猜測接下來是不是就要開始分別加載這些小部件并且將他們放置在相應的位置上。
- startServicesIfNeeded(String[] services);
private void startServicesIfNeeded(String[] services) {
private SystemUI[] mServices;
...
mServices = new SystemUI[services.length];
...
final int N = services.length;
for (int i = 0; i < N; i++) {
String clsName = services[i];
Class cls;
try {
cls = Class.forName(clsName);
mServices[i] = (SystemUI) cls.newInstance();
} catch(ClassNotFoundException ex){
throw new RuntimeException(ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
}
mServices[i].mContext = this;
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
...
}
}
首先創建了一個SystemUI數組,這個是用來裝載systemUI上各個小部件,接著遍歷了在startServicesIfNeeded()方法中獲取的services數組,通過反射的方式,獲取各個不同的systemUI的對象,最后分別調用他們的start()方法。例如循環第六次獲取到的是VolumeUI的對象,最后便調用的是VolumeUI的start()方法。
到這里,正如上面的猜測,SystemUI開始加載不同位置的UI,而每個UI內部是如何加載,如何將view放置在不同的位置上的,我們繼續往下看。
三、StatusBar的加載與定制化
由于SystemUI所包含的部分很多,這里就以加載狀態欄StatusBar為例。上述遍歷獲取到了SystemBar對象,并開始調用它的start(),那么我們就進入到SystemBar類中查看它的start()。
/frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java
public class SystemBars extends SystemUI {
...
@Override
public void start() {
if (DEBUG) Log.d(TAG, "start");
createStatusBarFromConfig();
}
...
private void createStatusBarFromConfig() {
final String clsName = mContext.getString(R.string.config_statusBarComponent);
Class<?> cls = null;
try {
cls = mContext.getClassLoader().loadClass(clsName);
} catch (Throwable t) {
throw andLog("Error loading status bar component: " + clsName, t);
}
try {
mStatusBar = (SystemUI) cls.newInstance();
} catch (Throwable t) {
throw andLog("Error creating status bar component: " + clsName, t);
}
mStatusBar.mContext = mContext;
mStatusBar.mComponents = mComponents;
mStatusBar.start();
if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
}
}
start方法中調用了createStatusBarFromConfig(),接著進入到createStatusBarFromConfig中,在這里,第一眼感覺代碼有點熟悉,回想一下和上面SystemUIApplication類中startServicesIfNeeded(String[] services)加載不同systemUI的方法很像,都是使用了反射的手法,同樣的首先通過id 查找到config.xml文件里的name,如下:
/frameworks/base/packages/SystemUI/res/values/config.xml
<string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.StatusBar</string>
從這個string name可以發現最后啟動的是StatusBar,也就是調用StatusBar的start()方法。
public class StatusBar extends SystemUI{
@Override
public void start() {
...
//第一步
createAndAddWindows();
...
}
public void createAndAddWindows() {
//第二步
addStatusBarWindow();
}
...
private void addStatusBarWindow() {
//第三步
makeStatusBarView();
mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
...
//第五步
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
}
...
protected void makeStatusBarView() {
final Context context = mContext;
...
//第四步
inflateStatusBarWindow(context);
}
...
//加載Layout,初始化StatusBarWindow
protected void inflateStatusBarWindow(Context context) {
mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
R.layout.super_status_bar, null);
}
}
在StatusBar類中省略了大量代碼,只保留了StatusBar加載的主要流程,從Start()方法中調用createAndAddWindows()接著再調用addStatusBarWindow(),緊接著在makeStatusBarView()方法中通過inflate加載layout的方式初始化StatusBarWindow,將super_status_bar.xml里所設計的樣式加載到StatusBar界面上。
也就是說,當我們碰到需要定制化SystemUI的情況下,可以自己自定義一個layout.xml,然后在這里替換掉源文件。到了第5步是在初始化StatusBarWindow后,通過StatusBarWindowManager的add()方法,將statusBarView加載到系統界面上,并設置了statusBar的高度。那么我們就進入StatusBarWindowManager類的add()方法,如下所示:
public class StatusBarWindowManager{
...
/**
* Adds the status bar view to the window manager.
*
* @param statusBarView The view to add.
* @param barHeight The height of the status bar in collapsed state.
*/
public void add(View statusBarView, int barHeight) {
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
mLp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
barHeight,
WindowManager.LayoutParams.TYPE_STATUS_BAR,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT);
mLp.token = new Binder();
mLp.gravity = Gravity.TOP;
mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
mLp.setTitle("StatusBar");
mLp.packageName = mContext.getPackageName();
mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
mStatusBarView = statusBarView;
mBarHeight = barHeight;
mWindowManager.addView(mStatusBarView, mLp);
mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
}
}
可以發現StatusBar的加載真正在于WindowManager的處理,先設置好WindowManager.LayoutParams的寬高,層級TYPE,Flag等參數,然后將設置好的LayoutParams和上面傳進來的mStatusBarView作為參數,調用addView()方法使View加載到相應的位置上。那么我們反過來思考下,如果將mStatusBarView換成我們自定義的View,那么結果會是什么樣?
至此,StatusBar在SystemUI上的加載也就結束了,同樣的道理,VolumeBar,NavigationBar等SystemUI其他部分的加載也和StatusBar的加載基本一致,這里就不再做分析。理清了上述代碼,再回到文章開頭看那張時序圖,就可以清楚的知道SystemUI的啟動流程,在此基礎上,對SystemUI的定制化任務也就變得明朗起來。
參考資料: