SystemUI总结

SystemUI概览

SystemUI属于系统级的apk,位置在frameworks\base\packages\SystemUI,主要功能有:

  • 状态栏信息显示,比如电池,wifi信号,3G/4G等icon显示
  • 通知面板,比如系统消息,第三方应用消息
  • 近期任务栏显示面板,比如长按近期任务快捷键,显示近期使用的应用
  • 截图服务
  • 壁纸服务
  • ……

SystemUI的启动流程

SystemServer启动后,会在Main Thread启动ActivityManagerService,当ActivityManagerService systemReady后,会去启动SystemUIService。
SystemServer路径:frameworks/base/services/java/com/android/server/SystemServer.java

mActivityManagerService.systemReady(new Runnable() {
        @Override
        public void run() {
            Slog.i(TAG, "Making services ready");
            ......
            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);
            ......
        }            
     });

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

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

通过startServiceAsUser,SystemUIService就启动了,即SystemUI进程开机启动。

public class SystemUIService extends Service {

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

在SystemUIService的onCreate方法中会调用SystemUIApplication的startServicesIfNeeded方法,这个方法会调用 startServicesIfNeeded(SERVICES)方法启动一系列服务(并不是真正的service,都继承自SystemUI)

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

    /**
     * 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,
            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,
            com.android.systemui.VendorServices.class
    };

    ......

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

所有SERVICES统一继承了SystemUI类:

public abstract class SystemUI {
    ......

    public abstract void start();

    protected void onConfigurationChanged(Configuration newConfig) {
    }

    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    }

    protected void onBootCompleted() {
    }
    
    ......
}

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

private void startServicesIfNeeded(Class<?>[] services) {
    ......

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

状态栏

状态栏(SystemBars)service是SystemUI中最重要的service,代码量最多,最复杂的,界面结构也复杂。根据前面的内容可知,启动SystemBars是通过调用start()方法,如下图:

    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
    }

这里实质是回调到到SystemBars的onNoService()方法,最后是调用SystemBars的createStatusBarFromConfig()方法

    private void createStatusBarFromConfig() {
        ......
        String clsName = mContext.getString(R.string.config_statusBarComponent);
        ......
        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.start();
        ......
    }

上图可以看到,从string资源文件里面读取class name,通过java的映射机制实例化对象,然后调用start()方法启动,class name的值如下图:

    <!-- Component to be used as the status bar service.  Must implement the IStatusBar
     interface.  This name is in the ComponentName flattened format (package/class)  -->
    <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.PhoneStatusBar</string>

该配置文件在SystemUI/res/values/config.xml中。所以实质是PhoneStatusBar调用了start()方法。
SystemBars模块的初始化过程主要涉及的类有:

SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
SystemUI/src/com/android/systemui/statusbar/CommandQueue.java

PhoneStatusBar的父类是BaseStatusBar继承于SystemUI,SystemBars调用PhoneStatusBar中的start()方法,类关系图如下:

StatusBarManager.png

PhoneStatusBar的start()

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

        ......
        addNavigationBar();

        ......
}

如上图,调用父类中的start()方法,即BaseStatsuBar中的start()方法。然后调用addNavigationBar()方法实例化导航条。状态栏的布局层次结构如下图:

PhoneStatusBar.png

继续看BaseStatsuBar中的方法。

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

    mBarService = IStatusBarService.Stub.asInterface(
            ServiceManager.getService(Context.STATUS_BAR_SERVICE));

    ......

    // Connect in to the status bar manager service
    mCommandQueue = new CommandQueue(this);

    int[] switches = new int[9];
    ArrayList<IBinder> binders = new ArrayList<IBinder>();
    ArrayList<String> iconSlots = new ArrayList<>();
    ArrayList<StatusBarIcon> icons = new ArrayList<>();
    Rect fullscreenStackBounds = new Rect();
    Rect dockedStackBounds = new Rect();
    try {
        mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
                fullscreenStackBounds, dockedStackBounds);
    } catch (RemoteException ex) {
        // If the system process isn't there we're doomed anyway.
    }

    createAndAddWindows();

    ......

    // Set up the initial icon state
    int N = iconSlots.size();
    int viewIndex = 0;
    for (int i=0; i < N; i++) {
        setIcon(iconSlots.get(i), icons.get(i));
    }

    // Set up the initial notification state.
    try {
        mNotificationListener.registerAsSystemService(mContext,
                new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
                UserHandle.USER_ALL);
    } catch (RemoteException e) {
        Log.e(TAG, "Unable to register notification listener", e);
    }

    ......
}

如上面BaseStatsuBar中的start()方法,实例化一些对象,此处的对象都是“空值”,然后通过IStatusBarService的实例mBarService对象注册到StatusBarManagerService。

mCommandQueue是CommandQueue的实例,在StatusBarManagerService的远程回调,实现StatusBarManagerService和SystemUI的通信。

然后调用createAndAddWindows()方法,该方法初始化status bar,notification,quick settings等的View控件。

在这里,还需要注意NotificationListenerService的实例mNotificationListener的registerAsSystemService()方法,该方法主要实现StatusBarManagerService和SystemUI的notification的控制通道,也就是说,StatusBarManagerService收到notification变化时,通过此通道通知SystemUI显示notification的变化。

通知显示过程

一个APP需要显示notification首先需要实例化一个NotificationManager的对象,然后调用NotificationManager的方法notify()方法把创建好的Notification对象作为参数传进去。

public void notify(int id, Notification notification){
    notify(null, id, notification);
}
public void notify(String tag, int id, Notification notification){
    notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user){
    ......
    INotificationManager service = getService();
    ......
    try {
        service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                copy, idOut, user.getIdentifier());
        ......
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

上图中可以看到一个service的对象调用了enqueueNotificationWithTag()方法,该方法实质是远程调用NotificationManagerService中的enqueueNotificationWithTag()方法,该方法又直接调用enqueueNotificationInternal(),该方法如下:

void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
        final int callingPid, final String tag, final int id, final Notification notification,
        int[] idOut, int incomingUserId) {
    ......
    final StatusBarNotification n = new StatusBarNotification(
            pkg, opPkg, id, tag, callingUid, callingPid, 0, notification,
            user);

    ......

    final NotificationRecord r = new NotificationRecord(getContext(), n);
    mHandler.post(new EnqueueNotificationRunnable(userId, r));

    ......
}

这里会把NotificationManager传递过来的Notification对象进行很多处理,比如变换成NotificationRecord,实质就是把Notification缓存下来。在上图的这个过程,还有一些其它的处理逻辑,在这里就不详细说明。最后把这个NotificationRecord传递给EnqueueNotificationRunnable线程来处理:

    private class EnqueueNotificationRunnable implements Runnable {
        private final NotificationRecord r;
        private final int userId;

        EnqueueNotificationRunnable(int userId, NotificationRecord r) {
            this.userId = userId;
            this.r = r;
        };

        @Override
        public void run() {

            synchronized (mNotificationList) {
                final StatusBarNotification n = r.sbn;
                ......

                if (notification.getSmallIcon() != null) {
                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                    mListeners.notifyPostedLocked(n, oldSbn);
                } else {
                    ......
                }

                buzzBeepBlinkLocked(r);
            }
        }
    }

代码的末尾调用了buzzBeepBlinkLocked()方法,该方法主要处理Notification的声音和震动的逻辑。mListeners调用了notifyPostedLocked()方法,此方法最终会执行到如下图的代码:

private void notifyPosted(final ManagedServiceInfo info,
        final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
    final INotificationListener listener = (INotificationListener)info.service;
    StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
    try {
        listener.onNotificationPosted(sbnHolder, rankingUpdate);
    } catch (RemoteException ex) {
        Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
    }
}

info.service返回一个INotificationListener的实例对象,该对象在上文中的mNotificationListener.registerAsSystemService()方法进行设置,所以listener.onNotificationPosted()方法实质是远程回调SystemUI中的方法:

private final NotificationListenerService mNotificationListener =
        new NotificationListenerService() {
    ......

    @Override
    public void onNotificationPosted(final StatusBarNotification sbn,
            final RankingMap rankingMap) {
        ......
        if (sbn != null) {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    ......

                    if (isUpdate) {
                        updateNotification(sbn, rankingMap);
                    } else {
                        addNotification(sbn, rankingMap, null /* oldEntry */);
                    }
                }
            });
        }
    }
}

代码运行又回到了BaseStatusBar.java类中,从APP调用NotificationManager的notify()方法到BaseStatusBar的addNotification()或updateNotification()方法,经历了一个复杂的过程。就不再往下详情说明Notification到达SystemUI的处理过程了,感兴趣可自行阅读代码。

锁屏

锁屏(Keyguard)service在SystemUI是一个比较特殊的模块,特殊在于SystemUI启动的service只是一个信息传递者,也就是KeyguardViewMediator,并没有做锁屏或解屏的实质操作。在这里,涉及到三个比较关键的类是:

SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java

KeyguardViewMediator和KeyguardService在源码中位于SystemUI中,而KeyguardUpdateMonitor则位于KeyGuard中。在KeyguardViewMediator的初始化中主要做了三件事,如图:

public void start() {
    synchronized (this) {
        setupLocked();
    }
    putComponent(KeyguardViewMediator.class, this);
}
private void setupLocked() {
    ......

    mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);

    ......

    mStatusBarKeyguardViewManager =
            SystemUIFactory.getInstance().createStatusBarKeyguardViewManager(mContext,
                    mViewMediatorCallback, mLockPatternUtils);
    final ContentResolver cr = mContext.getContentResolver();

    mDeviceInteractive = mPM.isInteractive();

    ......

    if (soundPath != null) {
        mLockSoundId = mLockSounds.load(soundPath, 1);
    }
    if (soundPath == null || mLockSoundId == 0) {
        Log.w(TAG, "failed to load lock sound from " + soundPath);
    }
    soundPath = Settings.Global.getString(cr, Settings.Global.UNLOCK_SOUND);
    if (soundPath != null) {
        mUnlockSoundId = mLockSounds.load(soundPath, 1);
    }
    if (soundPath == null || mUnlockSoundId == 0) {
        Log.w(TAG, "failed to load unlock sound from " + soundPath);
    }
    soundPath = Settings.Global.getString(cr, Settings.Global.TRUSTED_SOUND);
    if (soundPath != null) {
        mTrustedSoundId = mLockSounds.load(soundPath, 1);
    }
    if (soundPath == null || mTrustedSoundId == 0) {
        Log.w(TAG, "failed to load trusted sound from " + soundPath);
    }

    ......
}

实例化KeyguardUpdateMonitor的实例mUpdateMonitor,KeyguardUpdateMonitor负责更新已经锁屏界面上的内容(如时间)。当然,KeyguardUpdateMonitor只是一个信息传递者,实际去刷新界面的是StatusBar模块。Keyguard模块通知StatusBar刷新解密是通过KeyguardUpdateMonitorCallback这个类进行远程回调,该类的实例在StatusBar模块启动时通过KeyguardService获取到IKeyguardService的远端实例,通过IKeyguardService远程调用IKeyguardService的addStateMonitorCallback()方法实例化KeyguardUpdateMonitorCallback对象,SystemUI/src/com/android/systemui/keyguard/KeyguardService.java

private final IKeyguardService.Stub mBinder = new IKeyguardService.Stub() {

    @Override // Binder interface
    public void addStateMonitorCallback(IKeyguardStateCallback callback) {
        checkPermission();
        mKeyguardViewMediator.addStateMonitorCallback(callback);
    }

    ......

    @Override // Binder interface
    public void onScreenTurnedOn() {
        Trace.beginSection("KeyguardService.mBinder#onScreenTurningOn");
        checkPermission();
        mKeyguardViewMediator.onScreenTurnedOn();
        Trace.endSection();
    }

    ......
};

SystemUI启动的Keyguard模块并没有真正的去操作锁屏界面,而是作为一个信息传递者把信息传递给StatusBar模块。
KeyguardService在KeyguardServiceDelegate中绑定调用。
frameworks/base/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java:

public void bindService(Context context) {
    Intent intent = new Intent();
    final Resources resources = context.getApplicationContext().getResources();

    final ComponentName keyguardComponent = ComponentName.unflattenFromString(
            resources.getString(com.android.internal.R.string.config_keyguardComponent));
    intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
    intent.setComponent(keyguardComponent);

    boolean isBox = isBox();

    if (isBox || !context.bindServiceAsUser(intent, mKeyguardConnection,
            Context.BIND_AUTO_CREATE, mScrimHandler, UserHandle.SYSTEM)) {
        Log.v(TAG, "*** Keyguard: can't bind to " + keyguardComponent);
        mKeyguardState.showing = false;
        mKeyguardState.showingAndNotOccluded = false;
        mKeyguardState.secure = false;
        synchronized (mKeyguardState) {
            // TODO: Fix synchronisation model in this class. The other state in this class
            // is at least self-healing but a race condition here can lead to the scrim being
            // stuck on keyguard-less devices.
            mKeyguardState.deviceHasKeyguard = false;
            hideScrim();
        }
    } else {
        if (DEBUG) Log.v(TAG, "*** Keyguard started");
    }
}

private final ServiceConnection mKeyguardConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        if (DEBUG) Log.v(TAG, "*** Keyguard connected (yay!)");
        mKeyguardService = new KeyguardServiceWrapper(mContext,
                IKeyguardService.Stub.asInterface(service), mShowingStateChangedCallback);
        if (mKeyguardState.systemIsReady) {
            // If the system is ready, it means keyguard crashed and restarted.
            mKeyguardService.onSystemReady();
            if (mKeyguardState.currentUser != UserHandle.USER_NULL) {
                // There has been a user switch earlier
                mKeyguardService.setCurrentUser(mKeyguardState.currentUser);
            }
            // This is used to hide the scrim once keyguard displays.
            if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) {
                mKeyguardService.onStartedWakingUp();
            }
            if (mKeyguardState.screenState == SCREEN_STATE_ON
                    || mKeyguardState.screenState == SCREEN_STATE_TURNING_ON) {
                mKeyguardService.onScreenTurningOn(
                        new KeyguardShowDelegate(mDrawnListenerWhenConnect));
            }
            if (mKeyguardState.screenState == SCREEN_STATE_ON) {
                mKeyguardService.onScreenTurnedOn();
            }
            mDrawnListenerWhenConnect = null;
        }
        if (mKeyguardState.bootCompleted) {
            mKeyguardService.onBootCompleted();
        }
        if (mKeyguardState.occluded) {
            mKeyguardService.setOccluded(mKeyguardState.occluded, false /* animate */);
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        if (DEBUG) Log.v(TAG, "*** Keyguard disconnected (boo!)");
        mKeyguardService = null;
    }

};

com.android.internal.R.string.config_keyguardComponent的默认配置值:

<!-- Keyguard component -->
<string name="config_keyguardComponent" translatable="false">com.android.systemui/com.android.systemui.keyguard.KeyguardService</string>

KeyguardViewMediator启动的流程图如下:

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

推荐阅读更多精彩内容