- 如我们所知,现在有很多牛逼的安全软件都带有程序锁的功能。当然,名字可能不一样,有的叫隐私保护,有的叫软件锁。等等。但是这种名字其实都是表达一种意思,就是可以给你手机里面的app加以保护。只有你给你的App设置了程序锁,这样每次在你进入这个App的时候就要先输入密码,然后才能进入。
- 程序锁功能并不是什么高大上的技术,也不是很难的技术点。于是,受到灰驹的启发,我就写了一个简单的,UI比较丑陋的程序锁的小Demo。
- 国际惯例:先分析一下做一个程序锁功能所涉及到的知识点:
-
Service
组建的使用。 -
Broadcast
组建的使用(当然,这个不是必须的,在实现这个功能上)。 -
Activity
启动模式的了解。 -
ActivityManager
的APi使用。 -
PackageManager
的API的使用。 - 数据库
SQLiteDatabase
的使用。(这个也不是必须的) -
InputMethodManager
的API的使用。(有些手机不主动打开软键盘,就需要使用这个类)
- OK,介绍就这些了,下面是代码区:
- 关于UI方面,简单说明一下,里面就一个主
Activity
,作用就是让用户选择添加需要加锁的应用,进行加锁操作。然后就是一个弹出让用户给加锁的应用输入密码的Activity
。在用户点击要打开已经加锁的应用时候弹出。
- 首先是xml代码:
-
MainActivity
的布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:gravity="center_horizontal"
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="8dp"
android:background="@android:color/darker_gray"
android:clickable="true"
android:padding="8dp"
android:text="未加锁"
android:textColor="@android:color/white"
android:textSize="16sp" />
<ListView
android:id="@+id/lockListView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/tvTitle" >
</ListView>
</RelativeLayout>
* 输入密码界面的布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:padding="10dp"
android:text="加锁了" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/textView1" />
<Button
android:onClick="open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:layout_centerHorizontal="true"
android:layout_marginTop="99dp"
android:text="芝麻开门" />
</RelativeLayout>
- xml布局就没有了。
- 需要注意一下,程序锁功能里面需要添加的权限:
<uses-permission android:name="android.permission.GET_TASKS" />
- 然后是Java代码:
- 首先是程序的
MainActivity
package com.duck.husband;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements OnItemClickListener {
private ListView listView;
private Context context;
private AppInfoDao infoDao;
private List<String> unLockedDatas;
private List<String> lockedDatas;
private List<String> adapterDatas;
private TextView tvTitle;
private LockAdapter adapter;
private WatchDogDao watchDogDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, WatchDogService.class);
startService(intent);
watchDogDao = new WatchDogDao(context);
updateData();
initTitle();
initListView();
}
private void initTitle() {
tvTitle = (TextView) findViewById(R.id.tvTitle);
tvTitle.setText("未加锁");
adapterDatas = unLockedDatas;
tvTitle.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (tvTitle.getText().toString().equals("未加锁")) {
tvTitle.setText("已加锁");
// TODO: 去 已加锁界面
adapterDatas = lockedDatas;
} else {
tvTitle.setText("未加锁");
// TODO:去 未加锁界面
adapterDatas = unLockedDatas;
}
adapter.notifyDataSetChanged();
}
});
}
private void updateData() {
infoDao = new AppInfoDao();
unLockedDatas = infoDao.getAllApps(context);
lockedDatas = watchDogDao.queryAllInfos();
for (String text : lockedDatas) {
if (unLockedDatas.contains(text))
unLockedDatas.remove(text);
}
}
private void initListView() {
setTitle("程序锁功能");
listView = (ListView) findViewById(R.id.lockListView);
adapter = new LockAdapter();
listView.setAdapter(adapter);
listView.setOnItemClickListener(this);
}
private class LockAdapter extends BaseAdapter {
@Override
public int getCount() {
return adapterDatas.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = new TextView(context);
}
TextView textView = (TextView) convertView;
textView.setPadding(8, 8, 8, 8);
textView.setTextSize(18);
textView.setText(adapterDatas.get(position));
return textView;
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
System.out.println("position: " + position);
System.out.println("id: " + id);
vtoast("position: " + position + "\n id: " + id);
if (tvTitle.getText().toString().equals("未加锁")) {
String removedPackageName = unLockedDatas.remove(position);
watchDogDao.insert(removedPackageName);
lockedDatas.add(removedPackageName);
adapter.notifyDataSetInvalidated();
} else {
String removedPackageName = lockedDatas.remove(position);
watchDogDao.delete(removedPackageName);
unLockedDatas.add(removedPackageName);
adapter.notifyDataSetInvalidated();
}
}
protected void vtoast(String text) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
}
}
- 然后是
WatchDogService
package com.duck.husband;
import java.util.List;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.IntentService;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.SystemClock;
public class WatchDogService extends IntentService {
private static final boolean DEBUG = false;
private static final String COM_DUCK_HUSBAND_UNCHECKED = "com.duck.husband.UNCHECKED";
private Context context;
private WatchDogDao watchDogDao;
private String unCheckedPackageName;
private UnCheckedReceiver receiver;
public WatchDogService() {
super("abcde");
context = this;
watchDogDao = new WatchDogDao(context);
}
@Override
public void onCreate() {
super.onCreate();
receiver = new UnCheckedReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(COM_DUCK_HUSBAND_UNCHECKED);
filter.addAction(Intent.ACTION_SCREEN_OFF);
registerReceiver(receiver, filter);
}
@Override
protected void onHandleIntent(Intent intent) {
while (true) {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> runningTasks = am.getRunningTasks(99);
String packageName = runningTasks.get(0).topActivity
.getPackageName();
// 获取最近打开的App包名
boolean b = watchDogDao.query(packageName);
if (b) {
// 说明是加锁的程序
if (packageName.equals(unCheckedPackageName)) {
} else {
Intent intent2 = new Intent(context, LockActivity.class);
intent2.putExtra("packageName", packageName);// TODO:这一行不加,就没有办法去临时取消保护了!!!
intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent2);
}
} else {
}
if (DEBUG) {
System.out.println("packageName 0:" + packageName);
}
SystemClock.sleep(300);
}
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (receiver != null) {
unregisterReceiver(receiver);
receiver = null;
}
}
class UnCheckedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
if (intent.getAction().equals(COM_DUCK_HUSBAND_UNCHECKED)) {
unCheckedPackageName = intent.getStringExtra("packageName");
System.out.println("unCheckedPackageName: "
+ unCheckedPackageName);
} else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
unCheckedPackageName = null;
}
}
}
}
}
- 然后是将需要包含的App包名存放到数据库的代码
package com.duck.husband;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class WatchDogOpenHelper extends SQLiteOpenHelper {
public static final String TABLENAME = "appLocktb";
public WatchDogOpenHelper(Context context, String name, int version) {
super(context, name, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table if not exists appLocktb(_id integer primary key, packageName text not null)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
以及
package com.duck.husband;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
public class WatchDogDao {
public static final String TABLENAME = WatchDogOpenHelper.TABLENAME;
public static final String PACKAGENAME = "packageName";
private WatchDogOpenHelper openHelper;
public WatchDogDao(Context context) {
openHelper = new WatchDogOpenHelper(context, "watchDog.db", 1);
// "create table if not exists appLocktb(_id integer primary key, packageName text not null)");
}
/**
* 插入一条数据,如果数据已经存在,就不插入
*
* @param packageName
* @return 插入成功.失败
*/
public boolean insert(String packageName) {
boolean insert = false;
SQLiteDatabase db = openHelper.getWritableDatabase();
Cursor cursor = db.rawQuery(
"select * from appLocktb where packageName=?",
new String[] { packageName });
if (cursor != null) {
insert = cursor.moveToNext();
cursor.close();
}
ContentValues values = new ContentValues();
values.clear();
if (insert) {
return insert;
} else {
values.put(PACKAGENAME, packageName);
long data = db.insert(TABLENAME, null, values);
db.close();
if (data != -1) {
return true;
} else {
return false;
}
}
}
/**
* 删除一条数据,如果数据不存在,就不删除
*
* @param packageName
* @return 删除成功.失败
*/
public boolean delete(String packageName) {
boolean delete = false;
SQLiteDatabase db = openHelper.getWritableDatabase();
Cursor cursor = db.rawQuery(
"select * from appLocktb where packageName=?",
new String[] { packageName });
if (cursor != null) {
delete = cursor.moveToNext();
cursor.close();
}
ContentValues values = new ContentValues();
values.clear();
if (delete) {
// 说明有这条数据,删除
int data = db.delete(TABLENAME, PACKAGENAME + "=?",
new String[] { packageName });
db.close();
if(data>0){
return true;
}else {
return false;
}
} else {
// 说明没有有这条数据,删除
return false;
}
}
/**
* 查询数据库有没有对应的数据
* @param packageName
*/
public boolean query(String packageName){
SQLiteDatabase db = openHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select * from appLocktb where packageName=?",
new String[] { packageName });
if(cursor!=null){
boolean moveToNext = cursor.moveToNext();
cursor.close();
db.close();
if(moveToNext)
return true;
else {
return false;
}
}
return false;
}
public List<String> queryAllInfos(){
List<String> packageNames = new ArrayList<String>();
SQLiteDatabase db = openHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select * from appLocktb", null);
if(cursor!=null){
while(cursor.moveToNext()){
String name = cursor.getString(cursor.getColumnIndex(PACKAGENAME));
packageNames.add(name);
}
cursor.close();
}
db.close();
return packageNames;
}
}
- 然后是获取手机里面已安装应用的工具类
package com.duck.husband;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
public class AppInfoDao {
/**
* 获取手机中所有的app包名集合
* @param context
* @return 包名的集合
*/
public List<String> getAllApps(Context context){
List<String> packageNames = new ArrayList<String>();
PackageManager pm = context.getPackageManager();
List<PackageInfo> infos = pm.getInstalledPackages(0);
for (PackageInfo info : infos) {
String packageName = info.packageName;
packageNames.add(packageName);
}
return packageNames;
}
}
- 最后是输入密码的界面:
package com.duck.husband;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.Toast;
public class LockActivity extends Activity {
private static final String COM_DUCK_HUSBAND_UNCHECKED = "com.duck.husband.UNCHECKED";
private EditText etPwd;
private Context context;
private String packageName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = this;
setContentView(R.layout.lock_ui);
packageName = getIntent().getStringExtra("packageName");
etPwd = (EditText) findViewById(R.id.et_pwd);
etPwd.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (hasFocus) {
// //显示软键盘//
imm.showSoftInputFromInputMethod(v.getWindowToken(), 0);
} else {
// 隐藏软键盘
//
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
}
});
}
public void open(View view) {
String text = etPwd.getText().toString().trim();
if (text.equalsIgnoreCase("123")) {
Intent intent = new Intent(COM_DUCK_HUSBAND_UNCHECKED);
intent.putExtra("packageName", packageName);
sendBroadcast(intent);
finish();
} else {
vtoast("密码不对");
}
}
@Override
public void onBackPressed() {
/*
* <activity android:name="com.android.launcher2.Launcher"
* android:launchMode="singleTask" android:clearTaskOnLaunch="true"
* android:stateNotNeeded="true" android:theme="@style/Theme"
* android:screenOrientation="nosensor"
* android:windowSoftInputMode="stateUnspecified|adjustPan">
* <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>
* </activity>
*/
// 打开桌面
Intent intent = new Intent();
intent.setAction("android.intent.action.MAIN");
intent.addCategory("android.intent.category.HOME");
startActivity(intent);
// super.onBackPressed();
}
@Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
}
protected void vtoast(String text) {
Toast.makeText(context, text, Toast.LENGTH_SHORT).show();
}
}
- 以上代码运行OK。
- 简单说明一下:
- 对于
MainActivity
里面的数据操作,还是比较生硬的,应该可以有更好的方式。 - 特别要注意的是广播的使用,我在写这个Demo的时候,差不多浪费了20多分钟就是为了处理广播。其实广播相对来说并不难。但是使用起来还是比较繁琐的。首先,你要搞一个意图,意图里面要包含你的行为(和数据,数据可以没有),然后以发送广播的方式发送这个意图。然后,你要搞一个对应的接收器,去接收对应的意图,和里面的数据(如果没有发数据,那就没有数据收到。只是收到意图了)。但是,如果就到此为止了,那这个广播就白做了。你还要在你需要接收广播的地方,先去注册这个广播.(这个好像也可以直接写在清单文件里面),注册了之后,算是可以使用这个广播了。然后往往还要去反注册这个广播,在不再使用的时候。总之就是比较麻烦吧。如果仅仅是为了数据的传递。个人更倾向于使用回调接口的形式来传递。但是这次为什么使用广播?因为我对广播不熟悉,也希望通过这个Demo,去熟悉一下广播的使用。
- 这里面的数据库的操作就没有什么说的,因为这真的是一个非常简单是表,里面几乎没有内容。
- 最后需要稍微注意一下的是,有的手机在你的
EditText
里面不主动打开软件盘。你点半天就是不出来软键盘,这样你就没有办法输入了。于是,这时候就需要去主动打开软键盘让用户可以输入。