基于android7.1.1锁屏模块分析

<h2>基于Android 7.1.1锁屏模块分析</h2>

1.代码基于Android7.1.1可以从 https://android.googlesource.com/ 中获取代码,主要关注两个package,
一个是frameworks/base/package/Keyguard主要为系统中锁屏模块的代码,例如我们看到的锁屏界面,以及画开锁屏界面会有密码,图案等不同的解锁界面等,主要是和锁屏的view相关的代码。
二是frameworks/base/package/SystemUI这个代表的是系统UI,状态栏,通知中心显示,最近任务列表,锁屏的都在这里面控制,只看锁屏模块的话,keyguard相当于处理显示view,而SystemUI是属于对keyguard进行管理的,控制其显示逻辑的。

2.从SystemUI目录下的Android.mk也可以看出来这两个模块的关系。
LOCAL_STATIC_JAVA_LIBRARIES := \KeyguardLOCAL_RESOURCE_DIR := \frameworks/base/packages/Keyguard/res \
SystemUI编译需要是要依赖Keyguard模块的。
修改Keyguard模块的代码后编译此模块不会产生相应的apk而是要在继续编译SystemUI模块的代码产生的SystemUI.apk放到手机里才会起作用,也就是说锁屏不会产生一个单独的apk文件,而是由SystemUI.apk包含了现有手机的锁屏模块。

3.锁屏分为两部分:

  • 锁屏界面称为Keyguard(常见为锁屏界面时钟,通知界面)
  • 解锁界面称为Bouncer (常见为滑开锁屏界面,显示密码,图案等解锁方式界面)
Paste_Image.png

上图为keyguard界面的时序调用关系,这只是展示了锁屏界面,还有锁屏界面后面的解锁界面还需要加载和展示
StatusBarKeyguardViewManager.java中的showBouncerOrKeyguard()方法中,从上图看,调用此方法加载锁屏界面:

protected void showBouncerOrKeyguard() {
        if (mBouncer.needsFullscreenBouncer()) {

            // The keyguard might be showing (already). So we need to hide it.
            mPhoneStatusBar.hideKeyguard();
            mBouncer.show(true /* resetSecuritySelection */);
        } else {
            mPhoneStatusBar.showKeyguard();
            mBouncer.hide(false /* destroyView */);
            mBouncer.prepare();
        }
    }

此方法中就是对于显示Bouncer还是Keyguard的区分,先执行了KeyguardBouncer.java中的needsFullscreenBouncer()方法:

public boolean needsFullscreenBouncer() {
        ensureView();
        if (mKeyguardView != null) {
            SecurityMode mode = mKeyguardView.getSecurityMode();
            return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
        }
        return false;
    }

从此方法可以看出当解锁方式为SimPinSimPuk时才会返回为true,对应着sim卡的两种状态,这个时候要显示一个全屏的界面,必须要输入正确相应的密码才能进入,否则不能使用手机。

4.KeyguardSystemUI的交互

既然Keyguard是作为SystemUI的library存在的,那么在SystemUI中调用Keyguard中的类,就很方便了,只需要直接导入类即可,但是在Keyguard中如何和SystemUI通信呢?主要关注三个类:

  • KeyguardViewMediator.java此类是在SystemUI中做统一调度的,也就是像我们长熟悉的熄屏,亮屏,锁屏等的处理都是在这里面的做的,它是一个对Keyguard的调度者。
  • KeyguardUpdateMonitor.java,从此类的说明中就可以看的出来,它是来处理锁屏更新操作的类,KeyguardViewMediator.java等对于锁屏更新的额相关处理都是在这里面进行的操作。
  • KeyguardUpdateMonitorCallback.java,作为更新之后的回调类,当我们跟新一些状态之后,还需要更新之后反馈一些状态时就会需要用到此类,就像我们进行网络请求,不能就发送网络请求后就不管了,需要反馈结果是连接成功了,还是失败了,需要有明确的信息帮助我们进行下一步操作。

这样我们就清楚了整个SystemUIKeyguard的流程:

调用关系.png

这样我们就知道了整体锁屏是如何通信交流的,关键的两个类就是KeyguardUpdateMonitor.java,
KeyguardUpdateMonitorCallback.java,再明确一下这两个类的作用:

  • KeyguardUpdateMonitor.java做锁屏状态变更等代码的处理
  • KeyguardUpdateMonitorCallback.javaKeyguardSystemUI中有需要回调的类,就需要先注册此Callback然后再有具体实现。

当我们需要新加处理代码时就知道了,在KeyguardUpdateMonitorCallback.java接口中声明,在KeyguardUpdateMonitor.java中调用生命函数,在所要更新的类中注册,当接收到回调借口函数是,执行方法即可。

5.如何通过回调函数通知多个类(借鉴源码额外分析理解)

我们都会使用回调函数,但是一般写法的回调函数只能是1对1的,也就是说A,和B类之间通过接口回调,但是如果a类需要发送消息,b类,c类等其他类都需要执行回调函数时,我们需要怎么做呢?查看此处源码,我们就会使用了这个方法,通过在b类,c类中注册相应的接口,这样在a类中遍历所注册的接口,然后即可调用b,c类中的方法实现,这样也就是a类实现状态变化,同时跟新了b,c类等。

示例代码:
我们定义了三个类分别为:
CallbackUpdateMonitor.java

public class CallbackUpdateMonitor {

    private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();

    private volatile static CallbackUpdateMonitor sInstance;

    public static CallbackUpdateMonitor getInstance() {
        if (sInstance == null) {
            synchronized (CallbackUpdateMonitor.class) {
                if (sInstance == null) {
                    sInstance = new CallbackUpdateMonitor();
                }
            }
        }
        return sInstance;
    }

    void udpateCurrentState() {
        for (int i = 0; i < mCallbacks.size(); i++) {
            Callback cb = mCallbacks.get(i).get();
            if (cb != null) {
                cb.updateOtherState();
            }
        }
    }

    void registerCallback(Callback callback) {
        for (int i = 0; i < mCallbacks.size(); i++) {
            if (mCallbacks.get(i).get() == callback) {
                Log.d("callbacktest", "already add callback = " + callback, new Exception("Called by"));
                return;
            }
        }
        mCallbacks.add(new WeakReference<Callback>(callback));
        removeCallback(null); // remove unused references
    }

    void removeCallback(Callback callback) {
        for (int i = mCallbacks.size() - 1; i >= 0; i--) {
            if (mCallbacks.get(i).get() == callback) {
                mCallbacks.remove(i);
            }
        }
    }

    public interface Callback {
        void updateOtherState();
    }

}

CallbackTestB

public class CallbackTestB {

    CallbackUpdateMonitor.Callback callback = new CallbackUpdateMonitor.Callback() {
        @Override
        public void updateOtherState() {
            Log.d("callbacktest", "you update b");
        }
    };

    CallbackTestB() {
        CallbackUpdateMonitor.getInstance().registerCallback(callback);
    }

}

CallbackTestC

public class CallBackTestC {

    CallbackUpdateMonitor.Callback callback = new CallbackUpdateMonitor.Callback() {
        @Override
        public void updateOtherState() {
            Log.d("callbacktest", "you update c");
        }
    };

    CallBackTestC() {
        CallbackUpdateMonitor.getInstance().registerCallback(callback);
    }
}

然后在一个Activity中调用更新方法即可

public class MainActivity extends AppCompatActivity {

    Button mButton;
    CallbackUpdateMonitor callbackTestA;
    CallbackTestB callbackTestB;
    CallBackTestC callBackTestC;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        callbackTestA = CallbackUpdateMonitor.getInstance();
        callbackTestB = new CallbackTestB();
        callBackTestC = new CallBackTestC();

        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                callbackTestA.udpateCurrentState();
            }
        });
    }
}
log截图.png

从上述log截图可以看出来,当我们点击了button时,即可遍历代码更新所注册两个类的方法。
我们将接口和管理分发的中间代码都放到CallbackUpdateMonitor.java类中进行,当我们需要在添加其他类的时候,只需要通过注册之后通过CallbackUpdateMonitor来就可以进行通信了。

6.接下来我们再看一下锁屏方式和设置的关联

当我们在设置中选择不同的锁屏方式,为什么就可以设置不同的解锁密码,这个是由什么控制的?
这里只列出关键代码:
Settings应用包中的ChooseLockGeneric.java中的

    private boolean setUnlockMethod(String unlockMethod) {
            EventLog.writeEvent(EventLogTags.LOCK_SCREEN_TYPE, unlockMethod);
            if (KEY_UNLOCK_SET_OFF.equals(unlockMethod)) {
                updateUnlockMethodAndFinish(
                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, true /* disabled */ );
            } else if (KEY_UNLOCK_SET_NONE.equals(unlockMethod)) {
                updateUnlockMethodAndFinish(
                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, false /* disabled */ );
            } else if (KEY_UNLOCK_SET_MANAGED.equals(unlockMethod)) {
                maybeEnableEncryption(DevicePolicyManager.PASSWORD_QUALITY_MANAGED, false);
            } else if (KEY_UNLOCK_SET_PATTERN.equals(unlockMethod)) {
                maybeEnableEncryption(
                        DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false);
            } else if (KEY_UNLOCK_SET_PIN.equals(unlockMethod)) {
                maybeEnableEncryption(
                        DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, false);
            } else if (KEY_UNLOCK_SET_PASSWORD.equals(unlockMethod)) {
                maybeEnableEncryption(
                        DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, false);
            } else {
                Log.e(TAG, "Encountered unknown unlock method to set: " + unlockMethod);
                return false;
            }
            return true;
        }

一个关键类为LockPatternUtils.java它是用来设置和锁屏交互的类,设置,更新解锁方式和加密等操作都是通过此类来完成的。我们看到它设置锁屏方式就是设置了不同的DevicePolicyManager,这样的话我们就需要看在锁屏中的代码,根据搜关键字DevicePolicyManager我们就可以发现使如何设置当前的锁屏方式的。
keyguard包中的代码KeyguardSecurityModel.java

    public enum SecurityMode {
        Invalid, // NULL state
        None, // No security enabled
        Pattern, // Unlock by drawing a pattern.
        Password, // Unlock by entering an alphanumeric password
        PIN, // Strictly numeric password
        SimPin, // Unlock by entering a sim pin.
        SimPuk // Unlock by entering a sim puk
    }
    SecurityMode getSecurityMode() {
        KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);

        if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
                monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
            return SecurityMode.SimPuk;
        }

        if (SubscriptionManager.isValidSubscriptionId(
                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
            return SecurityMode.SimPin;
        }

        final int security = mLockPatternUtils.getActivePasswordQuality(
                KeyguardUpdateMonitor.getCurrentUser());
        switch (security) {
            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
            case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
                return SecurityMode.PIN;

            case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
            case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
            case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
            case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
                return SecurityMode.Password;

            case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
                return SecurityMode.Pattern;
            case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
                return SecurityMode.None;

            default:
                throw new IllegalStateException("Unknown security quality:" + security);
        }
    }

从而可以看出设置和锁屏中通过LockPatternUtils设置和保存不同的密码等来区分当前的解锁方式,然后再看到它的调用出的代码KeyguardSecurityContainer类,此类是屏幕 解锁界面的父view,同过此view跟新当前解锁方式view:

    private KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
        KeyguardSecurityView view = null;
        final int children = mSecurityViewFlipper.getChildCount();
        for (int child = 0; child < children; child++) {
            if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
                view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
                break;
            }
        }
        int layoutId = getLayoutIdFor(securityMode);
        if (view == null && layoutId != 0) {
            final LayoutInflater inflater = LayoutInflater.from(mContext);
            if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
            View v = inflater.inflate(layoutId, mSecurityViewFlipper, false);
            mSecurityViewFlipper.addView(v);
            updateSecurityView(v);
            view = (KeyguardSecurityView)v;
        }

        return view;
    }
    protected int getLayoutIdFor(SecurityMode securityMode) {
        switch (securityMode) {
            case Pattern: return R.layout.keyguard_pattern_view;
            case PIN: return R.layout.keyguard_pin_view;
            case Password: return R.layout.keyguard_password_view;
            case SimPin: return R.layout.keyguard_sim_pin_view;
            case SimPuk: return R.layout.keyguard_sim_puk_view;
            default:
                return 0;
        }
    }

这样就可以看到不同的解锁方式对应者不同的自定义布局例如:
密码布局为:KeyguardPasswordView.java
Pin码为:KeyguardPINView.java
图案为:KeyguardPatternView
等等.
这样能够我们就从SystemUI和Keyguard来分析了锁屏的逻辑和相关操作,主体就是这样。

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

推荐阅读更多精彩内容