You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android测试无需第三方应用模拟定位的方法及权限问题解决

Android Instrumentation测试模拟定位问题解决方案

问题描述

我需要给一款依赖实时定位的导航应用做Instrumentation测试,想在不借助第三方模拟定位工具的前提下,在Android设备上模拟当前位置。但尝试通过GrantPermissionRule申请android.permission.ACCESS_MOCK_LOCATION时,出现报错:

Permission: android.permission.ACCESS_MOCK_LOCATION cannot be granted!

我的测试代码如下:

@LargeTest
@RunWith(AndroidJUnit4ClassRunner::class)
class SpeedTTSWarningTrackingInstrumentedTests
{
// -------------------------------------------------------------------------------------------------
private var lm : LocationManager? = null
companion object
{
    private val appContext: Context = getApplicationContext()
    private val mainIntent = Intent(appContext, MainActivity::class.java)
    private var mActivityIdlingResource: IdlingResource? = null
}
// -------------------------------------------------------------------------------------------------
@Rule(order = 0)
@JvmField
val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
    Manifest.permission.INTERNET,
    Manifest.permission.ACCESS_NETWORK_STATE,
    Manifest.permission.ACCESS_FINE_LOCATION,
    Manifest.permission.ACCESS_COARSE_LOCATION,
    Manifest.permission.ACCESS_BACKGROUND_LOCATION,
    "android.permission.ACCESS_MOCK_LOCATION"
)


@Rule(order = 1)
@JvmField
val activityScenarioRule = ActivityScenarioRule<MainActivity>(mainIntent)

// -------------------------------------------------------------------------------------------------
@Before
fun beforeTests(){
    activityScenarioRule.scenario.moveToState(Lifecycle.State.RESUMED)
    runBlocking { delay(2000) }
    activityScenarioRule.scenario.onActivity { activity ->
        mActivityIdlingResource = activity.getActivityIdlingResource()
        IdlingRegistry.getInstance().register(mActivityIdlingResource)
    }
}

@After
fun closeActivity(){
    activityScenarioRule.scenario.close()
}
// -------------------------------------------------------------------------------------------------


@Test
fun changeLocation(){
    runBlocking { delay(10000) }
    mockLocation(45.661936, 25.608026)
    runBlocking { delay(10000) }
}

// -------------------------------------------------------------------------------------------------
private fun mockLocation(lat: Double, lon: Double)
{
    lm = getSystemService(appContext,LocationManager::class.java)
    val mocLocationProvider = LocationManager.FUSED_PROVIDER

    lm!!.addTestProvider(
        mocLocationProvider, false, false,
        false, false, true, true, true, 3, 2
    )

    lm!!.setTestProviderEnabled(mocLocationProvider, true)

    val mockLocation = Location(mocLocationProvider) // a string

    mockLocation.latitude = lat // double
    mockLocation.longitude = lon
    mockLocation.time = System.currentTimeMillis()
    mockLocation.accuracy = 1f
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
    {
        mockLocation.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
    }
    lm!!.setTestProviderLocation(mocLocationProvider, mockLocation)
}
}

我有几个疑问:

  1. 我的代码哪里出错了?该怎么修复?
  2. 已知ACCESS_MOCK_LOCATION从API 23开始被移除,现在模拟定位应该用什么方式?
  3. 我试过用Mock Location Provider、GrantPermissionRule,还单独创建了debug版本的AndroidManifest,这些方法是否正确?

问题根源与解决方案

1. 权限问题的核心原因

API 23(Android 6.0)之后,ACCESS_MOCK_LOCATION权限被彻底移除,模拟定位的授权不再通过运行时权限申请流程,而是需要:

  • 开启设备开发者选项中的允许模拟位置开关
  • 在开发者选项的选择模拟位置应用中,指定你的测试APP作为模拟位置的提供者
  • 确保APP已经申请了ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION权限(这两个权限仍需通过GrantPermissionRule申请)

你用GrantPermissionRule申请ACCESS_MOCK_LOCATION必然失败,因为这个权限已经不存在于高版本系统中。

2. 当前代码的错误点

  • 错误使用Fused Provider:LocationManager.FUSED_PROVIDER是系统内置的融合定位服务,不能直接作为测试Provider添加,必须自定义一个专属的测试Provider名称(比如"TEST_PROVIDER")
  • 未清理测试Provider:测试前后没有移除测试Provider,可能导致重复添加报错
  • 固定延迟不可靠:用runBlocking { delay() }等待页面加载,不如用IdlingResource监听页面状态更稳定

3. 正确的实现步骤

步骤一:配置Debug版本Manifest

在src/debug/AndroidManifest.xml中添加以下配置(只作用于测试环境,不影响正式包):

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 保留旧权限是为了兼容API 22及以下,高版本会自动忽略 -->
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" tools:ignore="MockLocationPermissions" />

<application>
    <!-- 可选:声明测试用定位Provider,让配置更规范 -->
    <provider
        android:name="android.location.LocationProvider"
        android:authorities="com.your.package.test.location"
        android:exported="false" />
</application>

步骤二:设备/模拟器前置配置

  1. 打开开发者选项(连续点击系统设置中的「版本号」7次)
  2. 找到「允许模拟位置」并开启
  3. 在「选择模拟位置应用」中,选中你的测试APP(通常包名带有.test后缀)

步骤三:修改测试代码

调整后的代码如下,修复了Provider使用问题,并添加了测试前后的资源清理:

@LargeTest
@RunWith(AndroidJUnit4ClassRunner::class)
class SpeedTTSWarningTrackingInstrumentedTests {
    private var lm: LocationManager? = null
    companion object {
        private val appContext: Context = getApplicationContext()
        private val mainIntent = Intent(appContext, MainActivity::class.java)
        private var mActivityIdlingResource: IdlingResource? = null
        // 自定义测试定位Provider名称
        private const val TEST_LOC_PROVIDER = "TEST_LOC_PROVIDER"
    }

    // 移除无效的ACCESS_MOCK_LOCATION权限申请
    @Rule(order = 0)
    @JvmField
    val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
        Manifest.permission.INTERNET,
        Manifest.permission.ACCESS_NETWORK_STATE,
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION,
        Manifest.permission.ACCESS_BACKGROUND_LOCATION
    )

    @Rule(order = 1)
    @JvmField
    val activityScenarioRule = ActivityScenarioRule<MainActivity>(mainIntent)

    @Before
    fun setupTests() {
        activityScenarioRule.scenario.moveToState(Lifecycle.State.RESUMED)
        // 用IdlingResource替代固定延迟,更可靠
        activityScenarioRule.scenario.onActivity { activity ->
            mActivityIdlingResource = activity.getActivityIdlingResource()
            IdlingRegistry.getInstance().register(mActivityIdlingResource)
        }

        // 初始化LocationManager并准备测试Provider
        lm = getSystemService(appContext, LocationManager::class.java)
        lm?.apply {
            // 先移除已存在的测试Provider,避免重复添加报错
            getProvider(TEST_LOC_PROVIDER)?.let {
                removeTestProvider(TEST_LOC_PROVIDER)
            }
            // 添加自定义测试Provider
            addTestProvider(
                TEST_LOC_PROVIDER,
                false, false, false, false,
                true, true, true, 0, 5
            )
            setTestProviderEnabled(TEST_LOC_PROVIDER, true)
        }
    }

    @After
    fun teardownTests() {
        // 清理测试资源
        lm?.apply {
            setTestProviderEnabled(TEST_LOC_PROVIDER, false)
            removeTestProvider(TEST_LOC_PROVIDER)
        }
        mActivityIdlingResource?.let {
            IdlingRegistry.getInstance().unregister(it)
        }
        activityScenarioRule.scenario.close()
    }

    @Test
    fun testLocationChange() {
        // 等待页面初始化完成(依赖IdlingResource)
        mockLocation(45.661936, 25.608026)
        // 这里可以添加断言,验证UI是否正确响应了位置变化
    }

    private fun mockLocation(lat: Double, lon: Double) {
        lm?.let { locationManager ->
            val mockLocation = Location(TEST_LOC_PROVIDER).apply {
                latitude = lat
                longitude = lon
                time = System.currentTimeMillis()
                accuracy = 1f
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                    elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    provider = TEST_LOC_PROVIDER
                }
            }
            // 设置模拟位置
            locationManager.setTestProviderLocation(TEST_LOC_PROVIDER, mockLocation)
            // 发送广播通知系统定位更新
            locationManager.sendBroadcast(Intent(LocationManager.PROVIDERS_CHANGED_ACTION))
        }
    }
}

4. 关于你尝试的方法的正确性

  • Mock Location Provider:思路正确,但不能直接使用系统的Fused Provider,必须自定义测试Provider
  • GrantPermissionRule:只适用于普通危险权限,模拟位置的授权不通过这个流程,所以申请ACCESS_MOCK_LOCATION无效,应该移除
  • debug AndroidManifest:是正确的做法,通过debug专属配置隔离测试环境和正式环境,非常推荐

额外技巧

如果使用模拟器测试,可以直接用ADB命令快速设置模拟位置,无需编写代码:

adb shell am set-location -e 45.661936 25.608026

内容的提问来源于stack exchange,提问作者Purple6666

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 02:59:53