12.09-12.10学习 Android自定义锁屏

分析锁屏广告的时候让我研究一下怎样让锁屏activity置于系统锁屏之上的,由此而来一篇学习

写在前面

Android 的API 27FLAG_SHOW_WHEN_LOCKED已经弃用

正常Android自定义锁屏都会用到FLAG_SHOW_WHEN_LOCKED这个flag,但是从API 27之后由于Android的安全性考虑FLAG_TURN_SCREEN_ON和FLAG_SHOW_WHEN_LOCKED都已经被弃用 ,改为用

1
2
3
4
//指定Activity在每次锁定屏幕并恢复活动时是否应在锁定屏幕顶部显示
setShowWhenLocked(true)
//点亮屏幕
setTurnScreenOn(true)

和keyguardManager.requestDismissKeyguard解锁屏幕(必需)

官方文档解释https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html#FLAG_SHOW_WHEN_LOCKED

由于网上的教程都是关于之前的版本的,所以分析还是会以FLAG_SHOW_WHEN_LOCKED为主。

言归正传,讲一讲Android自定义锁屏整个流程,当然整个流程是以Android逆向中锁屏广告的分析来看的,所以特别涉及开发的部分是会省略掉的

基本原理

思路:APP监听系统SCREEN_OFF和SCREEN_ON广播,当屏幕熄灭时,service监听到广播开启一个锁屏页Activity在屏幕最上层显示,当没有密码时直接去掉系统锁屏。

image-20201210151443824

广播注册

监听熄屏,开屏,解锁系统广播

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
IntentFilter mScreenOffFilter = new IntentFilter();
mScreenOffFilter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(mScreenOffReceiver, mScreenOffFilter);
  对应的BroadcastReceiver定义如下:

private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
@SuppressWarnings("deprecation")
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(NOTIFY_SCREEN_OFF)) {
Intent mLockIntent = new Intent(context, LockScreenActivity.class);
mLockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivity(mLockIntent);
}
}
};

关于启动Activity时Intent的Flag问题,如果不添加FLAG_ACTIVITY_NEW_TASK的标志位,会出现“Calling startActivity() from outside of an Activity”的运行时异常,毕竟我们是从Service启动的Activity。Activity要存在于activity的栈中,而Service在启动activity时必然不存在一个activity的栈,所以要新起一个栈,并装入启动的activity。使用该标志位时,也需要在AndroidManifest中声明taskAffinity,即新task的名称,否则锁屏Activity实质上还是在建立在原来App的task栈中。

标志位FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,是为了避免在最近使用程序列表出现Service所启动的Activity,但这个标志位不是必须的,其使用依情况而定。

屏幕相关广播

1
2
3
4
5
6
//屏幕亮起
android.intent.action.SCREEN_ON
//解锁屏幕
android.intent.action.USER_PRESENT
//屏幕熄灭
android.intent.action.SCREEN_OFF

Acivity设置

锁屏的activity做相应的配置,让activity在锁屏时也能够显示,同时去掉系统锁屏。但基于现在一般手机都有密码,而且之前去掉系统锁屏的方法也已经不适用换成keyguardManager.requestDismissKeyguard(),所以这里不具体讲去掉锁屏的过程。

这里贴一个检查Android锁屏状态/锁屏密码相关的代码

1
2
3
4
5
6
7
8
9
10
11
12
//当前系统锁屏是否有密码
public boolean hasScreenLockPwd(Context mContext){
KeyguardManager keyguardManager = (KeyguardManager )mContext.getSystemService(Context.KEYGUARD_SERVICE);
return keyguardManager.isKeyguardSecure();
}


//当前系统是否处于锁屏状态
public boolean hasScreenLocked(Context mContext){
KeyguardManager keyguardManager = (KeyguardManager )mContext.getSystemService(Context.KEYGUARD_SERVICE);
return keyguardManager.isKeyguardLocked();
}

锁屏activity实现

我们在自定义锁屏Activity的OnCreate()方法里设定一下标志位就能实现将锁屏activity置于锁屏界面之上

1
2
//使得窗口浮在锁屏界面之上
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);

按键屏蔽

只有通过过划瓶或者指纹才能解锁,因此有必要对按键进行一定程度上的屏蔽。

Back键和Menu键可以通过重写onKeyDown()方法进行屏蔽:

1
2
3
4
5
6
7
8
9
10
11
12
public boolean onKeyDown(int keyCode, KeyEvent event) {
int key = event.getKeyCode();
switch (key) {
case KeyEvent.KEYCODE_BACK: {
return true;
}
case KeyEvent.KEYCODE_MENU:{
return true;
}
}
return super.onKeyDown(keyCode, event);
}

划屏解锁

手指在屏幕上滑动时,拦截并处理滑动事件,使锁屏页面随着手指运动,当运动到达一定的阀值时,用户手指松开手指,锁屏页自动滑动到屏幕边界消失,如果没有达到运动阀值,就会自动滑动到起始位置,重新覆盖屏幕。

为了将划屏逻辑与页面内容隔离开来,我们在锁屏页面布局中添加一个自定义的UnderView,这个UnderView填充整个屏幕,位于锁屏内容View(将其引用称之为mMoveView,并传入到UnderView中)的下方,所有划屏相关的事件都在这里拦截并处理。

mMoveView是锁屏页的显示内容,除了处理一些简单的点击事件,其他非点击事件序列都由底层的UnderView进行处理。只需要重写UnderView的onTouchEvent方法就能够实现

此外,我们可以通过getBackground()获取UnderView的背景,并根据已划开屏幕占整个屏幕的百分比调用setAlpha方法改变背景的透明度,做出抽屉拉开时的光影变化效果。

具体代码参考链接,此处只讲思路,不多赘述。

注意请求小米的锁屏activity时要加入小米白名单,同时在了解锁屏广告时还发现OP_SHOW_WHEN_LOCKED这个标识符,了解知道这个是锁屏上显示权限的标识,值为10020,作用是检测应用是否具备锁屏上显示权限,具体参考https://patents.google.com/patent/WO2018049609A1/zh

参考链接:https://zhuanlan.zhihu.com/p/21628574

https://stackoverflow.com/questions/48277302/android-o-flag-show-when-locked-is-deprecated

https://stackoverflow.com/questions/40611432/getwindow-addflags-in-android

https://www.cnblogs.com/zhou-guobao/p/4994762.html

https://www.jianshu.com/p/36916ce0ab3b

https://juejin.cn/post/6844903841377288199