Android 7.0 SystemUI 之启动和状态栏和导航栏简介

4,878 阅读8分钟

PS:已同步至我的博客 点击打开

一、SystemUI 是什么

首先SystemUI 是一个系统应用,apk路径位于/system/priv-app

源码路径位于:/framework/base/packages/SystemUI

它负责的功能如下:

  • 状态栏信息的展示:比如电量信息,时间,wifi状态等
  • 通知栏消息
  • 壁纸管理
  • 截图功能
  • 近期任务栏显示,比如长按home键显示最近使用的app
  • 录制屏幕功能
  • 截图服务

以下是7.0 SystemUI 的代码截图


二、SystemUI 的启动

SystemUI 是在SystemServer里的AMS实例的systemReady方法里调用startSystemUi方法启动

SystemServer路径:/base/services/java/com/android/server/SystemServer.java


 mActivityManagerService.systemReady(new Runnable() {
 ......
 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);
    }
 ......

在这个方法里启动一个SystemUIService服务.

public class SystemUIService extends Service {

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

在onCreate方法中会调用SystemUIApplication的startServicesIfNeeded方法,这个方法会调用 startServicesIfNeeded(SERVICES)方法启动一系列服务(并不是真正的服务,都继承自SystemUI),可以这么说SystemUI就是一个容器,里面装有负责不同功能的模块。

public class SystemUIApplication extends Application {
    ......

    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,
            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
    };

public void startServicesIfNeeded() {
        startServicesIfNeeded(SERVICES);
    }
    ......

startServicesIfNeeded方法会遍历services这个数组,依次调用service的start方法启动服务


private void startServicesIfNeeded(Class<?>[] services) {
        //如果已经启动了就返回
        if (mServicesStarted) {
            return;
        }
        //如果没启动完成完成
        if (!mBootCompleted) {
            if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
                mBootCompleted = true;
                if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
            }
        }

        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].start();

            //如果启动完成了
            if (mBootCompleted) {
                mServices[i].onBootCompleted();
            }
        }
        mServicesStarted = true;
    }

这里以com.android.systemui.statusbar.SystemBars.class为例,讲解一下


三、状态栏和导航栏 的启动

SystemBars的start方法会创建一个ServiceMonitor(服务监听者),会进入到ServiceMonitor的start方法

 public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
 ......
 public void start() {

        //    ServiceMonitor是服务监听者
        mServiceMonitor = new ServiceMonitor(TAG, DEBUG, mContext, Settings.Secure.BAR_SERVICE_COMPONENT, this);
        mServiceMonitor.start();  // will call onNoService if no remote service is found
    }
 ......   
 }

在ServiceMonitor的start方法启动

  public class ServiceMonitor {
  ......    
  public void start() {
        ......
        mHandler.sendEmptyMessage(MSG_START_SERVICE);
    }
  ......    
    }

在Handler里处理这个MSG_START_SERVICE

  public class ServiceMonitor {
  ......    
  private final Handler mHandler = new Handler() {
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case MSG_START_SERVICE:
                    //启动服务
                    startService();
                    break;
                case MSG_CONTINUE_START_SERVICE:
                    continueStartService();
                    break;
                case MSG_STOP_SERVICE:
                    stopService();
                    break;
                case MSG_PACKAGE_INTENT:
                    packageIntent((Intent)msg.obj);
                    break;
                case MSG_CHECK_BOUND:
                    checkBound();
                    break;
                case MSG_SERVICE_DISCONNECTED:
                    serviceDisconnected((ComponentName)msg.obj);
                    break;
            }
        }
    };
    ......
    }

startService方法如下


 public class ServiceMonitor {
  ......    
 private void startService() {
    //获取服务组件名称
    mServiceName = getComponentNameFromSetting();

    //如果为空,回调服务的onNoService方法
    if (mServiceName == null) {
            mBound = false;
            mCallbacks.onNoService();
        } else {
        //不为空,回调服务的的onServiceStartAttempt方法
            long delay = mCallbacks.onServiceStartAttempt();
            mHandler.sendEmptyMessageDelayed(MSG_CONTINUE_START_SERVICE, delay);
        }
    }
    ......
    }

这里对mServiceName是否为空进行判断,总之无论如何它最终都会启动这个服务。

回调SystemBars的onNoService里创建StatusBar

   public class SystemBars extends SystemUI implements ServiceMonitor.Callbacks {
   ......
 @Override
    public void onNoService() {
    if (DEBUG) Log.d(TAG, "onNoService");
     //创建StatusBar
    createStatusBarFromConfig();  // fallback to using an in-process implementation
    }
   ......
  private void createStatusBarFromConfig() {

        //config_statusBarComponent就是PhoneStatusBar
        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 {
            //创建BaseStatusBar实例
            mStatusBar = (BaseStatusBar) cls.newInstance();
        } catch (Throwable t) {
            throw andLog("Error creating status bar component: " + clsName, t);
        }
        mStatusBar.mContext = mContext;
        mStatusBar.mComponents = mComponents;
        //启动
        mStatusBar.start();

    }

在createStatusBarFromConfig方法里会获取一个config_statusBarComponent的字符串值,这个值就是PhoneStatusBar的clasName

所以这里的mStatusBar是PhoneStatusBar实例,启动了PhoneStatusBar

PhoneStatusBar的start方法

public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
        ......

        public void start() {
         ......

        //调用父类的start方法,在父类BaseStatusBar里调用createAndAddWindows方法
        // 3.1
        super.start(); // calls createAndAddWindows()

         ......

        //添加导航栏
        // 3.2
        addNavigationBar();

        ......
    }

它会回调父类BaseStatusBar 的start方法

3.1、 super.start()

public abstract class BaseStatusBar extends SystemUI implements
        CommandQueue.Callbacks, ActivatableNotificationView.OnActivatedListener,
        ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
        ExpandableNotificationRow.OnExpandClickListener,
        OnGutsClosedListener {    

    ......

    public void start() {

    ......

    /*实例化IStatusBarService,
    随后BaseStatusBar将自己注册到IStatusBarService之中。以此声明本实例才是状态栏的真正
        实现者,IStatusBarService会将其所接受到的请求转发给本实例。
    IStatusBarService会保存SystemUi的状态信息,避免SystemUi崩溃而造成信息的丢失
    */
        mBarService = IStatusBarService.Stub.asInterface(
                ServiceManager.getService(Context.STATUS_BAR_SERVICE));

        ......

        //IStatusBarService与BaseStatusBar进行通信的桥梁。
        mCommandQueue = new CommandQueue(this);

     /*switches则存储了一些杂项:禁用功能列表,SystemUIVisiblity,是否在导航栏中显示虚拟的
           菜单键,输入法窗口是否可见、输入法窗口是否消费BACK键、是否接入了实体键盘、实体键盘是否被启用。
          */
        int[] switches = new int[9];

        ArrayList<IBinder> binders = new ArrayList<IBinder>();

          /*它保存了用于显示在状态栏的系统状态
          区中的状态图标列表。在完成注册之后,IStatusBarService将会在其中填充两个数组,一个字符串
          数组用于表示状态的名称,一个StatusBarIcon类型的数组用于存储需要显示的图标资源。
          */

        ArrayList<String> iconSlots = new ArrayList<>();
        ArrayList<StatusBarIcon> icons = new ArrayList<>();

        Rect fullscreenStackBounds = new Rect();
        Rect dockedStackBounds = new Rect();

    //IStatusBarService注册一些信息
    try {
            mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
                    fullscreenStackBounds, dockedStackBounds);
        } catch (RemoteException ex) {

        }

        //创建状态栏窗口
        createAndAddWindows();

        ......
        }
    ......
    }

BaseStatusBar进行一些设置,获取了IStatusBarService实例并注册一些信息到IStatusBarService中,IStatusBarService是一个系统服务,BaseStatusBar将自己注册到IStatusBarService之中,IStatusBarService会把操作状态栏和导航栏的请求转发给BaseStatusBar

为了保证SystemUI意外退出后不会发生信息丢失,IStatusBarService保存了所有需要状态栏与导航栏进行显示或处理的信息副本。 在注册时将一个继承自IStatusBar.Stub的CommandQueue的实例注册到IStatusBarService以建立通信,并将信息副本取回。

 public class CommandQueue extends IStatusBar.Stub {

IStatusBarService的真身是StatusBarManagerService

路径:./services/core/java/com/android/server/statusbar/StatusBarManagerService.java

它的注册方法做一些数据的初始化

public class StatusBarManagerService extends IStatusBarService.Stub {
     ......
 public void registerStatusBar(IStatusBar bar, List<String> iconSlots,
            List<StatusBarIcon> iconList, int switches[], List<IBinder> binders,
            Rect fullscreenStackBounds, Rect dockedStackBounds) {
        //检查权限   
        enforceStatusBarService();

        mBar = bar;

        synchronized (mIcons) {
            for (String slot : mIcons.keySet()) {
                iconSlots.add(slot);
                iconList.add(mIcons.get(slot));
            }
        }
        synchronized (mLock) {
            switches[0] = gatherDisableActionsLocked(mCurrentUserId, 1);
            switches[1] = mSystemUiVisibility;
            switches[2] = mMenuVisible ? 1 : 0;
            switches[3] = mImeWindowVis;
            switches[4] = mImeBackDisposition;
            switches[5] = mShowImeSwitcher ? 1 : 0;
            switches[6] = gatherDisableActionsLocked(mCurrentUserId, 2);
            switches[7] = mFullscreenStackSysUiVisibility;
            switches[8] = mDockedStackSysUiVisibility;
            binders.add(mImeToken);
            fullscreenStackBounds.set(mFullscreenStackBounds);
            dockedStackBounds.set(mDockedStackBounds);
        }
    }
    ......
    }

这几者的关系如下


回到PhoneStatusBar中, 父类BaseStatusBar中的createAndAddWindows为抽象方法,由子类实现,看下PhoneStatusBar的
createAndAddWindows

  @Override
    public void createAndAddWindows() {
        //添加状态栏窗口
        addStatusBarWindow();
    }

方法实现如下

  private void addStatusBarWindow() {
    //创建控件    
        makeStatusBarView();
    //创建StatusBarWindowManager实例    
        mStatusBarWindowManager = new StatusBarWindowManager(mContext);
    //创建远程输入控制实例    
        mRemoteInputController = new RemoteInputController(mStatusBarWindowManager,
                mHeadsUpManager);
    //添加状态栏窗口    
        mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
    }

看下makeStatusBarView方法

makeStatusBarView的方法里调用 inflateStatusBarWindow(context)加载布局

 protected void inflateStatusBarWindow(Context context) {
        mStatusBarWindow = (StatusBarWindowView) View.inflate(context, R.layout.super_status_bar, null);
    }

这里介绍下布局

状态栏布局介绍

整个状态栏的父布局是R.layout.super_status_bar,对应的是StatusBarWindowView这个自定义布局.


<com.android.systemui.statusbar.phone.StatusBarWindowView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sysui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    ......

    <!--正常状态栏下的布局 -->
    <include layout="@layout/status_bar"
        android:layout_width="match_parent"
        android:layout_height="@dimen/status_bar_height" />

    <!--状态栏图标下的SeekBar布局 -->
    <include layout="@layout/brightness_mirror" />

    <!--车载模式的布局  -->
    <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
              android:layout="@layout/car_fullscreen_user_switcher"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>

    <!--状态栏下拉的布局  -->
    <include layout="@layout/status_bar_expanded"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

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

我这里以主要的布局层次做介绍,结合图片分析会更加清楚

StatusBarWindowView里有几个主要的布局

  • layout/status_bar
  • layout/brightness_mirror
  • layout/status_bar_expanded

如下图


1.layout/status_bar

这个是正常状态下(未下拉的状态栏图标区域)

这个布局对应的是PhoneStatusBarView

<com.android.systemui.statusbar.phone.PhoneStatusBarView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
    android:id="@+id/status_bar"
    android:background="@drawable/system_bar_background"
    android:orientation="vertical"
    android:focusable="false"
    android:descendantFocusability="afterDescendants"
    >
    ......
    <!--状态栏  -->
    <LinearLayout android:id="@+id/status_bar_contents"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingStart="6dp"
        android:paddingEnd="8dp"
        android:orientation="horizontal"
        >

        <!-- The alpha of this area is controlled from both PhoneStatusBarTransitions and
             PhoneStatusBar (DISABLE_NOTIFICATION_ICONS). -->
        <!-- 通知图标区域-->
        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
            android:id="@+id/notification_icon_area"
            android:layout_width="0dip"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:orientation="horizontal" />

        <!-- 系统图标区域 -->
        <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal"
            >

            <!-- 系统图标 -->
            <include layout="@layout/system_icons" />

            <!-- 时钟信息 -->
            <com.android.systemui.statusbar.policy.Clock
                android:id="@+id/clock"
                android:textAppearance="@style/TextAppearance.StatusBar.Clock"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:singleLine="true"
                android:paddingStart="@dimen/status_bar_clock_starting_padding"
                android:paddingEnd="@dimen/status_bar_clock_end_padding"
                android:gravity="center_vertical|start"
                />
        </com.android.keyguard.AlphaOptimizedLinearLayout>
    </LinearLayout>

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

以下是细节图,连线表示层次结构


其中,状态栏的区域分为以下几种

  • 通知栏图标,在状态栏的最左侧显示通知信息,比如来了一个短信,那么就会弹出一个短信图标
  • 时间信息,显示一个时间,比如上午9:58
  • 信号图标,显示手机信号,wifi信号等
  • 电量图标,显示当前电量状态
  • 状态图标,wifi,蓝牙等开关状态

2.@layout/brightness_mirror

这个布局就是中间那个调整亮度的seekBar.没啥好介绍的.


3.@layout/status_bar_expanded

这个布局是下拉时的状态栏的布局

<com.android.systemui.statusbar.phone.NotificationPanelView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
    android:id="@+id/notification_panel"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    >
    <!--锁屏时的时钟布局  -->
    <include
        layout="@layout/keyguard_status_view"
        android:layout_height="wrap_content"
        android:visibility="gone" />

    <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="@integer/notification_panel_layout_gravity"
        android:id="@+id/notification_container_parent"
        android:clipToPadding="false"
        android:clipChildren="false">

        <!--quciksetting区域  -->
        <com.android.systemui.AutoReinflateContainer
            android:id="@+id/qs_auto_reinflate_container"
            android:layout="@layout/qs_panel"
            android:layout_width="@dimen/notification_panel_width"
            android:layout_height="match_parent"
            android:layout_gravity="@integer/notification_panel_layout_gravity"
            android:clipToPadding="false"
            android:clipChildren="false" />

        <!-- 通知栏区域 -->
        <com.android.systemui.statusbar.stack.NotificationStackScrollLayout
            android:id="@+id/notification_stack_scroller"
            android:layout_width="@dimen/notification_panel_width"
            android:layout_height="match_parent"
            android:layout_gravity="@integer/notification_panel_layout_gravity"
            android:layout_marginBottom="@dimen/close_handle_underlap" />

        <!--锁屏切换 -->
        <ViewStub
            android:id="@+id/keyguard_user_switcher"
            android:layout="@layout/keyguard_user_switcher"
            android:layout_height="match_parent"
            android:layout_width="match_parent" />

        <!--锁屏下的状态栏 -->
        <include
            layout="@layout/keyguard_status_bar"
            android:visibility="invisible" />

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

    <!--锁屏界面底部的图标 -->
    <include
            layout="@layout/keyguard_bottom_area"
            android:visibility="gone" />


</com.android.systemui.statusbar.phone.NotificationPanelView><!-- end of sliding panel -->

细节图



创建完布局后,就会添加窗口到WindowManager里,这样状态栏就创建完成了.接下来会回到3.2 addNavigationBar()的步骤中.

3.2、addNavigationBar

这个方法是添加底部的导航栏的,就是那些home键,back键所在的区域.


public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
    ......
    protected void addNavigationBar() {

        if (mNavigationBarView == null) return;
        //初始化导航栏
        prepareNavigationBarView();
        //添加到WindowManager中
        mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
    }
    ......
    }

在这个方法里先初始化导航栏,然后把导航栏添加到窗口中.

prepareNavigationBarView()

public class PhoneStatusBar extends BaseStatusBar implements DemoMode, DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,HeadsUpManager.OnHeadsUpChangedListener {
        ......
 private void prepareNavigationBarView() {
        //重新初始化    
        mNavigationBarView.reorient();
        //最近应用键
        ButtonDispatcher recentsButton = mNavigationBarView.getRecentsButton();
        recentsButton.setOnClickListener(mRecentsClickListener);
        recentsButton.setOnTouchListener(mRecentsPreloadOnTouchListener);
        recentsButton.setLongClickable(true);
        recentsButton.setOnLongClickListener(mRecentsLongClickListener);

        //后退键
        ButtonDispatcher backButton = mNavigationBarView.getBackButton();
        backButton.setLongClickable(true);
        backButton.setOnLongClickListener(mLongPressBackListener);

        //home键
        ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
        homeButton.setOnTouchListener(mHomeActionListener);
        homeButton.setOnLongClickListener(mLongPressHomeListener);

        //监听配置改变
        mAssistManager.onConfigurationChanged();
        }
        ......
    }

四、结束

关于SystemUI的状态栏和导航栏就介绍完了,讲的很浅显,只是从整体上梳理了下流程.