Android中程序锁-不断监听

来源:互联网 发布:js 数组值是否存在 编辑:程序博客网 时间:2024/06/03 18:49

程序锁-不断监听

原理 : 由于android/ios在用户启动一个程序之前不会发广播通知当前启动的应用是那个应用(考虑到系统安全),市面上的所有程序锁都是利用一个服务,不断监听手机上运行的最前端进程,如果是需要锁定的应用的进程,那么就开启一个Activity将最前端进程覆盖,密码输入正确将Activity结束,暴露出后面的进程.(不断的监听肯定是非常消耗资源和电量的)

  • 一.创建服务,持续监听
  • 1.提供给用户一个按键,打开或关闭服务,服务打开时不断监听运行在手机的最前端的栈的信息,代码如下:

    boolean flag = true;am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);//获取Activity管理器lockDao = AppLockDao.getInstance(ctx);while(flag){    tasks = am.getRunningTasks(1);    taskInfo = tasks.get(0);    packageName = taskInfo.topActivity.getPackageName();    if(lockDao.isInLockDB(packageName)){    //判断当前Activity是否为加锁应用        Intent intent = new Intent(ctx, InputLockPwdActivity.class);        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);        intent.putExtra("packageName", packageName);        startActivity(intent);    }    try {        Thread.sleep(100);    } catch (InterruptedException e) {        e.printStackTrace();    }}
    • 整个过程是非常耗时的,我们需要放在一个子线程中进行
    • 其中lockDao是操作数据库的工具类;
    • 通过Activity管理器可以获取当前正在运行的(最前端)的任务栈,获取一个就可以了.返回值是一个集合,我们只需要获取第一个元素即可;
    • 通过任务栈信息对象可以获取栈顶Activity,也就是用户正在操作的第一个Activity,我们可以通过Activity对象获取包名信息;
    • 然后通过包名可以在加锁数据库中查询该包名对应的应用是否需要加锁;
    • 如果需要加锁,我们启动一个Activity覆盖在当前Activity之上,就达到了加锁的目的,当然,当前是一个服务,在服务中启动一个Activity需要一个flag值;
    • 我们不可能每时每刻都监听,手机电量也不允许我们这么做,我们每100毫秒监听一次.
    • 一个基本的加密覆盖也就完成了,但是此时我们输入正确的密码,当前服务还会继续判断当前运行的第一个Activity是否需要加锁,就会继续覆盖上去,需要我们解锁
  • 2.创建一个集合,用于保存用于已经输入过密码的程序,当我们再次打开应用时判断当前进程是否在集合中,如果在则不覆盖当前Activity,改变后通过意图打开一个Activity代码如下:

    if(!allowPackageList.contains(packageName)){    Intent intent = new Intent(ctx, InputLockPwdActivity.class);    ...    startActivity(intent);}
  • 3.如何向集合中添加元素,我们需要在输入密码的Activity中发广播通知服务,当前输入密码的应用包名.

    if("123".equals(inputPwd)){    //发送广播,通知监听服务,当前的程序已经输入了正确的密码可以进入    Intent intent = new Intent();    intent.setAction(MyConstances.KEY_PWD_TRUE);    intent.putExtra(MyConstances.KEY_PACKAGE_NAME, packageName);    sendBroadcast(intent);    finish();//关闭当前Activity}
  • 4.在服务中注册一个广播接收者,处理接收到的广播(将包名添加到允许进入的集合列表中),代码如下:

    receiver = new MyReceiver();IntentFilter filter = new IntentFilter();filter.addAction(MyConstances.KEY_PWD_TRUE);//接收输入了正确密码的广播registerReceiver(receiver , filter ); private class MyReceiver extends BroadcastReceiver{public void onReceive(Context context, Intent intent) {    if(MyConstances.KEY_PWD_TRUE.equals(intent.getAction())){        //如果接收到密码输入正确的Action        allowPackageList.add(intent.getStringExtra(MyConstances.KEY_PACKAGE_NAME));    }}

    }

    • 这样我们就可以在用户输入正确密码时,不再监听当前应用,让用户进入
  • 5.但是我们又不希望输入密码后,需要重启服务,下次进入才能再次要求输入密码,所以我们设定,当用户锁屏后将允许进入的集合列表清空,解锁后进入密码应用需要重新输入密码,我们需要再添加一个接收的广播频段,用于接收锁屏广播.

    • 注册广播时的意图过滤器中添加,在广播接收者的onReceive方法中添加

      filter.addAction(Intent.ACTION_SCREEN_OFF);//接收锁屏广播    else if(Intent.ACTION_SCREEN_OFF.equals(intent.getAction())){    allowPackageList.clear();//将允许进入的集合清空,表示锁屏后,重新开启屏幕,需要重新输入密码}
  • 6.点击返回键时,我们仍然可以看到后面覆盖的页面,只是一闪就又进入输入密码页面了,但是多次点击返回键就有可能将用户的私密信息获取了,我们需要在输入密码的Activity中重写返回键的点击事件,点击返回键时直接退回到桌面.代码如下:

    public boolean onKeyDown(int keyCode, KeyEvent event) {if(keyCode == KeyEvent.KEYCODE_BACK){// 判断 是否是点击了后退按扭// 进入桌面//<intent-filter>// <action android:name="android.intent.action.MAIN" />// <category android:name="android.intent.category.HOME" />// <category android:name="android.intent.category.DEFAULT" />//<category android:name="android.intent.category.MONKEY"/>//</intent-filter>Intent intent = new Intent();intent.setAction("android.intent.action.MAIN");intent.addCategory("android.intent.category.HOME");intent.addCategory("android.intent.category.DEFAULT");intent.addCategory("android.intent.category.MONKEY");startActivity(intent);finish();return true;}return super.onKeyDown(keyCode, event);

    }

    • 进入桌面的意图过滤器是从系统源码的桌面应用中扒出来的
  • 7.这样功能就基本实现了,但是对于手机将是一个巨大的负担,开启服务后需要不停的,快速的监听当前最前端的应用栈,并且每监听一次还要查询一次数据库,任务量将是巨大的,我们需要改进,让手机的负担减轻

    • 首先,我们将所有执行在循环监听中的变量改为成员变量,这样就不需要每次都重新分配存储空间,可以稍微改善一点效率
    • 其次,我们将数据库中需要加锁的应用在服务开启时就加载到内存中使用一个集合存储起来,这样就不需要每拿到一个包名都查询一下数据库,可以较高的提高效率,代码如下:在服务的onCreate()中

      needLockedList = lockDao.getAllPackageList(); if(needLockedList.contains(packageName)){//改变当前包名是否需要加锁的判断条件
    • 最后,在用户锁屏时我们不需要监听最前端的栈状态,所以我们在用户锁屏时,停止监听(flag值为false),在用户解锁时开启监听,接收解锁屏幕的广播,代码如下:

      filter.addAction(Intent.ACTION_SCREEN_ON);//屏幕解锁广播//接收到不同广播的处理if(Intent.ACTION_SCREEN_OFF.equals(intent.getAction())){    allowPackageList.clear();//将允许进入的集合清空,表示锁屏后,重新开启屏幕,需要重新输入密码    flag = false;//将循环条件改变,结束循环,停止监听}else if(Intent.ACTION_SCREEN_ON.equals(intent.getAction())){    startListenAlong();//屏幕解锁时重新开启监听服务,腾讯的"内核级"优化}
  • 8.经过测试我们发现,当我们开启监听服务后,添加进来的加锁应用不能立即锁定,需要手动重新开启监听服务后才能生效.这时由于我们将数据库中需要加锁的应用添加到了集合中,再次添加时将应用添加到了锁定数据库中,我们的集合没有变化导致的,我们需要一个内容观察者观察加锁数据库的变化,动态的改变集合中的元素.代码如下:

    observer = new MyObserver(new Handler());uri = Uri.parse("content://cn.itcast.content.observer.update.db");//记得加上前缀getContentResolver().registerContentObserver(uri, true, observer);//第二个参数表示只要前面匹配就可以private class MyObserver extends ContentObserver{public MyObserver(Handler handler) {    super(handler);}public void onChange(boolean selfChange) {//数据库内容变化时,重写获取数据库中的内容    needLockedList = lockDao.getAllPackageList();}

    }

    • 当然我们还需要在数据库内容变化时通知内容观察者,数据库内容发生变化,代码如下:

      // 通知对应的内容观察者,数据库内容变化,后面的参数可以为null
      uri = Uri.parse(“content://cn.itcast.content.observer.update.db”);//记得加上前缀
      ctx.getContentResolver().notifyChange(uri, null);

  • 其中还有一个Bug我们在启动模式中介绍
2 0
原创粉丝点击