说起SystemUI,想必大家是既熟悉又陌生。熟悉是因为我们在使用安卓手机的过程中经常和它打交道,陌生是因为我们不知道哪些系统组件属于SystemUI以及各组件的运作机制是怎么样的。今天阳哥就从SystemUI的启动过程出发,带领大家学习SystemUI。
SystemUI位于/system/priv-app目录下,所以它是一个特权应用,它的AndroidManifest文件如下:
// 本文使用的源码版本为Android N
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
package="com.android.systemui"
android:sharedUserId="android.uid.systemui"
coreApp="true">
......
<application
android:name=".SystemUIApplication"
android:persistent="true"
android:process="com.android.systemui"
......
<service android:name="SystemUIService"
android:exported="true" />
......
</manifest>
从上面的清单文件中,我们可以看到SystemUI使用了共享的 Linux 用户 ID :com.uid.systemui。那么,还有哪个应用也使用了这个共享ID呢?它就是Keyguard,也就是大家常用的锁屏,它也是SystemUI体系的一部分。除此之外,它们还共享着同一个进程,组件默认都运行在com.android.systemui这个进程中。
那么,SystemUI是在什么时候由谁启动的呢?我们知道,Zygote在初始化的时候会启动SystemServer进程,并调用它的main方法来执行一些核心的系统服务(比如:BatteryService、UsageStatsService等)和必要的其他服务(比如:CameraService、WindowManagerService等)的初始化工作。而SystemUI的启动就是在这些必要服务初始化完毕之后触发的:
public final class SystemServer {
......
private void startOtherServices() {
......
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
// where third party code can really run (but before it has actually
// started launching the initial applications), for us to complete our
// initialization.
mActivityManagerService.systemReady(new Runnable() {
@Override
public void run() {
......
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartSystemUI");
try {
startSystemUi(context);
} catch (Throwable e) {
reportWtf("starting System UI", e);
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
......
}
});
}
......
}
可以看到这里通过systemReady方法通知ActivityManagerService(以下简称AMS)是时候执行第三方的代码。
/**
* {@link ActivityManagerService#systemReady}
*/
public void systemReady(final Runnable goingCallback) {
synchronized(this) {
if (mSystemReady) {
// If we're done calling all the receivers, run the next "boot phase" passed in
// by the SystemServer
if (goingCallback != null) {
goingCallback.run();
}
return;
}
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
// Make sure we have the current profile info, since it is needed for security checks.
mUserController.onSystemReady();
mRecentTasks.onSystemReadyLocked();
mAppOpsService.systemReady();
mSystemReady = true;
}
// 回收非persistent进程
ArrayList<ProcessRecord> procsToKill = null;
synchronized(mPidsSelfLocked) {
for (int i=mPidsSelfLocked.size()-1; i>=0; i--) {
ProcessRecord proc = mPidsSelfLocked.valueAt(i);
if (!isAllowedWhileBooting(proc.info)){
if (procsToKill == null) {
procsToKill = new ArrayList<ProcessRecord>();
}
procsToKill.add(proc);
}
}
}
synchronized(this) {
if (procsToKill != null) {
for (int i=procsToKill.size()-1; i>=0; i--) {
ProcessRecord proc = procsToKill.get(i);
Slog.i(TAG, "Removing system update proc: " + proc);
removeProcessLocked(proc, true, false, "system update done");
}
}
// Now that we have cleaned up any update processes, we
// are ready to start launching real processes and know that
// we won't trample on them any more.
mProcessesReady = true;
}
// 解析全局设置
retrieveSettings();
final int currentUserId;
synchronized (this) {
currentUserId = mUserController.getCurrentUserIdLocked();
readGrantedUriPermissionsLocked();
}
if (goingCallback != null) goingCallback.run();
......
}
AMS接收到通知后,在执行Runnable回调之前,会先执行一些准备工作,比如:回收boot过程中启动的非常驻进程,解析全局设置等。我们来看一看retrieveSettings方法做了些什么:
/**
* {@link ActivityManagerService#retrieveSettings}
*/
private void retrieveSettings() {
final ContentResolver resolver = mContext.getContentResolver();
// 检查自由形状模式开关是否打开
final boolean freeformWindowManagement =
mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
|| Settings.Global.getInt(
resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
// 检查画中画模式开关是否打开
final boolean supportsPictureInPicture =
mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
// 检查多窗口模式开关是否打开
final boolean supportsMultiWindow = ActivityManager.supportsMultiWindow();
final String debugApp = Settings.Global.getString(resolver, DEBUG_APP);
final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0;
final boolean alwaysFinishActivities =
Settings.Global.getInt(resolver, ALWAYS_FINISH_ACTIVITIES, 0) != 0;
final boolean lenientBackgroundCheck =
Settings.Global.getInt(resolver, LENIENT_BACKGROUND_CHECK, 0) != 0;
final boolean forceRtl = Settings.Global.getInt(resolver, DEVELOPMENT_FORCE_RTL, 0) != 0;
// 检查是否强制调整应用大小
final boolean forceResizable = Settings.Global.getInt(
resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
final boolean supportsLeanbackOnly =
mContext.getPackageManager().hasSystemFeature(FEATURE_LEANBACK_ONLY);
// Transfer any global setting for forcing RTL layout, into a System Property
SystemProperties.set(DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0");
final Configuration configuration = new Configuration();
Settings.System.getConfiguration(resolver, configuration);
if (forceRtl) {
// This will take care of setting the correct layout direction flags
configuration.setLayoutDirection(configuration.locale);
}
synchronized (this) {
mDebugApp = mOrigDebugApp = debugApp;
mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
mAlwaysFinishActivities = alwaysFinishActivities;
mLenientBackgroundCheck = lenientBackgroundCheck;
mSupportsLeanbackOnly = supportsLeanbackOnly;
mForceResizableActivities = forceResizable;
mWindowManager.setForceResizableTasks(mForceResizableActivities);
if (supportsMultiWindow || forceResizable) {
mSupportsMultiWindow = true;
mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
mSupportsPictureInPicture = supportsPictureInPicture || forceResizable;
} else {
mSupportsMultiWindow = false;
mSupportsFreeformWindowManagement = false;
mSupportsPictureInPicture = false;
}
// This happens before any activities are started, so we can
// change mConfiguration in-place.
updateConfigurationLocked(configuration, null, true);
if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
"Initial config: " + mConfiguration);
// Load resources only after the current configuration has been set.
final Resources res = mContext.getResources();
mHasRecents = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
......
}
}
不难发现,这个函数实际上就是在获取开发者选项中的设置,并将这些设置同步给AMS。在第44行,我么可以看到如果forceResizable开关打开,那么Android手机就同时支持多窗口、自由形状以及画中画三种模式。
回到SystemServer的startOtherServices方法,AMS在执行完相关准备工作后,通过Runnable回调执行SystemUI的初始化工作:
/**
* {@link SystemServer#startSystemUi}
*/
static final void startSystemUi(Context context) {
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);
}
这里实际上就是通过一个显式的Intent来启动SystemUIService组件。而从文章开头的AndroidManifest文件中,可以看到SystemUI有自己的Application实现类:SystemUIApplication。一个应用进程的初始化过程是从Application开始的,熟悉Android应用进程启动过程的读者对此一定不会陌生,所以在com.android.systemui进程初始化时首先会执行SystemUIApplication的相关生命周期方法。
我们先看看SystemUIApplication初始化时都干了些什么。
public class SystemUIApplication extends Application {
private static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
/**
* The classes of the stuff to start.
*/
private final Class<?>[] SERVICES = new Class[] {
// 系统界面调谐器
com.android.systemui.tuner.TunerService.class,
// 锁屏
com.android.systemui.keyguard.KeyguardViewMediator.class,
// 最近任务管理
com.android.systemui.recents.Recents.class,
// 音量调节
com.android.systemui.volume.VolumeUI.class,
// 多窗口
Divider.class,
// 状态栏和导航栏
com.android.systemui.statusbar.SystemBars.class,
// USB 存储通知
com.android.systemui.usb.StorageNotification.class,
// 电源事件
com.android.systemui.power.PowerUI.class,
// 铃声播放
com.android.systemui.media.RingtonePlayer.class,
// 软键盘
com.android.systemui.keyboard.KeyboardUI.class,
// 画中画
com.android.systemui.tv.pip.PipUI.class,
// 快捷键
com.android.systemui.shortcut.ShortcutKeyDispatcher.class
};
/**
* The classes of the stuff to start for each user. This is a subset of the services listed
* above.
*/
private final Class<?>[] SERVICES_PER_USER = new Class[] {
com.android.systemui.recents.Recents.class,
com.android.systemui.tv.pip.PipUI.class
};
/**
* Hold a reference on the stuff we start.
*/
private final SystemUI[] mServices = new SystemUI[SERVICES.length];
private boolean mServicesStarted;
private boolean mBootCompleted;
private final Map<Class<?>, Object> mComponents = new HashMap<>();
@Override
public void onCreate() {
super.onCreate();
// Set the application theme that is inherited by all services. Note that setting the
// application theme in the manifest does only work for activities. Keep this in sync with
// the theme set there.
setTheme(R.style.systemui_theme);
SystemUIFactory.createFromConfig(this);
if (Process.myUserHandle().equals(UserHandle.SYSTEM)) {
IntentFilter filter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mBootCompleted) return;
if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received");
unregisterReceiver(this);
mBootCompleted = true;
if (mServicesStarted) {
final int N = mServices.length;
for (int i = 0; i < N; i++) {
mServices[i].onBootCompleted();
}
}
}
}, filter);
} else {
// For a secondary user, boot-completed will never be called because it has already
// been broadcasted on startup for the primary SystemUI process. Instead, for
// components which require the SystemUI component to be initialized per-user, we
// start those components now for the current non-system user.
startServicesIfNeeded(SERVICES_PER_USER);
}
}
/**
* Makes sure that all the SystemUI services are running. If they are already running, this is a
* no-op. This is needed to conditinally start all the services, as we only need to have it in
* the main process.
*
* <p>This method must only be called from the main thread.</p>
*/
public void startServicesIfNeeded() {
startServicesIfNeeded(SERVICES);
}
/**
* Ensures that all the Secondary user SystemUI services are running. If they are already
* running, this is a no-op. This is needed to conditinally start all the services, as we only
* need to have it in the main process.
*
* <p>This method must only be called from the main thread.</p>
*/
void startSecondaryUserServicesIfNeeded() {
startServicesIfNeeded(SERVICES_PER_USER);
}
private void startServicesIfNeeded(Class<?>[] services) {
if (mServicesStarted) {
return;
}
if (!mBootCompleted) {
// check to see if maybe it was already completed long before we began
// see ActivityManagerService.finishBooting()
if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
mBootCompleted = true;
if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
}
}
Log.v(TAG, "Starting SystemUI services for user " +
Process.myUserHandle().getIdentifier() + ".");
final int N = services.length;
for (int i=0; i<N; i++) {
Class<?> cl = services[i];
if (DEBUG) Log.d(TAG, "loading: " + cl);
try {
Object newService = SystemUIFactory.getInstance().createInstance(cl);
mServices[i] = (SystemUI) ((newService == null) ? cl.newInstance() : newService);
} 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();
if (mBootCompleted) {
mServices[i].onBootCompleted();
}
}
mServicesStarted = true;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mServicesStarted) {
int len = mServices.length;
for (int i = 0; i < len; i++) {
if (mServices[i] != null) {
mServices[i].onConfigurationChanged(newConfig);
}
}
}
}
......
}
可以看到SystemUIApplication在初始化的时候,会依据用户的类型执行不同的操作。针对UserHandle.SYSTEM级用户,会注册BroadcastReceiver监听开机广播,在接收到开机广播之后会通过onBootCompleted接口通知各服务执行各自的操作。而对二级用户来说,则不会监听开机广播,而是直接初始化与用户紧密相关的服务SERVICES_PER_USER(画中画、最近任务)。除去在初始化时判断用户的级别,SystemUIApplication也会监听用户的切换行为,在切换用户时会执行startSecondaryUserServicesIfNeeded方法。有兴趣的读者可以去看UserSwitcherController的具体实现。
那么,SERVICES中的服务是何时启动的呢?前文说到SystemServer通过一个显式的Intent来启动SystemUIService组件。我们来看一看SystemUIService做了什么工作:
public class SystemUIService extends Service {
@Override
public void onCreate() {
super.onCreate();
((SystemUIApplication) getApplication()).startServicesIfNeeded();
}
......
}
没错,SERVICES中各服务的初始化正是由SystemUIService通过startServicesIfNeeded方法来触发的,实际上这也是它的唯一工作。到这里,我们就大概明白了,可以认为SystemUIService就是SystemUI整个应用的初始化入口。
SystemUIApplication除去启动各种服务之外,也执行了一些初始化工作,比如:SystemUIFactory.createFromConfig。
public class SystemUIFactory {
static SystemUIFactory mFactory;
public static SystemUIFactory getInstance() {
return mFactory;
}
public SystemUIFactory() {}
public static void createFromConfig(Context context) {
final String clsName = context.getString(R.string.config_systemUIFactoryComponent);
if (clsName == null || clsName.length() == 0) {
throw new RuntimeException("No SystemUIFactory component configured");
}
try {
Class<?> cls = null;
cls = context.getClassLoader().loadClass(clsName);
mFactory = (SystemUIFactory) cls.newInstance();
} catch (Throwable t) {
Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t);
throw new RuntimeException(t);
}
}
......
}
我们可以看到,createFromConfig方法主要就是从配置文件R.string.config_systemUIFactoryComponent中读取类名,然后通过反射的方式来获取实例并初始化mFactory,具体的类名如下:
<!-- config.xml -->
<resources>
......
<!-- SystemUIFactory component -->
<string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
......
</resources>
可以看到,配置文件中的类名实际上就是SystemUIFactory它自己。你可能会想:不就是个初始化,为啥搞得这么复杂,Android为什么要这样设计呢?我们知道Android是一个跨平台的操作系统,手机、智能手表、电视等等都可以搭载Android操作系统,那么相对应的SystemUIFactory可能就会有不同的实现。所以,为了方便不同平台的定制,才使用了这种方式初始化。
好了,今天的分享就到这里,欢迎大家一起讨论交流!也欢迎大家关注阳哥的公众号:阳哥说技术。