android App Lock開發(fā)半等,主要有幾個點:
第一個是所謂的應(yīng)用鎖带欢,就是在應(yīng)用打開的時候覆蓋一個Activity(或者是一個全局的Dialog念颈,甚至WindowManager懸浮窗也可以)在應(yīng)用上面汗销,要通過解鎖才能夠看到應(yīng)用跟束。
所以,問題來了:1萨咕、怎么知道用戶打開和關(guān)閉了加鎖的應(yīng)用统抬。2、用戶關(guān)閉解鎖界面的時候怎么把界面底下的應(yīng)用也關(guān)了。
問題一:
android里面沒有直接能監(jiān)聽?wèi)?yīng)用打開和關(guān)閉的方法聪建,所以做法是不斷遍歷應(yīng)用棧钙畔,獲取棧頂?shù)膽?yīng)用的包名來對比,如果是加鎖的包名金麸,則彈出解鎖界面擎析,如果不是則不處理。
獲取棧頂包名的方法有好幾個挥下,根據(jù)不同的android版本方法也不一樣叔锐,在android5.0以上,推薦使用UsageStatsManager來獲取见秽,具體方法:
public String getLauncherTopApp(Context context, ActivityManager activityManager) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
List<ActivityManager.RunningTaskInfo> appTasks = activityManager.getRunningTasks(1);
if (null != appTasks && !appTasks.isEmpty()) {
return appTasks.get(0).topActivity.getPackageName();
}
} else {
//5.0以后需要用這方法
UsageStatsManager sUsageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
long endTime = System.currentTimeMillis();
long beginTime = endTime - 10000;
String result = "";
UsageEvents.Event event = new UsageEvents.Event();
UsageEvents usageEvents = sUsageStatsManager.queryEvents(beginTime, endTime);
while (usageEvents.hasNextEvent()) {
usageEvents.getNextEvent(event);
if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
result = event.getPackageName();
}
}
if (!android.text.TextUtils.isEmpty(result)) {
return result;
}
}
return "";
}
使用UsageStatsManager需要獲取權(quán)限相關(guān)代碼:
/**
* 判斷是否已經(jīng)獲取 有權(quán)查看使用情況的應(yīng)用程序 權(quán)限
*
* @param context
* @return
*/
public static boolean isStatAccessPermissionSet(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
try {
PackageManager packageManager = context.getPackageManager();
ApplicationInfo info = packageManager.getApplicationInfo(context.getPackageName(), 0);
AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, info.uid, info.packageName);
return appOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, info.uid, info.packageName) == AppOpsManager.MODE_ALLOWED;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} else {
return false;
}
}
/**
* 查看是存在查看使用情況的應(yīng)用程序界面
*
* @return
*/
public static boolean isNoOption(Context context) {
PackageManager packageManager = context.getPackageManager();
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
List<ResolveInfo> list = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
return list.size() > 0;
}
/**
* 轉(zhuǎn)跳到 有權(quán)查看使用情況的應(yīng)用程序 界面
*
* @param context
*/
public static void startActionUsageAccessSettings(Context context) {
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
context.startActivity(intent);
}
這里有坑愉烙,就是有些手機是不存在這個權(quán)限和申請這個權(quán)限的設(shè)置界面的,所以要先判斷是否存在這個節(jié)面解取。
應(yīng)用鎖的主要核心代碼就這些步责。
問題二:
只需要修改返回鍵,使返回鍵的功能跟Home賤一樣即可:
/**
* Home鍵操作
*/
public static void goHome(BaseActivity activity) {
Intent homeIntent = new Intent(Intent.ACTION_MAIN);
homeIntent.addCategory(Intent.CATEGORY_HOME);
activity.startActivity(homeIntent);
activity.finish();
}
關(guān)于加載手機的應(yīng)用列表:
加載手機的應(yīng)用列表是耗時的禀苦,需要放在子線程中加載蔓肯,但如果是在進(jìn)入列表頁面的時候才加載,那樣時間也會比較長振乏,最好的方法是一進(jìn)應(yīng)用就開啟一個后臺服務(wù)去加載好蔗包,在進(jìn)入列表頁面的時候直接取,這樣體驗會好很多慧邮。
怎么判斷應(yīng)用的打開和關(guān)閉:
上面說到调限,因為判斷是否打開了加鎖應(yīng)用是在一個死循環(huán)里面進(jìn)行的,如果用戶解鎖了误澳,就不應(yīng)該再次加鎖耻矮,應(yīng)該等用戶退出這個應(yīng)用的時候再加鎖,不然就會導(dǎo)致解鎖了就立馬又彈出解鎖界面這樣的錯誤忆谓。所以要有一個標(biāo)志位去判斷裆装。
怎么做:
在用戶解鎖成功后將解鎖的應(yīng)用的包名保存在SP文件中,在死循環(huán)遍歷里面去判斷倡缠。如果保存的包名跟當(dāng)前獲取的棧頂包名一樣哨免,則應(yīng)用正在打開,不需要加鎖昙沦,如果不一樣琢唾,則用戶已經(jīng)關(guān)閉了當(dāng)前應(yīng)用,需要重新加鎖桅滋,例子:
//當(dāng)前棧頂?shù)陌?String packageName = getLauncherTopApp(LockService.this, activityManager);
//解鎖后保存的包名
String savePkgName = SpUtil.getInstance().getString(AppConstants.LOCK_LAST_LOAD_PKG_NAME, "");
if (!TextUtils.isEmpty(savePkgName)) {
if (!TextUtils.isEmpty(packageName)) {
if (!savePkgName.equals(packageName)) {
//再加多層判斷慧耍,更加準(zhǔn)確,如果返回了桌面
if (getHomes().contains(packageName) || packageName.contains("launcher")) {
//這是設(shè)置相關(guān)的代碼丐谋,表示是否設(shè)置了不鎖這個應(yīng)用芍碧,可忽略
boolean isSetUnLock = mLockInfoManager.isSetUnLock(savePkgName);
if (!isSetUnLock) {
//加鎖
mLockInfoManager.lockCommApplication(savePkgName);
}
}
}
}
}
/**
* 獲得屬于桌面的應(yīng)用的應(yīng)用包名稱
*/
private List<String> getHomes() {
List<String> names = new ArrayList<>();
PackageManager packageManager = this.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo ri : resolveInfo) {
names.add(ri.activityInfo.packageName);
}
return names;
}
因為應(yīng)用列表是存在數(shù)據(jù)庫里面的,那么加鎖解鎖的操作其實就是修改數(shù)據(jù)庫中的一個標(biāo)志位而已号俐。
Demo已經(jīng)初步改版好泌豆,地址:https://github.com/lizixian18/AppLock
感謝各位的關(guān)注。