转自:http://shigongbo.blog.163.com/blog/static/976090201452572535715
今天看到某Android安全软件有一个安全锁的功能,使用的时候,点击返回竟然是无效的,必须要用户输入密码!这用户体验也太差了!在网上查了一下,发现许多人都是在Activity中显示解锁画面,并屏蔽返回键。我感觉这样用户体验很差,于是就想自己实现一个。现在把主要思路和代码分享一下。
其实这个程序核心代码很少,不超过一百行就搞定。不过其中牵扯到的Android知识点很多,现在听我从头道来。
我们先来解释一下APPLock的原理。
用户启动APPLock,此时会列出所有的应用程序,选择要Lock的应用,将此应用的信息(包名)保存到数据库或文件。
并启动一个Service,我们称其为LockService,在后台一直监听,查看要Lock的应用是否被启动。
当用户启动了要Lock的应用,LockService就会察觉到,此时显示密码界面,让用户输入密码,密码正确后,密码界面消失。
如果密码界面正在显示的时候,用户点击返回,会怎么样?那肯定是密码界面消失,要启动的应用显示出来,这就不能达到应用锁的效果,所以开发者选择了在密码界面屏蔽返回键的方式。体验太差!
首先,要解决的就是怎么避免使用Activity显示解锁画面并屏蔽返回键这种不好的用户体验。
既要显示画面,又要不屏蔽返回键,怎么实现呢?
Service!对,就是在Service中显示画面。使用WindowManager加载layout的方式,让解锁画面显示在最前端。
View mLockView; TextView mTextView; WindowManager mWindowManager = null; mContext = getApplicationContext(); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.RGBA_8888); params.gravity = (Gravity.CENTER); mLockView= layoutInflater.inflate(R.layout.lock_app_service, null); mTextView = (TextView) mLockView.findViewById(R.id.service_input_pwd); if (mWindowManager == null) { mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); } if (mWindowManager != null && mLockView!= null) { mWindowManager.addView(mLockView, params); }把这段加到LockService中,它会显示一个覆盖屏幕的画面,也就是你xml定义的文件lock_app_service。
要使用这个功能,你还要在Manifest中添加权限
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
这样这个画面运行的时候就会在最前端覆盖整个屏幕了。
正式因为这是用WindowManager在最前端画出的,所以当前运行的Activity并不受影响,还是在正常运行,并不会改变生命周期,你可以在onPause中打印Log来印证,你会发现onPause并不会调用。
现在再看看怎么检测要Lock的应用开始启动了。Android中启动一个Activity的时候并没有对应的广播事件。启动一个Activity,首先是发送Intent,ActivityManagerService(AMS)会处理这个Intent。首先AMS会检测这个要启动的Activity是否存在,然后判断判断启动者是否有权限启动被启动的Activity(根据 PID和UID ),然后把被启动的Activity压入栈中。
我们就是根据这一特点,来判断刚刚启动的Activity是否为被Lock的。
从Log信息可以发现,此时出现的肯定是当前正在运行的应用的包名和Activity名字。ActivityManager mActivityManager;
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
ComponentName topActivity = mActivityManager.getRunningTasks(1).get(0).topActivity; String packageName = topActivity.getPackageName(); String className = topActivity.getClassName(); Log.v("LockService", "packageName == " + packageName); Log.v("LockService", "className == " + className);
要运行这个功能,需要添加权限
OK,核心功能就只有这么几行,是不是不超过一百行!<uses-permission android:name="android.permission.GET_TASKS"/>
剩下的工作就是界面配置和Service启动方面的了。我在这里只说一下思路。
1. 启动LockActivity。
这是一个含有list的界面,外加一个Lock开关按钮。当开关On状态,启动LockService。Off状态关闭LockService。
list中列出所有的APP应用。获取已经安装应用,代码如下
当用户点击list中的Item时候,将该应用的信息保存到数据库或者文件中。List<PackageInfo> packages = getPackageManager().getInstalledPackages(0); for(int i=0;i<packages.size();i++) { PackageInfo packageInfo = packages.get(i); Log.v("LockActivity", "packageInfo.packageName == " + packageInfo.packageName); Log.v("LockActivity", "packageInfo.versionName == " + packageInfo.versionName); Log.v("LockActivity", "packageInfo.versionCode == " + packageInfo.versionCode); }
2. LockService运行后,在其中写一个线程,不停地循环判断Activity栈顶是不是要Lock的Activity。
是,则显示密码界面,并保存当前状态。否则continue。
当用户输入了正确的密码,这则消失掉界面,代码为
mWindowManager.removeView(mLockView);这样就显示了真正的Activity了。
当用户点击了返回,由于我们的LockService中的WindowManager并没有改变实际的启动Activity的生命周期,所以返回键还是作用在实际Activity上了,所以实际Activity消失,此时根据后台的循环判断,最上次的Activity栈已经不是要被Lock的,根据刚才保存的状态,同样将密码界面消失掉。
OK。大功告成。整个应用程序安全管家就完成了。