Android类似钉钉类的APP 实现禁止用户虚拟定位

参考:https://blog.csdn.net/mawei7510/article/details/80250416

在应用开发中,如果有签到打卡之类的功能,我们肯定需要在项目中禁止用户开启虚拟定位,导致在***米之外的距离模拟定位然后进行了打卡操作!

(一)

首先:获取用户手机是否打开了  “允许模拟位置”  选项?

其实很简单,这些设置项,基本都是写在数据库里,所以只要看看setting的源码(或者查看logcat可能也可以得到些有用的信息),就能知道该配置是写了数据库的哪个字段。

boolean isOpen = Settings.Secure.getInt(context.getContentResolver(),Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0;
//很明显,Settings.Secure.ALLOW_MOCK_LOCATION 就是存放允许模拟位置的数据库字段了
//当isOpen为ture时,则是用户打开了允许模拟位置的选项,否则则没有开启!

(二)

其次:虚拟定位可以通过一些第三方应用,然后把我们自己的应用克隆一个。在启动的时候需要在第三方的应用里面来启动,这样的话在私有文件里面生成的包名势必会和直接启动自己的应用有区别,知道了这些,我们将通过以下三个方法来一一检测;

 

首先介绍一些那些使用应用分身双开和虚拟定位的应用和自己的应用在私有目录下生成的包名有什么区别:

我们知道App的私有目录是/data/data/包名//data/user/用户号/包名,通过Context.getFilesDir()方法可以拿到私有目录下的files目录。在多开环境下,获取到目录会变为/data/data/多开App的包名/xxxxxxxx/data/user/用户号/多开App的包名/xxxxxxxx

举个例子,在我手机上,正常使用App上面的代码获取到的路径为/data/user/0/top.darkness463.virtualcheck/files。在多开分身的多开环境下,路径为/data/user/0/dkmodel.zom.rxo/virtual/data/user/0/top.darkness463.virtualcheck/files

当然,多开软件是可以hook处理让你拿到正常的目录,但截至写这篇文章为止,市面上大部分多开App没有绕过这项检测,仅有360家的分身大师可以绕过。

下面开始正式检测:

1. ps检测(详见https://www.jianshu.com/p/216d65d9971e

我们先通过执行对uid进行过滤,得到类似下面的结果

// 正常情况下
u0_a148 8162 423 1806036 56368 SyS_epoll+ 0 S top.darkness463.virtualcheck

// 多开环境下
u0_a155 19752 422 4437612 62752 SyS_epoll+ 0 S top.darkness463.virtualcheck
u0_a155 19758 422 564234 54356 SyS_epoll+ 0 S com.lbe.parallel
u0_a155 19747 422 734562 24542 SyS_epoll+ 0 S com.lbe.parallel:mdserver

 

可以看到在多开环境下,会获取到自己的包名和多开App的包名这2个包名,通过这些包名去/data/data/下找会找到2个目录,而正常情况下只能在/data/data/下找到自己的App的目录。看下具体代码实现;

public static boolean isRunInVirtual() {

    String filter = getUidStrFormat();

    String result = exec("ps");

    if (result == null || result.isEmpty()) {

        return false;

    }

    String[] lines = result.split("\n");

    if (lines == null || lines.length <= 0) {

        return false;

    }

    int exitDirCount = 0;

    for (int i = 0; i < lines.length; i++) {

        if (lines[i].contains(filter)) {

            int pkgStartIndex = lines[i].lastIndexOf(" ");

            String processName = lines[i].substring(pkgStartIndex <= 0

                    ? 0 : pkgStartIndex + 1, lines[i].length());

            File dataFile = new File(String.format("/data/data/%s",

                    processName, Locale.CHINA));

            if (dataFile.exists()) {

                exitDirCount++;
            }
        }
    }

    return exitDirCount > 1;

}

这里的应用列表检测不是指简单的遍历应用列表判断是不是安装了多开App,我们并不阻止用户安装多开App并多开其他App,我们只是不希望用户多开我们自己的App,因此不能检测到用户安装了多开App就把他干掉。

2.应用列表检测

多开App都会对context.getPackageName()进行处理,让这个方法返回原始App的包名,因此在被多开的App看来,多开App的包名和原始的那个App的包名一样,因此在多开环境下遍历应用列表时会发现包名等于原始App的包名的应用会有两个。

private boolean checkPkg(Context context) {
    try {
        if (context == null) {
            return false;
        }
        int count = 0;
        String packageName = context.getPackageName();
        PackageManager pm = context.getPackageManager();
        List<PackageInfo> pkgs = pm.getInstalledPackages(0);
        for (PackageInfo info : pkgs) {
            if (packageName.equals(info.packageName)) {
                count++;
            }
        }
        return count > 1;
    } catch (Exception ignore) {}
    return false;
}

3.maps检测

读取/proc/self/maps,多开App会加载一些自己的so到内存空间,举个例子,360的分身大师加载了其目录下的某个so,/data/app/com.qihoo.magic-gdEsg8KRAuJy0MuY18BlqQ==/lib/arm/libbreakpad-jni-1.5.so,通过对各种多开App的包名的匹配,如果maps中有多开App的包名的东西,那么当前就是运行在多开环境下。目前没有发现多开App绕过该项检测,但缺点是需要收集所有多开App的包名,一旦多开App改个包名就失效了。

复制代码

Set<String> virtualPkgs;  // 多开第三方App包名列表
private boolean check() {
    BufferedReader bufr = null;
    try {
        bufr = new BufferedReader(new FileReader("/proc/self/maps"));
        String line;
        while ((line = bufr.readLine()) != null) {
            for (String pkg : virtualPkgs) {
                if (line.contains(pkg)) {
                    return true;
                }
            }
        }
    } catch (Exception ignore) {
        
    } finally {
        if (bufr != null) {
            try {
                bufr.close();
            } catch (IOException e) {
                
            }
        }
    }
    return false;
}

 

以上三种检测方法有的会被第三方虚拟定位软件或者多开分身软件躲避掉,有的则不会,所以使用的时候建议三种方法全部用上。

  • 7
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Android 10 中,由于 Google 引入了分区存储和后台限制等新功能,`android:stopWithTask` 属性已被弃用,无法再用于禁止用户手动杀死应用程序。相反,您可以使用前台服务来实现此功能。 具体来说,您可以使用 `startForegroundService()` 方法启动一个前台服务,并在服务中使用 `startForeground()` 方法将应用程序启动到前台。这样,即使用户手动杀死应用程序,它也仍然会在后台运行,并且可以通过通知栏显示相关信息,以提醒用户应用程序正在运行。需要注意的是,您需要在通知栏中显示一个通知,以满足 Android 系统的前台服务要求。 以下是一个示例代码: ```java public class MyService extends Service { private static final int NOTIFICATION_ID = 123; @Override public int onStartCommand(Intent intent, int flags, int startId) { // 创建一个通知 Notification notification = new NotificationCompat.Builder(this, "channel_id") .setContentTitle("My App is running") .setContentText("Click to open the app") .setSmallIcon(R.drawable.ic_launcher) .build(); // 启动前台服务 startForeground(NOTIFICATION_ID, notification); // 执行您的业务逻辑 return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); // 停止前台服务 stopForeground(true); } @Nullable @Override public IBinder onBind(Intent intent) { return null; } } ``` 在此示例中,我们创建了一个前台服务 `MyService`,并在 `onStartCommand()` 方法中启动了该服务。在服务启动时,我们创建了一个通知,并使用 `startForeground()` 方法将应用程序启动到前台。在服务停止时,我们使用 `stopForeground()` 方法停止前台服务。 需要注意的是,如果您的应用程序需要在后台运行长时间任务,您可能需要使用 `startForeground()` 方法来启动更高优先级的前台服务,以避免 Android 系统的后台限制。同时,您需要确保您的应用程序不会过度使用设备资源,以免影响用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值