Android4.4 锁屏流程梳理
刚毕业不久由于项目需要就接触到锁屏,从2.2到4.1都解过bug,也定制过一些功能。4.1之前的锁屏工作不难,但很费时间,因为它的逻辑,视图,资源分别分布在不同的路径下,就像散落在海边沙滩上的珠子,想串起来还是蛮费劲的。最开始时锁屏就是改个字段也要全编译生成img。后来新技能get,会针对修改的地方进行单编译,但每次编译jar,导入手机,重启看效果也是不方便的。
一年前把锁屏交出去就没有再看过了,前些日子自己的谷歌四太子升到4.4,发现锁屏有很大变化,可以左右滑页,添加删除widget,添加删除分页。简直就是一个简化版的launcher。很好奇google是怎么改变的,于是费了很大的劲拉了google的最新源码。在android 4.4中这个模块的改动简直是巨大,这里略作整理。
android4.2前做一些第三方锁屏软件都会用到该服务接口来控制系统锁屏(比如禁止系统锁屏),现在该接口已经不建议使用了,有更好的做法:
/**
* @deprecated使用{@link android.view.WindowManager.LayoutParams#FLAG_DISMISS_KEYGUARD}
* and/or {@link android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED}
* 来代替; 利用该方式可以使应用达到禁用锁屏的效果而不需要额外的权限申请。this allows you to
* Enables you to lock or unlock thekeyboard. Get an instance of this class by
* calling {@link android.content.Context#getSystemService(java.lang.String)Context.getSystemService()}.
* This class is wrapped by {@link android.app.KeyguardManagerKeyguardManager}.
* @param tag A tag that informallyidentifies who you are (for debugging who
* is disabling he keyguard).
*
* @return A {@link KeyguardLock} handle to use todisable and reenable the
* keyguard.
*/
@Deprecated
public KeyguardLock newKeyguardLock(Stringtag) {
return new KeyguardLock(tag);
}
这个类很厉害也很重要,它由WindowManagerService派生,处理了phone的顶层逻辑,主要有以下几块:
a) 横竖屏处理(屏幕旋转等)
publicvoidsetCurrentOrientationLw(intnewOrientation){
synchronized (mLock){
if (newOrientation != mCurrentAppOrientation) {
mCurrentAppOrientation = newOrientation;
updateOrientationListenerLp();
}
}
}
b) 是否显示状态条或者navigation_bar。
privateintupdateSystemUiVisibilityLw() {
// If there is no window focused,there will be nobody to handle the events
// anyway, so just hang on inwhatever state we're in until things settle down.
WindowState win = mFocusedWindow != null ? mFocusedWindow :mTopFullscreenOpaqueWindowState;
if (win == null){
return 0;
}
if (win.getAttrs().type == TYPE_KEYGUARD&&mHideLockScreen == true) {
// We are updating at a pointwhere the keyguard has gotten
// focus, but we were last in astate where the top window is
// hiding it. This is probably because the keyguardas been
// shown while the top window wasdisplayed, so we want to ignore
// it here because this is just avery transient change and it
// will quickly lose focus once itcorrectly gets hidden.
return 0;
}
inttmpVisibility = win.getSystemUiVisibility()
& ~mResettingSystemUiFlags
& ~mForceClearedSystemUiFlags;
if (mForcingShowNavBar&&win.getSurfaceLayer()
c) 各种按键事件的拦截和分发(比如长按home键)
Home键的事件是在phonewindow这一层就拦截的,所以一般情况应用本身无法正常拦截该事件。
privatevoidhandleLongPressOnHome() {
if (mLongPressOnHomeBehavior != LONG_PRESS_HOME_NOTHING) {
mHomeConsumed = true;
performHapticFeedbackLw(null,HapticFeedbackConstants.LONG_PRESS, false);
if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_SYSTEM_UI) {
toggleRecentApps();
} elseif (mLongPressOnHomeBehavior == LONG_PRESS_HOME_ASSIST){
launchAssistAction();
}
}
}
d) 锁屏事件处理和响应
publicvoidsystemReady() {
if (!mHeadless){
mKeyguardDelegate = newKeyguardServiceDelegate(mContext, null);
mKeyguardDelegate.onSystemReady();
}
synchronized (mLock){
updateOrientationListenerLp();
mSystemReady = true;
mHandler.post(new Runnable() {
@Override
publicvoid run() {
updateSettings();
}
});
}
}
这两个类是android 4.4新增加的,分别对KeyguardService进行了代理和包装,代理类里面有一个Scrim视图在keyguard崩溃时显示。包装类就是对keyguardService的简单包装,最终把调度都会传给keyguardService。
上面一再说到该类,那么它有啥出众的地方呢,其实它就是keyguard的入口,锁屏所有的往生都因它而起,这个类很简单,实例化了一个IKeyguardService.Stub供其他类bindservice时调用,需要特别注意的是整个keyguard的核心实力派KeyguardViewMediator在这里诞生了。
字面上的意思是keyguard视图调度者,功能上是负责处理keyguard视图的事件,比如完成锁屏和解锁这些动作的视图响应,它作为一个位高权重的调度使当然不会亲手去做这些,它有个得力干将KeyguardviewManager,所有的大小任务都会放权给它。
/**
*有关锁屏请求的调度者。包括锁屏状态的查询,power management事件影响锁屏是否应该被显示或者重置,特定的回调函数来
*通知windowmanager锁屏是什么时候显示,以及接受view视图传过来的消息表明已经成功完成解锁。
*请注意锁屏是在灭屏后立即被调用显示的。这样当你点亮屏幕,锁屏才能第一时间显示出来。
*例如外部事件调度锁屏视图流程:
*
*-灭屏动作-》重置锁屏并显示它为下次点亮屏幕做好准备。
*-锁屏很自然流畅的打开了-》如果他不是安全的,隐藏之。
*
*来自于锁屏视图的事件:
*-用户成功完成解锁条件-》隐藏锁屏视图,不再对输入事件进行拦截。
*请再注意:第三方应用通过条用power managment实例可以屏蔽系统的锁屏。
*
*线程和同步:
*该类是由WindowManagerPolicy创建并运行在它的线程里,锁屏UI也是这个类的构造函数里面产生。这个apis也可以被其他线程所调用。
*然而,这个类的方法手势同步的,同时任何一个锁屏视图都会发消息到handle来保证它是在锁屏UI线程里面执行的。
*/
public class KeyguardViewMediatorimplements KeyguardViewCallback,
KeyguardUpdateMonitor.InfoCallback,KeyguardUpdateMonitor.SimStateCallback {
private static final intKEYGUARD_DISPLAY_TIMEOUT_DELAY_DEFAULT = 30000;
/**
* This handler will be associated with the policy thread, which willalso
* be the UI thread of the keyguard. Since the apis of the policy, and therefore
* this class, can be called by other threads, any action that directly
* interacts with the keyguard ui should be posted to this handler,rather
* than called directly.
*/
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case TIMEOUT:
handleTimeout(msg.arg1);
return ;
case SHOW:
handleShow();
return ;
case HIDE:
handleHide();
return ;
case RESET:
handleReset();
return ;
case VERIFY_UNLOCK:
handleVerifyUnlock();
return;
case NOTIFY_SCREEN_OFF:
handleNotifyScreenOff();
return;
case NOTIFY_SCREEN_ON:
handleNotifyScreenOn((KeyguardViewManager.ShowListener)msg.obj);
return;
case WAKE_WHEN_READY:
handleWakeWhenReady(msg.arg1);
return;
case KEYGUARD_DONE:
handleKeyguardDone(msg.arg1!= 0);
return;
case KEYGUARD_DONE_DRAWING:
handleKeyguardDoneDrawing();
return;
caseKEYGUARD_DONE_AUTHENTICATING:
keyguardDone(true);
return;
case SET_HIDDEN:
handleSetHidden(msg.arg1 !=0);
break;
case KEYGUARD_TIMEOUT:
synchronized(KeyguardViewMediator.this) {
doKeyguardLocked();
}
break;
}
}
};
private void adjustStatusBarLocked() {
......//控制是否能在锁屏界面下拉状态栏。
}
}
如果说mediator相当于总裁,那这个就是经理,而且是视图部门老大,它有一个类型为FrameLayout名叫ViewManagerHost的内部类,用来作为keyguard的viewroot。在viewroot里添加了KeyguardHostView,我们叫它mKeyguardView。Keyguard里任何的view细节和问题都能通过它找到蛛丝马迹。
/**
* Manages creating, showing, hiding andresetting the keyguard. Callsback
* via {@link KeyguardViewMediator.ViewMediatorCallback} to poke
* the wake lock and report that the keyguardis done, which is in turn,
* reported to this class by the current {@link KeyguardViewBase}.
*/
public class KeyguardViewManager {
private final static boolean DEBUG = KeyguardViewMediator.DEBUG;
private static String TAG = "KeyguardViewManager";
public static boolean USE_UPPER_CASE = true;
public final static String IS_SWITCHING_USER = "is_switching_user";
// Timeoutused for keypresses
static final int DIGIT_PRESS_WAKE_MILLIS = 5000;
private final Context mContext;
private final ViewManager mViewManager;
private final KeyguardViewMediator.ViewMediatorCallbackmViewMediatorCallback;
private WindowManager.LayoutParams mWindowLayoutParams;
private boolean mNeedsInput = false;
private ViewManagerHost mKeyguardHost;
private KeyguardHostView mKeyguardView;
这里完成keyguardView布局,实例化。分析一个自定义的viewgroup,重点需要分析的是它的构造方法和onFinishInflate()方法:
public KeyguardHostView(Context context, AttributeSet attrs) {
super(context, attrs);
if (DEBUG) Log.e(TAG, "KeyguardHostView()");
mLockPatternUtils = newLockPatternUtils(context);
// Note: This depends on KeyguardHostView getting reconstructed every timethe
// user switches, since mUserId will be used for the entire session.
// Once created, keyguard should *never* re-use this instance withanother user.
// In other words, mUserId should never change - hence it's marked final.
mUserId = mLockPatternUtils.getCurrentUser();
DevicePolicyManager dpm =
(DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
if (dpm != null) {
mDisabledFeatures =getDisabledFeatures(dpm);
mCameraDisabled =dpm.getCameraDisabled(null);
}
mSafeModeEnabled= LockPatternUtils.isSafeModeEnabled();
// These need to be created with the user context...
Context userContext = null;
try {
final String packageName = "system";
userContext = mContext.createPackageContextAsUser(packageName,0,
new UserHandle(mUserId));
} catch (NameNotFoundException e) {
e.printStackTrace();
// This should never happen, but it's better to have no widgets than tocrash.
userContext = context;
}
mAppWidgetHost = new AppWidgetHost(userContext, APPWIDGET_HOST_ID, mOnClickHandler,
Looper.myLooper());
mAppWidgetManager =AppWidgetManager.getInstance(userContext);
mSecurityModel = new KeyguardSecurityModel(context);
mViewStateManager = newKeyguardViewStateManager(this);
}
@Override
protected void onFinishInflate() {
// Grab instances of and make any necessary changes to the main layouts.Create
// view state manager and wire up necessary listeners / callbacks.
View deleteDropTarget = findViewById(R.id.keyguard_widget_pager_delete_target);
mAppWidgetContainer =(KeyguardWidgetPager) findViewById(R.id.app_widget_container);
mAppWidgetContainer.setVisibility(VISIBLE);
mAppWidgetContainer.setCallbacks(mWidgetCallbacks);
mAppWidgetContainer.setDeleteDropTarget(deleteDropTarget);
mAppWidgetContainer.setMinScale(0.5f);
mSlidingChallengeLayout =(SlidingChallengeLayout) findViewById(R.id.sliding_layout);
if (mSlidingChallengeLayout != null) {
mSlidingChallengeLayout.setOnChallengeScrolledListener(mViewStateManager);
}
mAppWidgetContainer.setViewStateManager(mViewStateManager);
mAppWidgetContainer.setLockPatternUtils(mLockPatternUtils);
mMultiPaneChallengeLayout =
(MultiPaneChallengeLayout)findViewById(R.id.multi_pane_challenge);
ChallengeLayout challenge =mSlidingChallengeLayout != null ? mSlidingChallengeLayout :
mMultiPaneChallengeLayout;
challenge.setOnBouncerStateChangedListener(mViewStateManager);
mAppWidgetContainer.setBouncerAnimationDuration(challenge.getBouncerAnimationDuration());
mViewStateManager.setPagedView(mAppWidgetContainer);
mViewStateManager.setChallengeLayout(challenge);
mSecurityViewContainer = (KeyguardSecurityViewFlipper)findViewById(R.id.view_flipper);
mKeyguardSelectorView =(KeyguardSelectorView) findViewById(R.id.keyguard_selector_view);
mViewStateManager.setSecurityViewContainer(mSecurityViewContainer);
setBackButtonEnabled(false);
if (KeyguardUpdateMonitor.getInstance(mContext).hasBootCompleted()){
updateAndAddWidgets();
} else {
// We can't add widgets until after boot completes because AppWidgetHostmay try
// to contact the providers. Do itlater.
mPostBootCompletedRunnable = new Runnable() {
@Override
public void run() {
updateAndAddWidgets();
}
};
}
showPrimarySecurityScreen(false);
updateSecurityViews();
enableUserSelectorIfNecessary();
}
说明:监听系统状态值的改变如时间、SIM卡状态、电池电量等,状态值的改变会回调监听了该状态信息的对象实例。如果只是关注功能的话只需要看hadle里面的每个消息调用的方法即可。
/**
*Watches for updates that may be interesting to the keyguard, and provides
*the up to date information as well as a registration for callbacks that care
* tobe updated.
*
*Note: under time crunch, this has been extended to include some stuff that
*doesn't really belong here. see {@link#handleBatteryUpdate} where it shutdowns
*the device, and {@link #getFailedAttempts()}, {@link #reportFailedAttempt()}
*and {@link #clearFailedAttempts()}. Maybe we should rename this 'KeyguardContext'...
*/
public class KeyguardUpdateMonitor {
private Handler mHandler;
private ContentObserver mContentObserver;
private int mRingMode;
private int mPhoneState;
......
/**
* SIM卡状态改变捕获赋值。
* the intent and provide a {@link SimCard.State} result.
*/
private static class SimArgs {
public final IccCard.State simState;
private SimArgs(Intent intent) {
if(!TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
throw newIllegalArgumentException("only handles intentACTION_SIM_STATE_CHANGED");
}
String stateExtra = intent.getStringExtra(IccCard.INTENT_KEY_ICC_STATE);
if (IccCard.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) {
final String absentReason =intent
.getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON);
if(IccCard.INTENT_VALUE_ABSENT_ON_PERM_DISABLED.equals(
absentReason)) {
this.simState =IccCard.State.PERM_DISABLED;
} else {
this.simState =IccCard.State.ABSENT;
}
} else if (IccCard.INTENT_VALUE_ICC_READY.equals(stateExtra)) {
this.simState =IccCard.State.READY;
} else if (IccCard.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) {
final String lockedReason =intent
.getStringExtra(IccCard.INTENT_KEY_LOCKED_REASON);
if (IccCard.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)){
this.simState =IccCard.State.PIN_REQUIRED;
} else if(IccCard.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) {
this.simState = IccCard.State.PUK_REQUIRED;
} else {
this.simState =IccCard.State.UNKNOWN;
}
} else if (IccCard.INTENT_VALUE_LOCKED_NETWORK.equals(stateExtra)) {
this.simState =IccCard.State.NETWORK_LOCKED;
} else {
this.simState =IccCard.State.UNKNOWN;
}
}
public String toString() {
return simState.toString();
}
}
public KeyguardUpdateMonitor(Context context) {
mContext = context;
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_TIME_UPDATE:
handleTimeUpdate();
break;
case MSG_BATTERY_UPDATE:
handleBatteryUpdate(msg.arg1, msg.arg2);
break;
caseMSG_CARRIER_INFO_UPDATE:
handleCarrierInfoUpdate();
break;
case MSG_SIM_STATE_CHANGE:
handleSimStateChange((SimArgs) msg.obj);
break;
caseMSG_RINGER_MODE_CHANGED:
handleRingerModeChange(msg.arg1);
break;
caseMSG_PHONE_STATE_CHANGED:
handlePhoneStateChanged((String)msg.obj);
break;
caseMSG_CLOCK_VISIBILITY_CHANGED:
handleClockVisibilityChanged();
break;
caseMSG_DEVICE_PROVISIONED:
handleDeviceProvisioned();
break;
}
}
};