返回

Android API 31 以下模拟位置检测最佳实践

Android

Android API 31 以下模拟位置检测方案

Android应用中检测模拟位置对安全性和数据准确性至关重要。Location.isFromMockProvider() 方法虽已废弃,但在API级别低于31的设备上,直接使用会导致问题,它始终返回 false,即使正在使用模拟位置。这就需要针对不同API级别设备,找到合适的模拟位置检测方案。

方案一:检查允许模拟位置的应用

一种方案是检查是否开启了开发者选项中的允许模拟位置功能,并识别提供模拟位置的应用。 这是一种在较低API级别设备上较可靠的方式。具体做法如下:

  1. 获取应用列表: 获取系统中已安装应用的列表。

  2. 检查Settings.Secure.ALLOW_MOCK_LOCATION 读取这个系统设置,判断模拟位置是否已开启。

  3. 检查Provider: 获取模拟位置应用所在的PackageName, 利用系统API的特性, 检测特定的位置提供程序, 然后在特定条件和特定Android版本检测,返回是否被 Mock, 该部分实现见代码实例部分。

    如果允许模拟位置并且已启用模拟位置提供程序,则可以相当确信位置是模拟的。但是请注意, 如果设备开启允许模拟位置的应用, 返回的结果会是真实位置, 而不是被Mock的位置.

  4. 需要注意

    • 该方案依赖于Settings.Secure.ALLOW_MOCK_LOCATION的设置状态和已安装的应用情况,有一定的局限性。
    • 一些厂商的定制系统可能对此项设置有影响,请充分测试不同设备下的兼容性。
  5. 附加的安全提示

  • 请谨慎处理从位置API收集的任何信息。 用户可能会欺骗该API,但不能欺骗该信息对于您的应用的意义。请务必彻底验证位置。
  • 无论您的位置数据来源如何,都请进行验证,而不要仅仅信任它是正确的。

代码示例:

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.provider.Settings;
import java.util.List;

public class MockLocationDetector {
    
    private final Context context;

    public MockLocationDetector(Context context) {
        this.context = context;
    }
    public boolean isMockLocationEnabled(){
        // Android 23及更高版本使用Secure
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
             return Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION,0) != 0;
         } else {
           // 在低于API 23版本中使用Setting
            return  Settings.System.getInt(context.getContentResolver(),Settings.Secure.ALLOW_MOCK_LOCATION,0) !=0;
        }
    }


    public boolean isMockLocation(Location location) {
        //如果SDK_INT >=31, 使用 isMock()
         if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
              return location.isMock();
        }
       // 如果SDK_INT <31, 需要检查是否是mock providers

        boolean isMockSettingsON = isMockLocationEnabled();

        if(!isMockSettingsON) {
              return false;
        }

          String mockProviders = android.provider.Settings.Secure.getString(
                context.getContentResolver(), android.provider.Settings.Secure.MOCK_LOCATION);
                if(mockProviders==null|| "".equals(mockProviders)) {
                 return false;
              }

              List<String> providerlist=java.util.Arrays.asList(mockProviders.split(","));

             return  providerlist.contains(location.getProvider());

    }

}

步骤:

  1. 创建一个 MockLocationDetector 类,构造方法传入 Context
  2. 实现isMockLocationEnabled(), 使用Settings 获取ALLOW_MOCK_LOCATION 的值。
  3. 实现 isMockLocation(Location location),根据API级别选择对应的mock 监测方案。
    • API 31+直接使用 location.isMock()
    • API 31以下先检查Settings.Secure.ALLOW_MOCK_LOCATION 是否允许Mock, 再检测获取模拟位置 provider是否包含在系统的 Provider列表里。
  4. 在代码中使用: boolean isMocked = new MockLocationDetector(context).isMockLocation(location);
    • 可以对返回的boolean 进行判断,如果 isMocked == true 代表为Mock,反之则为真。

方案二: 使用其他位置数据

由于Android系统可能修改 Settings.Secure.ALLOW_MOCK_LOCATION 或存在设备差异,完全依赖该值可能无法百分百准确地判断模拟位置。另外一种增强可靠性的方法,同时利用系统其它传感器信息,并比对他们的差异,实现模拟位置的综合检测。

  1. 传感器信息
  • 加速度计和陀螺仪, 获取传感器的原始数据,分析其数据分布。如果位置移动与传感器的活动不符,有可能表明是模拟位置。
  1. 网络位置和基站信息

    将位置数据和网络信息和周围基站信息比较, 模拟位置无法轻易模拟附近的手机信号基站信息。将定位与网络或者基站信息进行对比,能够判断当前的位置的真实性。

  • 获取WIFI或者基站信息,比对网络环境位置。模拟位置很难模拟手机附近的WIFI以及手机基站位置信息。
  • 获取位置附近区域是否有地标建筑,比如: 商场, 学校,医院, 等信息。根据比对结果,可以一定程度确认是否是 Mock。
  1. 实现注意点:
  • 该方案的复杂性更高,需要较多的计算和数据分析。实现过程和方案比较复杂,本文档不赘述详细的代码示例。
  • 实现该方案需要综合考量设备差异,资源消耗等因素。建议按需取用,选择合适你的应用场景。

步骤:

  1. 集成Android传感器服务,获取加速度和陀螺仪的数据,分析移动特征。
  2. 集成位置信息服务,获取网络定位,wifi基站信息,并进行比对分析。
  3. 综合分析多方面的信息数据,判断当前位置是否可信。

检测模拟位置需要一种综合的方法。单独使用Location.isMock()在低版本上不适用,依赖单个方案会有局限性。组合多种方式检测,能更好的避免各种模拟位置的绕过和干扰,有效提高数据安全性。 实际项目中,请务必全面测试各种Android版本,保证应用的鲁棒性。