实现一个Android锁屏App功能的难点总结

作者:李争献 时间:2022-07-21 03:03:19 

自定义一个漂亮实用的锁屏app,如果能赢得用户的认可,替换系统自带的锁屏,绝对是一个不小的日活入口。这段时间正好总结一下最近调研的Android平台的锁屏app开发中的难点。

一、前言

锁屏的大概实现原理都很简单。监听系统的亮屏广播,在亮屏的时候展示自己的锁屏界面,用户在锁屏界面上进行一系列的动作才能解锁。有的手机启动锁屏界面的过程会很卡,所以会明显看到亮屏之后锁屏界面的启动有延时,因此也可以选择监听系统灭屏的广播,屏幕关掉的时候就将锁屏界面准备好,直接亮屏展示(灭屏后你的app会比较容易被杀死,这点要注意做保活)。

还需要注意,亮屏和灭屏广播,SCREEN_ON/SCREEN_OFF都是只能动态监听的,所以要另开一个Service来注册,这个Service的自启动和保活也要做好。

基本的实现细节就不多讲了,这篇文章只会讲遇到的几个难点。

二、锁屏实现中的难点

1.屏蔽Home键

既然是锁屏界面,当然只能通过界面上的一些滑动或者输入动作来解开锁屏,不能简单的直接被Home键一按,就解开了。从4.0开始,Home直接在framework层就被系统响应到,强退到桌面,第三方应用里已经无法再通过Activity.onKeyDown方法来监听和拦截Home键,尽管还象征性的保留了Home键的KeyCode来向前兼容,但是Home键按下去,并不会回调这个方法。

除了onKeyDown,有没有其他办法监听Home键,有的。前台App退到后台会有广播ACTION_CLOSE_SYSTEM_DIALOGS,收到广播携带的intent之后,解析里面的"reason"参数,就可以知道退出原因是什么了。home键按下后,reason是"homekey",最近任务键按下后,reason是"recentapps"。

这当然不是最终方案,因为有些三星ROM里并不会有这个广播。而且广播的意思只是通知你一下,人家framework层已经把你的应用退回桌面了,你能监听home键,但没有办法拦截home键。也许想到了可以监听到home键的时候,马上把自己的Activity又重新打开展示,我试了一下,home键按下后startActivity会有延时3秒左右,这应该是Google早就想到了我们会这么干,做了这么一个延时方案。

直接拦截行不通了,想想别的路子。按Home键是让系统退回到Launcher(即桌面启动器),那么如果我们的锁屏Activity本身就是Launcher的话,那按Home键不就等于回到我们的锁屏Activity,也就可以阻止它把锁屏Activity关掉了。

怎么把自己的Activity声明为Launcher,在Activity中添加intent-filter:


<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>

这样,新安装的app会是一个能够作为launcher的app,所以首次按Home键的时候,就会有弹窗提示你选择要进入哪个launcher,选择我们自己的Activity,这样home键就被我们接管了。

不过这样有一个很明显的问题,如果不在我们的锁屏界面按Home键,同样会进入到锁屏Activity。当然,解决的方式也简单,当我们按Home时进入锁屏Activity的onCreate里做一个判断,如果前一个前台Activity是锁屏Activity,那就不用对Home键处理,如果不是锁屏Activity,那就要关闭锁屏Activity,跳到用户真正的桌面启动器去了。真正的桌面启动器是哪一个,我们可以这样来找:


List<String> pkgNamesT = new ArrayList<String>();
List<String> actNamesT = new ArrayList<String>();
List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (int i = 0; i < resolveInfos.size(); i++) {
String string = resolveInfos.get(i).activityInfo.packageName;
if (!string.equals(context.getPackageName())) {//自己的launcher不要
 pkgNamesT.add(string);
 string = resolveInfos.get(i).activityInfo.name;
 actNamesT.add(string);
}
}

如果实际的launcher只有一个,那直接跳转过去就可以了:


ComponentName componentName = new ComponentName(pkgName, actName);
Intent intent = new Intent();
intent.setComponent(componentName);
context.startActivity(intent);
((Activity) context).finish();

如果手机安装有多个launcher(如360桌面一类的app)就会麻烦一点,需要展示一个列表让用户来选取用哪个launcher,这个在产品形态上可能会让用户觉得有点不解。

现在,如果在其他APP里按一下Home键,会跳到我们的锁屏Activity然后跳转到真正的launcher。这里可能会有Activity闪现一下的场景,影响用户体验。最优的办法其实是另外弄一个Activity来作为Home键跳转的Activity,这个Activity设为透明的,就不会被用户感知。如此,产品形态就变成了,锁屏Activity中按Home键,跳转到透明Activity,跳转回锁屏Activity,相当于Home键无效;其他APP中按Home键,跳转到透明Activity,跳转到真正的桌面。

实现透明的Activity,只需要在xml中声明


android:theme="@android:style/Theme.Translucent.NoTitleBar"

这样的界面是透明的,实际上有占位在屏幕的顶层,所以跳转后记得要finish掉,不然会阻断跳转后的界面的交互。另外,Theme.NoDisplay也能将Activity设置为不可见,而且不占位,但是笔者实现的时候发现,NoDisplay的Activity无法被系统设置为launcher(设置后会弹窗让你重新设置,如此反复)

2.悬浮窗的实现方式

由于受Home键无法直接拦截的限制,Activity实现的锁屏会需要绕较多的路。所以有的锁屏应用会使用悬浮窗来实现,悬浮窗能够无视Home键,在按下home键的时候不会退到后台。所以不需要在home键的问题上纠结。悬浮窗统一由WindowManager来管理,具体的实现比较简单,笔者就不赘述了,有个坑要注意,悬浮窗需要声明权限:

   


<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

有的手机设置里,默认是不给应用授权悬浮窗使用权的,所以应用里还要考虑引导用户授权悬浮窗使用。

此外,有些应急解锁的场景,比如来电接听,闹铃处理,对于Activity实现的锁屏界面,系统会自动把所有的前台Activity隐藏,让用户直接去处理这些场景。但是悬浮窗会盖住场景,所以遇到这些场景,悬浮窗实现的锁屏界面要自己去处理这些特殊场景的自动解锁。

3.禁用系统锁屏

有了自己的锁屏界面,还需要禁用掉系统的锁屏,以免造成用户需要解锁两次的局面。

首先我们需要知道用户是否设置了锁屏,方法如下:

对于API Level 16及以上SDK,可以使用如下方法判断是否有锁:


((KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE)).isKeyguardSecure()

对API Level 15及以下SDK,可以使用反射来判断:


try {
Class<?> clazz = Class.forName("com.android.internal.widget.LockPatternUtils");
Constructor<?> constructor = clazz.getConstructor(Context.class);
constructor.setAccessible(true);
Object utils = constructor.newInstance(this);
Method method = clazz.getMethod("isSecure");
return (Boolean) method.invoke(utils);
}catch (Exception e){
e.printStackTrace();
}
标签:android,锁屏
0
投稿

猜你喜欢

  • C#基于委托实现多线程之间操作的方法

    2022-07-16 23:58:16
  • Java时间转换成unix时间戳的方法

    2022-06-09 14:39:36
  • springBoot的事件机制GenericApplicationListener用法解析

    2023-09-02 14:22:26
  • java获取Date时间的各种方式汇总

    2021-12-07 16:36:16
  • 在WCF数据访问中使用缓存提高Winform字段中文显示速度的方法

    2022-11-08 10:05:09
  • C#编程和Visual Studio使用技巧(上)

    2021-10-01 00:54:27
  • java实现科研信息管理系统

    2022-05-13 02:49:41
  • Java中的Object类详细介绍

    2023-11-23 23:18:46
  • Android自动编辑文本框(AutoCompleteTextView)使用方法详解

    2023-02-05 18:17:53
  • c#创建Graphics对象的三种方法

    2022-11-24 10:20:53
  • java微信公众号开发案例

    2023-01-30 11:05:36
  • Spring Cloud Gateway网关XSS过滤方式

    2021-08-07 13:16:53
  • Java接口返回省市区树形结构的实现

    2021-10-16 05:07:05
  • springboot使用单元测试实战

    2023-05-17 11:55:29
  • java 对称加密算法实现详解

    2021-07-29 22:34:57
  • Android调试出现The selected device is incompatible问题解决

    2023-08-11 12:58:34
  • Android 6.0区别U盘和SD卡设备的方法详解

    2022-09-09 13:10:58
  • 利用C#守护Python进程的方法

    2023-01-05 13:25:56
  • 浅谈Ribbon、Feign和OpenFeign的区别

    2022-12-21 15:40:27
  • Android应用中使用SharedPreferences类存储数据的方法

    2022-06-11 20:27:08
  • asp之家 软件编程 m.aspxhome.com