<h2>基于Android 7.1.1鎖屏模塊分析</h2>
1.代碼基于Android7.1.1可以從 https://android.googlesource.com/ 中獲取代碼,主要關(guān)注兩個package椿猎,
一個是frameworks/base/package/Keyguard
主要為系統(tǒng)中鎖屏模塊的代碼,例如我們看到的鎖屏界面宴合,以及畫開鎖屏界面會有密碼,圖案等不同的解鎖界面等,主要是和鎖屏的view相關(guān)的代碼澎迎。
二是frameworks/base/package/SystemUI
這個代表的是系統(tǒng)UI稼虎,狀態(tài)欄衅檀,通知中心顯示,最近任務(wù)列表霎俩,鎖屏的都在這里面控制哀军,只看鎖屏模塊的話,keyguard
相當(dāng)于處理顯示view打却,而SystemUI
是屬于對keyguard
進(jìn)行管理的杉适,控制其顯示邏輯的。
2.從SystemUI目錄下的Android.mk
也可以看出來這兩個模塊的關(guān)系学密。
LOCAL_STATIC_JAVA_LIBRARIES := \Keyguard
淘衙,LOCAL_RESOURCE_DIR := \frameworks/base/packages/Keyguard/res \
SystemUI
編譯需要是要依賴Keyguard
模塊的。
修改Keyguard
模塊的代碼后編譯此模塊不會產(chǎn)生相應(yīng)的apk而是要在繼續(xù)編譯SystemUI
模塊的代碼產(chǎn)生的SystemUI.apk
放到手機(jī)里才會起作用腻暮,也就是說鎖屏不會產(chǎn)生一個單獨(dú)的apk
文件彤守,而是由SystemUI.apk
包含了現(xiàn)有手機(jī)的鎖屏模塊。
3.鎖屏分為兩部分:
- 鎖屏界面稱為
Keyguard
(常見為鎖屏界面時鐘哭靖,通知界面) - 解鎖界面稱為
Bouncer
(常見為滑開鎖屏界面具垫,顯示密碼,圖案等解鎖方式界面)
上圖為keyguard界面的時序調(diào)用關(guān)系,這只是展示了鎖屏界面试幽,還有鎖屏界面后面的解鎖界面還需要加載和展示
在StatusBarKeyguardViewManager.java
中的showBouncerOrKeyguard()
方法中筝蚕,從上圖看,調(diào)用此方法加載鎖屏界面:
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
的區(qū)分,先執(zhí)行了KeyguardBouncer.java
中的needsFullscreenBouncer()
方法:
public boolean needsFullscreenBouncer() {
ensureView();
if (mKeyguardView != null) {
SecurityMode mode = mKeyguardView.getSecurityMode();
return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
}
return false;
}
從此方法可以看出當(dāng)解鎖方式為SimPin
和SimPuk
時才會返回為true
起宽,對應(yīng)著sim
卡的兩種狀態(tài)洲胖,這個時候要顯示一個全屏的界面,必須要輸入正確相應(yīng)的密碼才能進(jìn)入坯沪,否則不能使用手機(jī)绿映。
4.Keyguard
與SystemUI
的交互
既然Keyguard
是作為SystemUI
的library存在的,那么在SystemUI
中調(diào)用Keyguard
中的類腐晾,就很方便了叉弦,只需要直接導(dǎo)入類即可,但是在Keyguard
中如何和SystemUI
通信呢藻糖?主要關(guān)注三個類:
-
KeyguardViewMediator.java
此類是在SystemUI
中做統(tǒng)一調(diào)度的淹冰,也就是像我們長熟悉的熄屏,亮屏巨柒,鎖屏等的處理都是在這里面的做的樱拴,它是一個對Keyguard
的調(diào)度者。 -
KeyguardUpdateMonitor.java
潘拱,從此類的說明中就可以看的出來疹鳄,它是來處理鎖屏更新操作的類,KeyguardViewMediator.java
等對于鎖屏更新的額相關(guān)處理都是在這里面進(jìn)行的操作芦岂。 -
KeyguardUpdateMonitorCallback.java
瘪弓,作為更新之后的回調(diào)類,當(dāng)我們跟新一些狀態(tài)之后禽最,還需要更新之后反饋一些狀態(tài)時就會需要用到此類腺怯,就像我們進(jìn)行網(wǎng)絡(luò)請求,不能就發(fā)送網(wǎng)絡(luò)請求后就不管了川无,需要反饋結(jié)果是連接成功了呛占,還是失敗了,需要有明確的信息幫助我們進(jìn)行下一步操作懦趋。
這樣我們就清楚了整個SystemUI
和Keyguard
的流程:
這樣我們就知道了整體鎖屏是如何通信交流的晾虑,關(guān)鍵的兩個類就是KeyguardUpdateMonitor.java
,
KeyguardUpdateMonitorCallback.java
,再明確一下這兩個類的作用:
-
KeyguardUpdateMonitor.java
做鎖屏狀態(tài)變更等代碼的處理 -
KeyguardUpdateMonitorCallback.java
當(dāng)Keyguard
和SystemUI
中有需要回調(diào)的類仅叫,就需要先注冊此Callback
然后再有具體實(shí)現(xiàn)帜篇。
當(dāng)我們需要新加處理代碼時就知道了,在KeyguardUpdateMonitorCallback.java
接口中聲明诫咱,在KeyguardUpdateMonitor.java
中調(diào)用生命函數(shù)笙隙,在所要更新的類中注冊,當(dāng)接收到回調(diào)借口函數(shù)是坎缭,執(zhí)行方法即可竟痰。
5.如何通過回調(diào)函數(shù)通知多個類(借鑒源碼額外分析理解)
我們都會使用回調(diào)函數(shù)签钩,但是一般寫法的回調(diào)函數(shù)只能是1對1的,也就是說A坏快,和B類之間通過接口回調(diào)铅檩,但是如果a類需要發(fā)送消息,b類莽鸿,c類等其他類都需要執(zhí)行回調(diào)函數(shù)時柠并,我們需要怎么做呢?查看此處源碼富拗,我們就會使用了這個方法,通過在b類鸣戴,c類中注冊相應(yīng)的接口啃沪,這樣在a類中遍歷所注冊的接口,然后即可調(diào)用b,c類中的方法實(shí)現(xiàn)窄锅,這樣也就是a類實(shí)現(xiàn)狀態(tài)變化创千,同時跟新了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
中調(diào)用更新方法即可
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截圖可以看出來入偷,當(dāng)我們點(diǎn)擊了button
時追驴,即可遍歷代碼更新所注冊兩個類的方法。
我們將接口和管理分發(fā)的中間代碼都放到CallbackUpdateMonitor.java
類中進(jìn)行疏之,當(dāng)我們需要在添加其他類的時候殿雪,只需要通過注冊之后通過CallbackUpdateMonitor來就可以進(jìn)行通信了。
6.接下來我們再看一下鎖屏方式和設(shè)置的關(guān)聯(lián)
當(dāng)我們在設(shè)置中選擇不同的鎖屏方式锋爪,為什么就可以設(shè)置不同的解鎖密碼丙曙,這個是由什么控制的?
這里只列出關(guān)鍵代碼:
Settings
應(yīng)用包中的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;
}
一個關(guān)鍵類為LockPatternUtils.java
它是用來設(shè)置和鎖屏交互的類其骄,設(shè)置亏镰,更新解鎖方式和加密等操作都是通過此類來完成的。我們看到它設(shè)置鎖屏方式就是設(shè)置了不同的DevicePolicyManager
拯爽,這樣的話我們就需要看在鎖屏中的代碼索抓,根據(jù)搜關(guān)鍵字DevicePolicyManager
我們就可以發(fā)現(xiàn)使如何設(shè)置當(dāng)前的鎖屏方式的。
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);
}
}
從而可以看出設(shè)置和鎖屏中通過LockPatternUtils
設(shè)置和保存不同的密碼等來區(qū)分當(dāng)前的解鎖方式毯炮,然后再看到它的調(diào)用出的代碼KeyguardSecurityContainer
類逼肯,此類是屏幕 解鎖界面的父view,同過此view跟新當(dāng)前解鎖方式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;
}
}
這樣就可以看到不同的解鎖方式對應(yīng)者不同的自定義布局例如:
密碼布局為:KeyguardPasswordView.java
Pin碼為:KeyguardPINView.java
圖案為:KeyguardPatternView
等等.
這樣能夠我們就從SystemUI和Keyguard來分析了鎖屏的邏輯和相關(guān)操作否副,主體就是這樣汉矿。