一共有 6 点小建议:
LockService 应用锁服务优化建议,服务不再继承 IntentService 了,继承 Service,在里面自己开线程。
循环的判断条件不用简单的布尔型,而用具有原子操作性的 AtomicBoolean 代替:
public class AppLockService extends Service {
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
@Override
public void onCreate() {
super.onCreate();
//...
AsyncTask.SERIAL_EXECUTOR.execute(new ServiceWorker());
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestoryed.get()) {
//...
}
}
}
@Override
public void onDestroy() {
mIsServiceDestoryed.set(true);
super.onDestroy();
}
}
目前循环里面的获取 Sp 文件的操作太多太频繁了,因为读写 Sp 文件有缓冲时间,而且又是属于读写文件,在循环这么频繁的情况下性能感觉不好而已有点慢。 而加锁时间这些变量又需要实时改变的,所以不能写成全局变量,所以优化建议是可以用全局的静态变量来存储代替 sp 文件。而 sp 文件只保存最后一次 改变的时间,如果app被杀掉,下次打开的时候,sp 文件的值付给静态变量就可以。
解锁界面,现在项目里面的解锁界面是一个 Activity,建议换成 Window 悬浮窗来实现,下面给个代码示例参考,可以直接使用或者自己实现:
public class UnlockView extends FrameLayout {
private int mFailedPatternAttemptsSinceLastTimeout = 0;
private WindowManager.LayoutParams mLayoutParams;
private WindowManager mWindowManager;
private Context mContext;
private View mUnLockView;
private Drawable iconDrawable;
private String appLabel;
private ImageView mBgView, mUnLockIcon, mBtnMore;
private TextView mUnLockAppName, mUnlockFailTip;
private LockPatternView mPatternView;
private LockPatternUtils mPatternUtils;
private LockPatternViewPattern mPatternViewPattern;
private LockAppInfo mLockAppInfo;
private ApplicationInfo mApplicationInfo;
private PackageManager mPackageManager;
public UnlockView(@NonNull Context context) {
super(context, null, 0);
init();
}
private void init() {
mContext = getContext();
mPackageManager = mContext.getPackageManager();
mUnLockView = LayoutInflater.from(mContext).inflate(R.layout.layout_unlock_view, this);
mBgView = mUnLockView.findViewById(R.id.bg_view);
mUnLockIcon = mUnLockView.findViewById(R.id.unlock_icon);
mBtnMore = mUnLockView.findViewById(R.id.btn_more);
mUnLockAppName = mUnLockView.findViewById(R.id.unlock_app_name);
mPatternView = mUnLockView.findViewById(R.id.unlock_lock_view);
mUnlockFailTip = mUnLockView.findViewById(R.id.unlock_fail_tip);
//创建悬浮窗
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
mLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSPARENT
);
mLayoutParams.gravity = Gravity.CENTER;
initLockPatternView();
}
public void setLockAppInfo(LockAppInfo lockAppInfo) {
mLockAppInfo = lockAppInfo;
}
private final static int MSG_ADDVIEW = 100;
private final static int MSG_GO_HOME = 200;
private Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_ADDVIEW:
mWindowManager.addView(UnlockView.this, mLayoutParams);
break;
case MSG_GO_HOME:
closeUnLockView();
break;
}
}
};
/**
* 打开解锁界面
*/
public void showUnLockView() {
if (mLockAppInfo == null) {
return;
}
initBgView();
mHandler.obtainMessage(MSG_ADDVIEW).sendToTarget();
}
/**
* 关闭解锁界面
*/
private boolean closeUnLockView() {
if (mWindowManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (isAttachedToWindow()) {
mWindowManager.removeViewImmediate(this);
return true;
} else {
return false;
}
} else {
try {
if (getParent() != null) {
mWindowManager.removeViewImmediate(this);
}
return true;
} catch (Exception e) {
return false;
}
}
} else {
return false;
}
}
/**
* 按Home键
*/
public void closeUnLockViewFormHomeAction() {
if (getParent() != null && mHandler != null) {
mHandler.sendEmptyMessageDelayed(MSG_GO_HOME, 500);
}
}
/**
* 背景图片
*/
private void initBgView() {
mApplicationInfo = mLockAppInfo.getAppInfo();
if (mApplicationInfo != null) {
try {
iconDrawable = mPackageManager.getApplicationIcon(mApplicationInfo);
appLabel = mLockAppInfo.getAppName();
mUnLockIcon.setImageDrawable(iconDrawable);
mUnLockAppName.setText(appLabel);
mUnlockFailTip.setText(mContext.getString(R.string.password_gestrue_tips));
mBgView.setBackground(iconDrawable);
mBgView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mBgView.getViewTreeObserver().removeOnPreDrawListener(this);
mBgView.buildDrawingCache();
Bitmap bmp = BlurUtil.drawableToBitmap(iconDrawable, mBgView);
BlurUtil.blur(mContext, BlurUtil.big(bmp), mBgView); //高斯模糊
return true;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 初始化解锁控件
*/
private void initLockPatternView() {
mPatternView.setLineColorRight(0x80ffffff);
mPatternUtils = new LockPatternUtils(mContext);
mPatternViewPattern = new LockPatternViewPattern(mPatternView);
mPatternViewPattern.setPatternListener(new LockPatternViewPattern.onPatternListener() {
@Override
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
if (mPatternUtils.checkPattern(pattern)) { //解锁成功,更改数据库状态
mPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
//TODO
closeUnLockView();
} else {
mPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
mFailedPatternAttemptsSinceLastTimeout++;
int retry = LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT - mFailedPatternAttemptsSinceLastTimeout;
if (retry >= 0) {
String format = mContext.getResources().getString(R.string.password_error_count);
mUnlockFailTip.setText(format);
}
} else {
mUnlockFailTip.setText(mContext.getResources().getString(R.string.password_short));
}
if (mFailedPatternAttemptsSinceLastTimeout >= 3) { //失败次数大于3次
mPatternView.postDelayed(mClearPatternRunnable, 500);
}
if (mFailedPatternAttemptsSinceLastTimeout >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) { //失败次数大于阻止用户前的最大错误尝试次数
mPatternView.postDelayed(mClearPatternRunnable, 500);
} else {
mPatternView.postDelayed(mClearPatternRunnable, 500);
}
}
}
});
mPatternView.setOnPatternListener(mPatternViewPattern);
mPatternView.setTactileFeedbackEnabled(true);
}
private Runnable mClearPatternRunnable = new Runnable() {
public void run() {
mPatternView.clearPattern();
}
};
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
if (getParent() != null) {
LockUtil.launchHome(mContext);
mHandler.sendEmptyMessageDelayed(MSG_GO_HOME, 500);
}
return true;
}
return false;
}
}
如果你有使用 RxJava,那就结合 RxJava 去修改吧,效果很棒。
比如,目前项目里面获取应用列表的操作是放到一个服务里面做的,当时这么做的原因是,把这样一个复杂的操作放到后台做,就不会因为
耗时而导致卡顿甚至ANR的情况。如果你用了 RxJava ,线程切换变得非常简单,可以用 RxJava 实现就行,下面给出代码示例,LoadAppHelper 是获取应用列表的帮助类,
DbManager 是数据库操作管理类,非常简单,可以自己实现,就不贴代码了:
//初始化数据
LoadAppHelper.loadAllLockAppInfoAsync(this)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.filter(new Predicate<List<LockAppInfo>>() {
@Override
public boolean test(List<LockAppInfo> lockAppInfos) throws Exception {
if (isFirstTime) {
DbManager.get()
.saveLockAppInfoListAsync(lockAppInfos)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Boolean>() {
@Override
public void accept(Boolean aBoolean) throws Exception {
animator.start();
}
});
return false;
} else {
return true;
}
}
})
.observeOn(Schedulers.newThread())
.map(new Function<List<LockAppInfo>, List<LockAppInfo>>() {
@Override
public List<LockAppInfo> apply(List<LockAppInfo> appList) throws Exception {
//数据库数据与最新获取的app数据作对比
List<LockAppInfo> dbList = DbManager.get().queryInfoList();
if (appList.size() > dbList.size()) { //如果有安装新应用
List<LockAppInfo> resultList = new ArrayList<>();
HashMap<String, LockAppInfo> hashMap = new HashMap<>();
for (LockAppInfo info : dbList) {
hashMap.put(info.getPackageName(), info);
}
for (LockAppInfo info : appList) {
if (!hashMap.containsKey(info.getPackageName())) {
resultList.add(info);
}
}
//将新应用数据插入数据库
if (resultList.size() != 0) {
DbManager.get().saveInfoList(resultList);
}
} else if (appList.size() < dbList.size()) { //如果有卸载应用
List<LockAppInfo> resultList = new ArrayList<>();
HashMap<String, LockAppInfo> hashMap = new HashMap<>();
for (LockAppInfo info : appList) {
hashMap.put(info.getPackageName(), info);
}
for (LockAppInfo info : dbList) {
if (!hashMap.containsKey(info.getPackageName())) {
resultList.add(info);
}
}
//将卸载的应用从数据库删除
if (resultList.size() != 0) {
DbManager.get().deleteInfoByList(resultList);
}
}
return DbManager.get().queryInfoList();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<LockAppInfo>>() {
@Override
public void accept(List<LockAppInfo> lockAppInfos) throws Exception {
if (lockAppInfos.size() != 0) {
animator.start();
} else {
Toast.makeText(mContext, "数据处理错误", Toast.LENGTH_SHORT).show();
}
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Toast.makeText(mContext, "数据处理错误", Toast.LENGTH_SHORT).show();
}
});
LoadAppHelper:
/**
* 加载app列表帮助类
*/
public class LoadAppHelper {
/**
* 获取手机上的所有应用
*/
private static List<ResolveInfo> loadPhoneAppList(PackageManager packageManager) {
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
return packageManager.queryIntentActivities(intent, 0);
}
/**
* 初始化推荐加锁的应用
*/
private static List<String> loadRecommendApps() {
List<String> packages = new ArrayList<>();
packages.add("com.android.gallery3d"); //相册
packages.add("com.android.mms"); //短信
packages.add("com.tencent.mm"); //微信
packages.add("com.android.contacts"); //联系人和电话
packages.add("com.facebook.katana"); //facebook
packages.add("com.facebook.orca"); //facebook Messenger
packages.add("com.mediatek.filemanager"); //文件管理器
packages.add("com.sec.android.gallery3d"); //也是个相册
packages.add("com.android.email"); //邮箱
packages.add("com.sec.android.app.myfiles"); //三星的文件
packages.add("com.android.vending"); //应用商店
packages.add("com.google.android.youtube"); //youtube
packages.add("com.tencent.mobileqq"); //qq
packages.add("com.tencent.qq"); //qq
packages.add("com.android.dialer"); //拨号
packages.add("com.twitter.android"); //twitter
return packages;
}
/**
* 将app信息封装成需要的数据
*/
private static List<LockAppInfo> loadLockAppInfo(Activity activity) {
List<LockAppInfo> list = new ArrayList<>();
try {
PackageManager mPackageManager = activity.getPackageManager();
List<ResolveInfo> resolveInfos = loadPhoneAppList(mPackageManager);
for (ResolveInfo resolveInfo : resolveInfos) {
String packageName = resolveInfo.activityInfo.packageName;
boolean isRecommend = isRecommendApp(packageName);
LockAppInfo info = new LockAppInfo(packageName, false, isRecommend);
ApplicationInfo appInfo = mPackageManager.getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
String appName = mPackageManager.getApplicationLabel(appInfo).toString();
if (!isFilterOutApps(packageName)) {
boolean isSysApp = (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
info.setLocked(isRecommend);
info.setAppName(appName);
info.setSysApp(isSysApp);
info.setAppType(isSysApp ? "SystemApp" : "OtherApp");
info.setSetUnLock(false);
info.setAppInfo(appInfo);
list.add(info);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
/**
* 异步获取
*/
public static Observable<List<LockAppInfo>> loadAllLockAppInfoAsync(final Activity activity) {
return Observable.create(new ObservableOnSubscribe<List<LockAppInfo>>() {
@Override
public void subscribe(ObservableEmitter<List<LockAppInfo>> emitter) throws Exception {
emitter.onNext(loadLockAppInfo(activity));
}
});
}
public static Observable<List<LockAppInfo>> loadLockedAppInfoAsync(final Activity activity) {
return Observable.create(new ObservableOnSubscribe<List<LockAppInfo>>() {
@Override
public void subscribe(ObservableEmitter<List<LockAppInfo>> emitter) throws Exception {
List<LockAppInfo> list = loadLockAppInfo(activity);
List<LockAppInfo> lockAppInfos = new ArrayList<>();
for (LockAppInfo info : list) {
if (info.isLocked()) {
lockAppInfos.add(info);
}
}
emitter.onNext(lockAppInfos);
}
});
}
public static Observable<List<LockAppInfo>> loadUnLockAppInfoAsync(final Activity activity) {
return Observable.create(new ObservableOnSubscribe<List<LockAppInfo>>() {
@Override
public void subscribe(ObservableEmitter<List<LockAppInfo>> emitter) throws Exception {
List<LockAppInfo> list = loadLockAppInfo(activity);
List<LockAppInfo> lockAppInfos = new ArrayList<>();
for (LockAppInfo info : list) {
if (!info.isLocked()) {
lockAppInfos.add(info);
}
}
emitter.onNext(lockAppInfos);
}
});
}
public static Observable<List<LockAppInfo>> loadSystemAppInfoAsync(final Activity activity) {
return Observable.create(new ObservableOnSubscribe<List<LockAppInfo>>() {
@Override
public void subscribe(ObservableEmitter<List<LockAppInfo>> emitter) throws Exception {
List<LockAppInfo> list = loadLockAppInfo(activity);
List<LockAppInfo> lockAppInfos = new ArrayList<>();
for (LockAppInfo info : list) {
if (info.isSysApp()) {
lockAppInfos.add(info);
}
}
emitter.onNext(lockAppInfos);
}
});
}
public static Observable<List<LockAppInfo>> loadUserAppInfoAsync(final Activity activity) {
return Observable.create(new ObservableOnSubscribe<List<LockAppInfo>>() {
@Override
public void subscribe(ObservableEmitter<List<LockAppInfo>> emitter) throws Exception {
List<LockAppInfo> list = loadLockAppInfo(activity);
List<LockAppInfo> lockAppInfos = new ArrayList<>();
for (LockAppInfo info : list) {
if (!info.isSysApp()) {
lockAppInfos.add(info);
}
}
emitter.onNext(lockAppInfos);
}
});
}
/**
* 是否是推荐加锁app
*/
private static boolean isRecommendApp(String packageName) {
List<String> packages = loadRecommendApps();
return !TextUtils.isEmpty(packageName) && packages.contains(packageName);
}
/**
* 过滤的应用白名单
*/
private static boolean isFilterOutApps(String packageName) {
return packageName.equals(Constants.APP_PACKAGE_NAME) ||
packageName.equals("com.android.settings") ||
packageName.equals("com.google.android.googlequicksearchbox");
}
}
如果你愿意,可以把服务做成远程服务,因为基于IPC实现服务,可以减少应用内存峰值,避免OOM。
关于进程保活,其实不能绝对的保证一定能成功,下面给出一些我在某星球里面收集到相关的资料,可以参考一下: