AndroidM/L 锁屏分析

2015.12.01 10:41 Tue| 830 visits androidl| Text

AndroidL分析之Keyguard



AndroidL 锁屏与SystemUI

AndroidL出来都这么久,AndroidM现在都有了,现在出来写这个是有点晚了,这里仅是当作自己的一个总结吧,希望新接触系统开发的人看到,能对他们有一点帮助。

在AndroidL之后(看了下M代码,基本还是跟L一样,没变化),谷歌把之前相对独立的Keyguard整合进了SystemUI之中,打开SystemUI目录可以看到很多Keyguard命名的文件。这应该是为了可以复用SystemUI通知部分代码,使系统通知可以在锁屏上显示。

但是这么一来,锁屏与SystemUI界面代码耦合,想要为系统锁屏单独增加个动画特效都变得异常艰难,对SystemUI的调整也要同时考虑到会不会对锁屏有影响。

这里先分析一下锁屏代码,以后再记录下怎么反Google,怎么样可以把系统锁屏独立出来的一些想法。由于AndroidKK锁屏流程分析,网上资源比较丰富,为节省篇幅,更多的是讨论AndroidL中锁屏与KK不太一样的地方。

锁屏启动过程

我们按照AndroidKK的思路,锁屏应该有一个service通过AIDL与系统进行交互,观察一下SystemUI中的代码结构,发现了KeyguardService.java。其实keyguardService是在PhoneWindowManagersystemBooted()中启动的:

    /** {@inheritDoc} */
    @Override
    public void systemBooted() {
        if (mKeyguardDelegate != null) {
            mKeyguardDelegate.bindService(mContext);//通过mKeyguardDelegate以bind方式启动/连接锁屏服务。
            mKeyguardDelegate.onBootCompleted();
        }
        synchronized (mLock) {
            mSystemBooted = true;
        }
        wakingUp();
        screenTurningOn(null);
    }

KeyguardService.javaonCreate函数如下:

@Override
    public void onCreate() {
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
        mKeyguardViewMediator =
                ((SystemUIApplication) getApplication()).getComponent(KeyguardViewMediator.class);
    }

startServicesIfNeeded定义在SystemUIApplication中,是SystemUI用来初始化各个模块的入口。

再看下KeyguardServiceonBind函数返回的binder对象:

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

        private boolean mIsOccluded;

        @Override
        public boolean isShowing() {
            return mKeyguardViewMediator.isShowing();
        }

        @Override
        public boolean isSecure() {
            return mKeyguardViewMediator.isSecure();
        }

        @Override
        public boolean isShowingAndNotOccluded() {
            return mKeyguardViewMediator.isShowingAndNotOccluded();
        }
        ... ...

锁屏这样就跟系统服务PhoneWindowManager绑定了,系统可以控制锁屏的显示,在来电时或者其他应用窗口请求暂时隐藏锁屏时,可以进行响应。
KeyguardService只是为系统提供一个查询和控制锁屏状态的入口。那么锁屏界面到底是如何加载显示到屏幕上的呢?

锁屏界面的加载显示

我们再倒回去看一下,startServicesIfNeeded到底初始化了哪些模块。

    /**
     * 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() {
        if (mServicesStarted) {
            return;
        }

        //省略判断开机状态代码

        Log.v(TAG, "Starting SystemUI services.");
        final int N = SERVICES.length;
        for (int i=0; i<N; i++) {
            Class<?> cl = SERVICES[i];//各个模块Class
            if (DEBUG) Log.d(TAG, "loading: " + cl);
            try {
                mServices[i] = (SystemUI)cl.newInstance();//实例化
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InstantiationException ex) {
                throw new RuntimeException(ex);
            }
            mServices[i].mContext = this;//统一设置mContext
            mServices[i].mComponents = mComponents;//统一设置mComponents
            if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
            mServices[i].start();//调用每个模块的start方法。

            if (mBootCompleted) {
                mServices[i].onBootCompleted();
            }
        }
        mServicesStarted = true;
    }
    /**
     * The classes of the stuff to start.
     */
    private final Class<?>[] SERVICES = new Class[] {
            com.android.systemui.keyguard.KeyguardViewMediator.class,
            com.android.systemui.recent.Recents.class,
            com.android.systemui.volume.VolumeUI.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
    };//所有的这些类都继承自`SystemUI`这个父类。Google的这个做法非常好的把每个模块分开初始化,又统一设置了同样的Context和Components。这个应该算是哪种设计模式呢???

我们找到了KeyguardViewMediator这个重要的类,它现在被放到了

SystemUI\src\com\android\systemui\keyguard\

中。职责跟KK时代相比基本没变,负责管理锁屏界面的加载,控制密码锁、pin码锁等界面的显示。

KeyguardViewMediator的start方法很简单:

@Override
    public void start() {
        setup();//这个方法负责初始化变量和生成实例,AndroidKK是放到KeyguardViewMediator的onCreate中调用的。
        putComponent(KeyguardViewMediator.class, this);//由于在startServicesIfNeeded中统一设置了统一的`mComponents`,所以当其他模块需要用到KeyguardViewMediator的时候,只需要简单的调用`getComponent`方法就可以取到同一个对象,这个设计真是·简单好用·。
    }

setUp()初始化AlarmManagerPowerManager等并加载了锁屏音,进行了一系列跟锁屏相关的初始化工作。

继续分析KeyguardViewMediator代码handleShow:

    /**
     * Handle message sent by {@link #showLocked}.
     * @see #SHOW
     */
    private void handleShow(Bundle options) {
        ... ...
        mStatusBarKeyguardViewManager.show(options);
        //忽略判断系统状态和其他的代码,关注这句,这个是跟布局显示相关的调用。
        ... ...
    }

继续向下追踪代码到了StatusBarKeyguardViewManager中的reset函数:

    /**
     * Reset the state of the view.
     */
    public void reset() {
        if (mShowing) {
            if (mOccluded) {//occluded 变量判断当前锁屏是否需要被隐藏。(当前台Window有SHOW_WHEN_LOCKED属性的时候)
                mPhoneStatusBar.hideKeyguard();
                mBouncer.hide(false /* destroyView */);
            } else {
                showBouncerOrKeyguard();//这一句。
            }
            updateStates();
        }
    }

往下看:

    /**
     * Shows the notification keyguard or the bouncer depending on
     * {@link KeyguardBouncer#needsFullscreenBouncer()}.
     */
    private void showBouncerOrKeyguard() {
        if (mBouncer.needsFullscreenBouncer()) {//当需要显示手机卡sim pin码锁屏的时候,返回true。

            // The keyguard might be showing (already). So we need to hide it.
            mPhoneStatusBar.hideKeyguard();
            mBouncer.show();//显示安全锁屏
        } else {
            mPhoneStatusBar.showKeyguard();//显示传统意义上的锁屏。
            mBouncer.hide(false /* destroyView */);//hide了什么?
            mBouncer.prepare();
        }
    }

看到这里,我们可能会有疑惑:这个hideKeyguardhide的是哪个keyguard?这个mBouncer又是个什么?也是锁屏吗?难道有两个锁屏在互相切换吗?

前面已经讲过,在AndroidL之后的代码中,锁屏布局的加载显示已经移到了SystemUI模块中。SystemUI这里的Keyguard指的是:
Keyguard

Bouncer是加载安全锁屏的容器:
Bouncer

找到了PhoneStatusBarBouncer的调用以后,我们查看他们的布局文件,可以发现,锁屏是依附于SystemUI的布局文件进行加载的。我们看到的系统锁屏, 其实是把SystemUI拉下展开之后的一个特殊状态,在它上面可以很方便的显示系统通知 。这个是Google在AndroidL之后重新设计的。

这样可以方便的显示系统通知,但是锁屏View和SystemUI绑定到一起,如果要调整锁屏的布局和动画特效,就要小心可能会对SystemUI产生的影响,如果要更改锁屏的触摸操作、交互逻辑,很难不影响SystemUI。
我们看到华为荣耀的锁屏效果,解锁效果非常酷炫,UI元素和交互方式也比原生锁屏丰富,他们肯定是把锁屏独立出来了。