没有触发的地理围栏具有模拟位置

时间:2015-01-29 17:00:43

标签: android unit-testing geolocation android-location geofencing

我正在尝试为位置感知应用程序测试GeoFences。我已经获得了多达11次连续成功,但通常会有数十次失败。重新启动设备没有帮助。卸载/重新安装没有帮助。禁用位置,重新启用位置无济于事。更改设备位置准确度设置没有帮助。

我已经在v2.3.4,v4.0.3,v4.4.2,v4.4.4和2 v5.0.1物理设备上进行了测试。其中一个Lollipop设备是具有蜂窝服务的设备。其余设备是SIMless的,仅用于测试。旁注:没有服务的设备很难通过MockLocation设置它们的位置,但是如果他们设法设置它们的位置,他们几乎从不注册围栏入口/出口。

位置正在通过Mock Location更新,但GeoFence不会被任何可靠性触发。

我用谷歌搜索了它。我查看了developer.android.com网站上的文档。我搜索过StackOverflow。事实上,其他人提出的许多建议已经在下面的代码中实现。

那么,如何使用MockLocation可靠地触发我的GeoFence?

import android.location.Location;
import android.location.LocationManager;

public class GeoFenceTester extends ActivityInstrumentationTestCase2<GeoFenceTesterActivity> {


    public static final String TAG = GeoFenceTester.class.getSimpleName();

    private LocationManager locationManager;
    private Activity activityUnderTest;

    protected void setUp() throws Exception {
        super.setUp();
        activityUnderTest = getActivity();
        /*
            MOCK Locations are required for these tests.  If the user has not enabled MOCK Locations
            on the device or emulator these tests cannot work.
         */
        if (Settings.Secure.getInt(activityUnderTest.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) == 0) {
            throw new RuntimeException("Mock locations are currently disabled in Settings - These tests require mock locations");
        }

        Log.i(TAG, "Setup MOCK Location Providers");
        locationManager = (LocationManager) activityUnderTest.getSystemService(Context.LOCATION_SERVICE);

        Log.i(TAG, "GPS Provider");
        locationManager.addTestProvider(LocationManager.GPS_PROVIDER, false, true, false, false, false, false, false, Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);
        locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);

        Log.i(TAG, "Network Provider");
        locationManager.addTestProvider(LocationManager.NETWORK_PROVIDER, true, false, true, false, false, false, false, Criteria.POWER_MEDIUM, Criteria.ACCURACY_FINE);
        locationManager.setTestProviderEnabled(LocationManager.NETWORK_PROVIDER, true);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            Log.wtf(TAG, String.format("Location Accuracy: %1$d", Settings.Secure.getInt(activityUnderTest.getContentResolver(), Settings.Secure.LOCATION_MODE)));
        }

        /* Our EventBus for onEvent() callbacks */
        EventBus.getDefault().register(this);
    }

    protected void tearDown() {
        /* Our EventBus for onEvent() callbacks */
        EventBus.getDefault().unregister(this);

        locationManager.removeTestProvider(LocationManager.GPS_PROVIDER);
        locationManager.removeTestProvider(LocationManager.NETWORK_PROVIDER);
        locationManager = null;

        activityUnderTest = null;

        super.tearDown();
    }

    public void testGeoFence() {

        /*
            Using one or the other or both of these makes no difference.
        */
        Location mockGpsLocation = new Location(LocationManager.GPS_PROVIDER);
        mockGpsLocation.setLatitude(32.652411);
        mockGpsLocation.setLongitude(-79.938063);
        mockGpsLocation.setAccuracy(1.0f);
        mockGpsLocation.setTime(System.currentTimeMillis());

        Location mockNetworkLocation = new Location(LocationManager.NETWORK_PROVIDER);
        mockNetworkLocation.setLatitude(32.652411);
        mockNetworkLocation.setLongitude(-79.938063);
        mockNetworkLocation.setAccuracy(1.0f);
        mockNetworkLocation.setTime(System.currentTimeMillis());

        /*
            setElapsedRealtimeNanos() was added in API 17
         */
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            mockGpsLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
            mockNetworkLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
        }

        try {
            Method locationJellyBeanFixMethod = Location.class.getMethod("makeComplete");
            if (locationJellyBeanFixMethod != null) {
                locationJellyBeanFixMethod.invoke(mockGpsLocation);
                locationJellyBeanFixMethod.invoke(mockNetworkLocation);
            }
        } catch (Exception e) {
            // There's no action to take here.  This is a fix for Jelly Bean and no reason to report a failure.
        }

        for (int i = 0; i < 30; i++){
            Log.i(TAG, String.format("Iterating over our location ... (%1$d)", i));
            locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, mockGpsLocation);
            locationManager.setTestProviderLocation(LocationManager.NETWORK_PROVIDER, mockNetworkLocation);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage(), e);
            }
        }
    }

    public void onEvent(final GeoFenceUpdateEvent event) {
        // This doesn't get called about 9/10 times.
        // I have a GeoFence with a 5000m radius around 32.652411, -79.938063
    }

    public void onEvent(final LocationUpdateEvent event) {
        // This gets called without incident and event.getLatitude() & event.getLongitude() are correct.
    }

}

1 个答案:

答案 0 :(得分:3)

Geofence管理员将平等地考虑所有系统范围的位置请求,而不仅仅是您的特定应用程序所提出的请求。如果您在设备上有任何其他正在使用定位服务的应用程序或服务,这些外部应用程序将生成在触发您的地理围栏时使用的Lat / Lon位置。

鉴于您正在制作Mock Locations,外部应用程序或服务可能会产生冲突的LocationUpdates,并且Mock和Real位置之间的这些跳转会导致您的Geofence测试结果不稳定。

您可以通过在设置中禁用真实的位置服务并纯粹使用模拟位置来确认这一点,或查找当前可能正在使用位置服务的任何应用程序,并在这些特定应用程序停止或禁用后重新测试。