Android App不被手动强制停止和卸载的实现

最近在做项目时,遇到一个这样的问题,如何让我们的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上测试有效。

大家如果有好的建议,请留言交流,谢谢~~
 

本文章已同步发表在此技术公众号,欢迎大家关注,转发。

<think>好的,我现在需要帮用户设计关于APP安装卸载的测试用例。首先,我得回顾一下用户的需求,他们想了解如何设计这方面的测试用例,并且之前的引用内容提到了一些相关的例子,比如同操作系统、杀毒软件的影响、安装路径等。过用户现在特别关注的是APP的安装卸载功能,所以需要覆盖各种可能的场景。 首先,安装测试需要考虑首次安装的情况,比如在从未安装过APP的设备上进行安装,验证基本功能是否正常。然后要考虑升级安装,包括存在旧版本且正在运行或者未运行的情况,这时候安装程序是否能够正确处理。还要考虑同路径的安装,比如用户选择默认路径或自定义路径,特别是当路径中有空格或特殊字符时,安装是否成功。另外,磁盘空间足是一个关键点,必须测试安装过程中是否有足够的空间,以及安装程序是否给出明确的错误提示。 接下来是卸载测试。正常卸载后,需要检查所有相关文件注册表项是否被清除,同时确保卸载后重新安装没有问题。还要考虑在卸载过程中突然中断的情况,比如强制关闭安装程序或断电,系统是否能恢复稳定状态。此外,部分卸载的情况,比如用户手动删除了一些文件,这时候再次安装或卸载是否会出错。 环境兼容性也是重要的一部分。同的操作系统版本、硬件配置、网络状态(比如断网安装)都可能影响安装卸载过程。同时,第三方软件如杀毒工具可能会干扰安装进程,需要测试这些情况下的兼容性。 异常场景测试包括多个安装进程同时运行,安装包损坏或签名错误,以及静默安装的情况。静默安装通常用于企业环境,需要确保在没有用户交互的情况下安装成功。 最后,用户体验方面,安装卸载的界面是否友好,提示信息是否清晰,操作流程是否简便,这些都需要验证。同时,安装后的APP图标名称是否正确,卸载后的快捷方式是否被移除。 现在需要确保测试用例覆盖全面,没有遗漏重要场景。比如,用户提到的引用中提到的存在杀毒软件、同路径安装、磁盘空间足等情况都要包含进去。此外,还需要考虑同平台的特殊情况,比如AndroidiOS在安装卸载时的差异,比如iOS的TestFlight安装或Android的APK手动安装等。 可能还需要考虑权限问题,比如在安装时是否请求了必要的权限卸载后这些权限是否被正确撤销。另外,对于需要登录的APP卸载后重新安装时是否保存了用户数据,或者是否按预期清除了数据。 最后,需要整理这些测试点,分门别类,形成结构清晰的测试用例列表,方便用户参考执行。同时,提醒用户根据实际应用场景调整用例,确保覆盖所有可能的情况。</think>### APP安装卸载功能测试用例设计 #### 一、安装功能测试 1. **首次安装验证** - 在未安装过该APP的设备上执行安装,验证基础功能是否正常[^1] - 安装过程中检查默认路径、图标、名称是否符合预期 - 安装完成后验证启动、登录、权限请求等核心流程 2. **覆盖安装场景** - 存在旧版本且APP正在运行时执行安装,检测进程终止文件覆盖能力 - 存在旧版本但未运行时安装,验证数据迁移完整性(如用户配置、缓存文件) - 存在更高版本时尝试安装,系统应提示版本冲突[^1] 3. **路径与存储测试** - 选择自定义路径(含空格/特殊字符路径如`C:\Program Files\测试APP`) - 安装目录磁盘空间足时,验证错误提示与中断机制[^1] ```python # 伪代码示例:磁盘空间检测逻辑 if free_space < package_size * 3: throw InstallationError("磁盘空间足") ``` 4. **异常中断测试** - 安装过程中强制关闭安装程序 - 安装时突然断网(针对在线安装包) - 安装过程中设备断电/重启,验证恢复机制 #### 二、卸载功能测试 1. **常规卸载** - 通过系统控制面板/应用管理界面执行卸载 - 验证残留文件清除(包括安装目录、临时文件、注册表项) - 卸载后重新安装验证无冲突[^1] 2. **异常卸载场景** - 卸载过程中强制终止进程 - 手动删除部分文件后执行卸载程序 - 卸载后检查关联服务(如后台进程、通知服务)是否终止 3. **数据保留策略** - 卸载时选择"保留用户数据",重新安装后验证数据加载 - 卸载时选择"清除所有数据",验证账号信息、本地记录的清除 #### 三、环境兼容性测试 1. **操作系统适配** - Android:覆盖主流系统(Android 10-14)及厂商定制系统(MIUI、EMUI) - iOS:验证从TestFlight安装、App Store安装的差异 - Windows/macOS:针对PC端模拟器的安装卸载 2. **安全软件交互** - 安装过程中触发360安全卫士、Windows Defender等拦截时,验证提示合理性[^1] - 卸载时杀毒软件占用文件的情况处理 3. **硬件环境边界** - 低内存设备(如1GB RAM)安装时的性能阈值 - ARM/X86架构芯片的兼容性验证 #### 四、高级场景测试 1. **多进程冲突** - 同时启动两个安装进程检测互斥锁机制 - 安装过程中启动APP操作文件读写 2. **安装包完整性** - 数字签名验证失败的场景处理 - 分包安装(APK扩展文件/OBB)的下载校验 3. **静默安装验证** - 通过ADB命令`adb install -r -g app.apk`测试无界面安装 - 企业MDM(移动设备管理)批量部署场景
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值