程序锁的实现

看到程序锁,想到的当然就是360手机卫生等等一系列的软件管理应用了,这里将程序锁的那个输入密码的界面当成看门狗:

看门狗: 监视系统程序的运行状态(这里假设这个activity是属于360手机卫士里面的),
每打开一个程序,系统就会分配一个任务栈,每个程序一个任务栈,程序锁的原理:
当你打开一个软件,这个看门狗就能检测到你开启的是哪个软件,并可以获取包名,如果这个软件是被保护的,需要你
输入密码才能进入,其实就是在你打开这个软件后,看门狗开启另一个活动界面(比如显示输入密码),当密码输入正确,
就销毁这个页面,最后显示的自然就是你刚打开的软件页面了.

由上可知看门狗肯定是需要长期在后台运行的,需要一直监视用户当前打开的软件具体是哪个

 看门狗服务类,WatchDogService.java

/**
 * 看门狗服务,监视系统程序的运行状态(这里假设这个activity是属于360手机卫士里面的),=
 */
public class WatchDogService extends Service {
    
    private boolean flag;//看门狗运行的标记
    private ActivityManager am;
    private InnerReceiver innerReceiver;
    private String tempStopPackname;//临时停止保护的程序包名
    
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    
    @Override
    public void onCreate() {
        super.onCreate();
        //监听自定义广播
        innerReceiver = new InnerReceiver();
        registerReceiver(innerReceiver, new IntentFilter("com.mybroadcast"));
        
        am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
        flag=true;//让看门狗一直运行
        new Thread(){
            public void run() {
                while(flag){
                    //获取运行的任务栈,100代表最多获取100个任务栈,最近打开的在list的前面,后打开的在后面
                    List<RunningTaskInfo> tasks = am.getRunningTasks(100);
                    //获取第一个任务栈的顶部activity,也就是获取最近打开的程序的当前打开的活动所在的包名
                    String packageName = tasks.get(0).topActivity.getPackageName();
                    System.out.println("当前打开的程序包名为:"+packageName);
                    if("这个软件是加锁了的,且临时不保护" && !packageName.equals(tempStopPackname)){
                        Intent intent=new Intent(getApplicationContext(),LockActivity.class);//输入密码的活动
                        //由于服务是没有任务栈信息的,在服务开启activity要指定这个activity运行的任务栈
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        intent.putExtra("packname", packageName);//告诉锁的界面锁住的是哪个应用
                        startActivity(intent);
                    }
                    try {
                        Thread.sleep(50);//必须稍微睡一下,不然回收机制都没时间回收垃圾了
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();
    }
    
    /**
     * 接收自定义的广播,来取消看门狗的临时保护
     */
    private class InnerReceiver extends BroadcastReceiver{

        @Override
        public void onReceive(Context context, Intent intent) {
            System.out.println("接收到临时停止保护的自定义广播");
            tempStopPackname = intent.getStringExtra("packname");
        }
        
    }
    
    @Override
    public void onDestroy() {
        super.onDestroy();
        flag=false;//结束子线程
        unregisterReceiver(innerReceiver);
    }
    
}

显示输入密码的界面:LockActivity.java

/**
 * 作为程序锁显示的界面:必须设置该活动的启动模式为:singleInstance,并且不能在任务列表中显示(按住小房子键不会显示这个活动界面)
 */
public class LockActivity extends Activity {

    private EditText editText;
    private String packname;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lock);//布局为一个编辑框和一个确定按钮
        editText = (EditText) findViewById(R.id.editText);
        Intent intent = getIntent();
        packname = intent.getStringExtra("packname");//得到当前被锁住的应用的包名
        PackageManager pm = getPackageManager();
        try {
            //通过包名获取图标和软件名
            ApplicationInfo info = pm.getApplicationInfo(packname, 0);
            Drawable appIcon = info.loadIcon(pm);
            String appName = info.loadLabel(pm).toString();
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        
    }
    /**
     * 提交按钮的单击时间,假设密码为12345
     */
    public void btnOnClick(View v){
        String password = editText.getText().toString();
        if(TextUtils.isEmpty(password)){
            Toast.makeText(this, "请输入密码", 0).show();
        }else if(password.equals("12345")){
            Toast.makeText(this, "密码正确", 0).show();
            /*告诉看门狗这个程序密码输入正确了,可以临时的停止保护了,不然销毁后,看门狗服务又监测到
             * 这个应用需要上锁,就又弹出输入密码的界面了,通过自定义广播来通知看门狗
             */
            Intent intent=new Intent();
            intent.setAction("com.mybroadcast");
            intent.putExtra("packname", packname);//告诉看门狗停止保护的应用
            sendBroadcast(intent);
            finish();//销毁界面
        }else{
            Toast.makeText(this, "密码错误,请重新输入", 0).show();
        }
    }
    
    /**
     * 当在输入密码的界面按返回键时,应该直接回到桌面,不然会显示被锁住的软件
     */
    @Override
    public void onBackPressed() {
        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);//回到桌面
    }
    
    /**
     * 界面不可见的时候就要销毁掉这个输入密码的界面
     */
    @Override
    protected void onStop() {
        super.onStop();
        finish();
    }
}
<!-- singleInstance使得手机里只有一个实例存在,避免了按返回键软件显示出现错乱问题     excludeFromRecents代表当前这个输入密码的活动不会出现在最近任务的列表里了 -->
        <activity
            android:name=".LockActivity"
            android:launchMode="singleInstance"
            android:excludeFromRecents="true"
            android:label="@string/title_activity_lock" >
        </activity>

还会出现的问题:

1.当屏幕锁定黑屏后再去开启软件不会要求输入密码,所以还需要在看门狗服务类中注册锁屏的监听,见:锁屏与亮屏广播的注意点

2.还需要解决出现输入密码界面的速度问题,:1)任务栈的查询没必要放在子线程,所以就需要定义一个集合来装用户上锁的所有程序,2)上面获取任务栈的个数设置的100,其实设置为1个就可以了,因为只需要最新打开的软件信息,3)子线性没必要睡50ms,睡20ms就差不多了

posted @ 2016-09-07 17:48  ts-android  阅读(533)  评论(0编辑  收藏  举报