最近在做项目时,遇到一个这样的问题,如何让我们的App在“系统设置”里面,不被手动强制停止和卸载?
首先,我们分析一下:
- 要实现不被手动停止,必须让系统设置的App详情页面的“强制停止”按钮被置灰才能做到。
- 要实现不被手动卸载,必须让App 获取系统的设备管理权限。
要实现以上两点,我们必须想办法让App在启动时自动获取系统的设备管理权限。接下来,我们通过查阅相关资料,需要按照以下步骤来做:
第一步:实现一个广播监听器,继承DeviceAdminReceiver。如下:
public class MyAdminReceiver extends DeviceAdminReceiver {
private static final String TAG = "CustomAdminReceiver";
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
}
@Override
public void onEnabled(Context context, Intent intent) {
super.onEnabled(context, intent);
}
@Override
public void onDisabled(Context context, Intent intent) {
// onDisabled
}
}
第二步:在工程的res目录下,创建xml目录,在xml目录下创建my_admin.xml文件,具体内容如下:
<?xml version="1.0" encoding="utf-8"?>
<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
<uses-policies>
<!--设置密码规则-->
<limit-password />
<!--监控登录次数-->
<watch-login />
<!--重置密码-->
<reset-password />
<!--强制锁屏-->
<force-lock />
<!--清空数据(恢复出厂设置)-->
<wipe-data />
</uses-policies>
</device-admin>
第三步:在AndroidManifest.xml清单文件,添加以下内容:
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
<receiver
android:name=".MyAdminReceiver"
android:exported="false"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/my_admin" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
第四步:创建DeviceAdminUtil类,主要是做相关初始化,设备管理权限获取等。具体如下:
public class DeviceAdminUtil {
private static DeviceAdminUtil instance;
private DevicePolicyManager devicePolicyManager;
private ComponentName componentName;
private Context mContext;
public static synchronized DeviceAdminUtil getInstance() {
if (instance == null) {
instance = new DeviceAdminUtil();
}
return instance;
}
/**
* 初始化“设备管理权限的获取”
*
* @param context
*/
public void init(Context context) {
mContext = context.getApplicationContext();
// 获取系统管理权限
devicePolicyManager = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
// 申请权限
componentName = new ComponentName(mContext, MyAdminReceiver.class);
}
/**
* 判断App是否已激活获取了设备管理权限,true:已激活,false:未激活
*
* @return
*/
private boolean isAdminActive() {
// 判断组件是否有系统管理员权限
return devicePolicyManager.isAdminActive(componentName);
}
public void lockScreen() {
if (isAdminActive() && devicePolicyManager != null) {
// 立刻锁屏
devicePolicyManager.lockNow();
}
}
}
经过上述4步操作,已基本实现我们当初的想法。现在我们来验证下。
首先:那我们在App的Application的onCreate方法调用
DeviceAdminUtil.getInstance().init(this);
然后,再某一个按钮点击事件里,调用
DeviceAdminUtil.getInstance().lockScreen();
接下来,我们运行下App,点击那个按钮并没有锁屏,在系统设置的App详情页面,我们也发现App还是可以手动强制停止,还是可以手动卸载。怎么回事呢?
我们调试发现,做了上述4步,devicePolicyManager.isAdminActive(componentName)这个方法返回仍是false,也就是说App并没有被激活。原因找到了,我们就要想办法激活App。我们查阅DevicePolicyManager的源码,里面有很多方法,其中有一个方法如下:
/**
* @hide
*/
public void setActiveAdmin(@NonNull ComponentName policyReceiver, boolean refreshing) {
setActiveAdmin(policyReceiver, refreshing, myUserId());
}
这个方法就是激活App获取设备管理权限的方法,但被@hide了,也就是说这个方法只有系统源码层才能直接调用,应用层不能直接调用。到这里,有经验的小伙伴应该会想到,用反射来调用setActiveAdmin方法,实现如下:
private void setDeviceAdminActive(boolean active) {
try {
if (devicePolicyManager != null && componentName != null) {
Method setActiveAdmin = devicePolicyManager.getClass().getDeclaredMethod("setActiveAdmin", ComponentName.class, boolean.class);
setActiveAdmin.setAccessible(true);
setActiveAdmin.invoke(devicePolicyManager, componentName, active);
}
} catch (Exception e) {
LogUtil.e(e);
}
}
这里给出DeviceAdminUtil类的最终实现,如下:
public class DeviceAdminTool {
private static DeviceAdminUtil instance;
private DevicePolicyManager devicePolicyManager;
private ComponentName componentName;
private Context mContext;
public static synchronized DeviceAdminUtil getInstance() {
if (instance == null) {
instance = new DeviceAdminUtil();
}
return instance;
}
/**
* 初始化“设备管理权限的获取”
*
* @param context
*/
public void init(Context context) {
mContext = context.getApplicationContext();
// 获取系统管理权限
devicePolicyManager = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
// 申请权限
componentName = new ComponentName(mContext, MyAdminReceiver.class);
setDeviceAdminActive(true);
}
/**
* 判断App是否已激活获取了设备管理权限,true:已激活,false:未激活
* app预装,启动后要授权静默激活
*
* @return
*/
private boolean isAdminActive() {
// 判断组件是否有系统管理员权限
return devicePolicyManager.isAdminActive(componentName);
}
public void lockScreen() {
if (isAdminActive() && devicePolicyManager != null) {
// 立刻锁屏
devicePolicyManager.lockNow();
}
}
private void setDeviceAdminActive(boolean active) {
try {
if (devicePolicyManager != null && componentName != null) {
Method setActiveAdmin = devicePolicyManager.getClass().getDeclaredMethod("setActiveAdmin", ComponentName.class, boolean.class);
setActiveAdmin.setAccessible(true);
setActiveAdmin.invoke(devicePolicyManager, componentName, active);
}
} catch (Exception e) {
LogUtil.e(e);
}
}
}
以上,在Android7.1,Android8.1上测试有效。
大家如果有好的建议,请留言交流,谢谢~~
本文章已同步发表在此技术公众号,欢迎大家关注,转发。