SystemUI启动流程及主体布局介绍

本文将基于Android 6.0代码,分析systemUI的启动加载流程,对systemUI几处关键的视图的布局及功能进行介绍。

一. SystemUI主体框架启动流程

android设备上电,引导程序引导进入boot(通常是uboot),加载initramfs、kernel镜像,启动kernel后,进入用户态程序。第一个用户空间程序是init, PID固定是1.
init的基本功能有:

  • 管理设备
  • 解析并处理Android启动脚本init.rc
  • 实时维护这个init.rc中的服务,包括加载 Zygote

而在Zygote中将启动SystemServer组件。

本文将从SystemServer开始分析。
SystemServer 名为系统服务进程,负责启动 Android 系统的关键服务。
其入口是SystemServer.main():

/**
     * The main entry point from zygote.
     */
    public static void main(String[] args) {
        new SystemServer().run();
    }

可以看到main()中生成了SystemServer对象并执行了run方法。
SystemServer.run():

private void run() {
    ......
    
     // Start services.
        try {
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
            ......
       } catch (Throwable ex) {
           ...
            throw ex;
        }
        ...
        // Loop forever.
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
}

先看一眼startBootstrapServices();

 private void startBootstrapServices() {
        ......
        Installer installer = mSystemServiceManager.startService(Installer.class);

        // Activity manager runs the show.
        mActivityManagerService = mSystemServiceManager.startService(
                ActivityManagerService.Lifecycle.class).getService();
        mActivityManagerService.setSystemServiceManager(mSystemServiceManager);
        ......
 }

在startBootstrapServices()中启动了mActivityManagerService。
随后,我们再回头去看startOtherServices():

private void startOtherServices() {
        final Context context = mSystemContext;
        AccountManagerService accountManager = null;
        ContentService contentService = null;
        .......
        
         mActivityManagerService.systemReady(new Runnable() {
            @Override
            public void run() {
              ......

                try {
                    startSystemUi(context);
                } catch (Throwable e) {
                    reportWtf("starting System UI", e);
                }
         .......

mActivityManagerService.systemReady创建线程去执行startSystemUi(context),从方法名称可以看出,这里将启动systemUI。

static final void startSystemUi(Context context) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.android.systemui",
                    "com.android.systemui.SystemUIService"));
        //Slog.d(TAG, "Starting service: " + intent);
        context.startServiceAsUser(intent, UserHandle.OWNER);
    }

通过intent.setComponent(new ComponentName("com.android.systemui",
"com.android.systemui.SystemUIService"));
设置启动systemui程序的SystemUIService
进入SystemUIService:

public class SystemUIService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
    }
......

onCreate方法中获得SystemUIApplication对象并调用其startServicesIfNeeded方法:

 public void startServicesIfNeeded() {
        final int N = SERVICES.length;
        for (int i=0; i<N; i++) {
            Class<?> cl = SERVICES[i];
            try {
                mServices[i] = (SystemUI)cl.newInstance();//加载实例
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }
            mServices[i].mContext = this;
            mServices[i].mComponents = mComponents;
            mServices[i].start();//start服务
            if (mBootCompleted) {
                mServices[i].onBootCompleted();
            }
        }
        mServicesStarted = true;
    }

可以看到startServicesIfNeeded()循环start了很多Services。
数组SERVICES的定义如下:

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,//全局音量控制
            com.android.systemui.statusbar.SystemBars.class,//系统状态栏
            com.android.systemui.usb.StorageNotification.class,//Storage存储通知
            com.android.systemui.power.PowerUI.class,//电量管理相关
            com.android.systemui.media.RingtonePlayer.class,//铃声播放
            com.android.systemui.keyboard.KeyboardUI.class,//键盘相关
};

可以看到子服务包括:TunerService,KeyguardViewMediator,Recents,VolumeUI,SystemBars,StorageNotification,PowerUI
RingtonePlayer,KeyboardUI。
不过这里的service与我们平时所讲的四大组件的service并不一样。这里的service只是继承了SystemUI类的普通对象而已。
例如SystemBars:

public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
           ......
}

小结:

查看到这里,可以总结一下systemUI主体框架的启动流程:
SystemServer启动Android核心服务包括了ActivityManagerService
--->ActivityManagerService一旦启动完成就会在systemReady的回调里启动SystemUIService
--->SystemUIService.onCreate—--->SystemUIApplication.startServicesIfNeeded
--->循环中调用mServices[i].start()启动SystemUI的各种核心service。

二. SystemUI关键视图呈现

手机中的下拉状态栏,锁屏,通知以及最近打开任务列表等功能都是SystemUI实现的。
主要功能点对应的界面如下图所示:



在上文中我们看到了SystemUI各项服务的启动流程,那么服务有了,界面视图又是如何呈现的呢?
接下来将以SystemBars为例,它是SystemUI的主要视图。
上文中mServices[i].start()将调用SystemBars.start():

    @Override
    public void start() {
        if (DEBUG) Log.d(TAG, "start");
        mServiceMonitor = new ServiceMonitor(TAG, DEBUG,
                mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this);
        mServiceMonitor.start();  // will call onNoService if no remote service is found
    }

start中创建ServiceMonitor实例并start();
注释中说明,/服务没启动时,ServiceMonitor会回调SystemBars的onNoService/
所以去看SystemBars的onNoService:

    @Override
    public void onNoService() {
        if (DEBUG) Log.d(TAG, "onNoService");
        createStatusBarFromConfig();  // fallback to using an in-process implementation
    }
    
    private void createStatusBarFromConfig() {
        final String clsName = mContext.getString(R.string.config_statusBarComponent);
        if (clsName == null || clsName.length() == 0) {
            throw andLog("No status bar component configured", null);
        }
        Class<?> cls = null;
        try {
            cls = mContext.getClassLoader().loadClass(clsName);
        } catch (Throwable t) {
            throw andLog("Error loading status bar component: " + clsName, t);
        }
        try {
            mStatusBar = (BaseStatusBar) cls.newInstance();
        } catch (Throwable t) {
            throw andLog("Error creating status bar component: " + clsName, t);
        }
        mStatusBar.mContext = mContext;
        mStatusBar.mComponents = mComponents;
        mStatusBar.start();
    }

需要注意的是,clsName得到的string为com.android.systemui.statusbar.phone.PhoneStatusBar
执行SystemBars.start()后,通过反射机制,最终得到BaseStatusBar对象。
这里需要说明的是BaseStatusBar是PhoneStatusBar的父类。
上文mStatusBar.start()即为PhoneStatusBar.start():

    public void start() {
            ......
            super.start(); // calls createAndAddWindows()
            ......
    }

再去BaseStatusBar.start():

public void start() {
        mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
        mDisplay = mWindowManager.getDefaultDisplay();
        mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
                Context.DEVICE_POLICY_SERVICE);

        mNotificationColorUtil = NotificationColorUtil.getInstance(mContext);

        mNotificationData = new NotificationData(this);

        mAccessibilityManager = (AccessibilityManager)
                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);

        mDreamManager = IDreamManager.Stub.asInterface(
                ServiceManager.checkService(DreamService.DREAM_SERVICE));
        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);

        .......
        //在这里实例化了许多systemui常用的对象,服务,Manager,Observer等等
        .......
        
        createAndAddWindows(); //创建并添加视图

查看createAndAddWindows():

 protected abstract void createAndAddWindows();

是个抽象方法,很显然调用去了BaseStatusBar的子类,
即PhoneStatusBar的createAndAddWindows():

 @Override
 public void createAndAddWindows() {
        addStatusBarWindow();
 }
 
 private void addStatusBarWindow() {
        makeStatusBarView();//关键方法,创建StatusBarView
        mStatusBarWindowManager = new StatusBarWindowManager(mContext);
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
 }

重点来了,makeStatusBarView,创建StatusBarView,
随后,mStatusBarWindowManager将其添加,呈现给用户
makeStatusBarView()的代码很长,但主要代码是这一句:

protected PhoneStatusBarView makeStatusBarView()
{
    ......
    mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
                R.layout.super_status_bar, null);
    ......
}

通过认识super_status_bar.xml就能认识SystemBars的大体构成。
下面是部分省略的super_status_bar文件

<!-- This is the combined status bar / notification panel window. -->
<com.android.systemui.statusbar.phone.StatusBarWindowView
    android:fitsSystemWindows="true">
    
    <com.android.systemui.statusbar.BackDropView
            android:id="@+id/backdrop"
            sysui:ignoreRightInset="true">
    </com.android.systemui.statusbar.BackDropView>
    
    <com.android.systemui.statusbar.ScrimView 
        android:id="@+id/scrim_behind"
        android:importantForAccessibility="no"/>

    <com.android.systemui.statusbar.AlphaOptimizedView
        android:id="@+id/heads_up_scrim"
        android:importantForAccessibility="no"/>

    <include layout="@layout/status_bar"
        android:layout_width="match_parent"

    <FrameLayout android:id="@+id/brightness_mirror">
        <FrameLayout
                android:background="@drawable/brightness_mirror_background">
            <include layout="@layout/quick_settings_brightness_dialog"
                     android:layout_height="wrap_content" />
        </FrameLayout>
    </FrameLayout>

    <com.android.systemui.statusbar.phone.PanelHolder
        android:id="@+id/panel_holder"
        <include layout="@layout/status_bar_expanded"
            android:visibility="gone" />
    </com.android.systemui.statusbar.phone.PanelHolder>

    <com.android.systemui.statusbar.ScrimView android:id="@+id/scrim_in_front"
        android:importantForAccessibility="no"
        sysui:ignoreRightInset="true"
        />

</com.android.systemui.statusbar.phone.StatusBarWindowView>

通过上述view布局及makeStatusBarView相关代码,可以发现mStatusBarWindow(StatusBarWindowView)中包含以下4个部分:

  • ScrimView
  • PhoneStatusBarView layout-> status_bar
  • PanelHolder id->PanelHolder
  • ScrimView

其实这里还漏掉了一个重要的view—-keyguard_bouncer,它不是直接在layout布局里加入的,只有用户设置锁屏保护后才可见。

StatusBarWindowView

这里主要查看一下PhoneStatusBarView,PanelHolder及KeyguardBouncer。

PhoneStatusBarView
PhoneStatusBarView即为手机最上方的状态栏,主要用于显示系统状态,通知等,主要包括 notification icons 和 status bar icons

PhoneStatusBarView

PanelHolder
PanelHolder是用户下拉 status bar 后得到的 view。它主要包含 QuickSettings 和 Notification panel 两个部分。
PanelHolder是一个继承自FrameLayout的自定义view,它的内容是通过include status_bar_expanded.xml进行填充的。
PanelHolder的布局比较复杂,为了提高view的重用性大量的使用了include标签。

PanelHolder

在平时修改QuickSettings以及Notification panel界面布局及功能相关的文件分别QSPanel及NotificationPanelView。

KeyguardBouncer
KeyguardBouncer是锁屏解锁界面,根据用户设置的解锁方式不同,展示不同的解锁模式。
先看看KeyguardBouncer长什么样子:

锁屏界面:

Notification Keyguard

上滑锁屏后:

KeyguardBouncer

需要注意的是KeyguardBouncer有多种形式,上图中展示的是图案解锁,若为密码解锁KeyguardBouncer将会以数字键盘的形式展示。但无论哪种形式,都是在KeyguardBouncer中加载进来的。

public class KeyguardBouncer { 
private ViewGroup mRoot; 
private ViewGroup mContainer; 
private KeyguardHostView mKeyguardView;
private void inflateView() {        
       mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
       mKeyguardView =(KeyguardHostView)mRoot.findViewById(R.id.keyguard_host_view);
       mKeyguardView.setLockPatternUtils(mLockPatternUtils);
       mKeyguardView.setViewMediatorCallback(mCallback); 
       mContainer.addView(mRoot,mContainer.getChildCount());
}

KeyguardBouncer的View树如下:

KeyguardBouncer

小结:

通过分析SystemBars的呈现流程介绍了PhoneStatusBarView,PanelHolder,keyguardbouncer三个常见SystemUI的界面布局及相关功能。当然,SystemUI的布局还是很复杂的,上述只对主要的视图从大的方向上做了分析。SystemUI也不只以上几个布局,包括最近应用视图,底部导航栏(Navigation Bar),全局音量管理Dialog等,本文暂时未介绍到。

总结:

本文分为两部分,分别介绍了SystemUI主体框架启动流程及SystemUI关键视图的界面布局呈现过程。通过以上介绍,在以后遇到SystemUI相关问题时,可以先定位出问题View属于哪个大的分类,然后结合图例给出的id缩小定位范围。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容