实现一个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();
}
猜你喜欢
- 入队(EnQueue) 、出队(TryDequeue) 、是否为空(IsEmpty)、获取队列内元素数量(Count)。一、Concurre
- 在java中我们常常使用加锁机制来确保线程安全,但是如果过度使用加锁,则可能导致锁顺序死锁。同样,我们使用线程池和信号量来限制对资源的使用,
- 解决 INSTALL FAILED CONFLICTING PROVIDER的问题方法 在安装Android应用时出现
- 目前为止我们已经了解了如何通过编程创建 CompletableFuture 对象以及如何获取返回值,虽然看起来这些操作已经比较方便,但还有进
- 自定义Repository接口要定义一个repository接口,你首先需要自定义一个实体类专用的Repository接口。该接口必须扩展
- 循环例子:while循环和do…while循环whlie(条件语句) {循环体}//先进行条件语句的判断,再进行循环体do {循环体}whl
- 因为某个项目需要,为团队其他兄弟姐妹开发了一个 XML 分析处理器,并将其设计为一个类库,提供相应的 API 接口。为了方便大家的使用,需要
- 1.理解全过程1.1.概述需要具备的知识Socket网络编程反向代理的理解平滑加权轮询算法的理解线程池的理解目的:实现Socket 集群服务
- 在项目开发中,经常会碰到日期处理。比如查询中,可能会经常遇到按时间段查询,有时会默认取出一个月的数据。当我们提交数据时,会需要记录当前日期,
- POM<dependency> <groupId>org.springframework.boot<
- 之前在项目中会用到在Java在后台把数据填入Word文档的模板来提供前台下载,为了自己能随时查看当时的实现方案及方便他人学习我写了这篇博客,
- JMF这个多媒体开发框架太牛了,简单的几句代码就能实现一个视频播放器的开发,厉害,就是支持的格式少了一些,没关系,这个视频播放器可以播放mp
- 1.要求输入10个整数,从大到小排序输出输入:2 0 3 -4 8 9 5 1 7 6输出:9 8 7 6 5 3 2 1 0 -4解决方法
- 一、单文件压缩 场景,文件可能比较大,需要压缩传输,比如上传和下载/// <summary&g
- 本文实例为大家分享了C#抽象类与抽象方法的具体实现代码,供大家参考,具体内容如下1.代码class Program { stat
- Logback设置property参数更多参数设置查看官方文档1.方式一:直接配置参数值<configuration>
- 什么是适配器模式? 适配器模式(Adapter):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不
- 概述Handler是Android消息机制的上层接口。通过它可以轻松地将一个任务切换到Handler所在的线程中去执行。通常情况下,Hand
- @Transactional是我们在用Spring时候几乎逃不掉的一个注解,该注解主要用来声明事务。它的实现原理是通过Spring AOP在
- 近期,Google宣布Kotlin成为了Android一级开发语言。于是就刚刚简单的研究了一下,查资料的时候发现现成的资料还是很少的,于是决